├── .travis.yml ├── .travis └── config.yaml ├── LICENSE ├── README.md ├── _examples └── state │ └── main.go ├── client.go ├── common.go ├── common_test.go ├── connection.go ├── files.go ├── files_test.go ├── job.go ├── printer.go ├── printer_test.go ├── settings.go ├── settings_test.go ├── system.go ├── system_test.go └── version.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | sudo: required 4 | 5 | services: 6 | - docker 7 | 8 | go: 9 | - 1.9 10 | 11 | before_install: 12 | - docker run -d -p 5000:5000 -v $PWD/.travis/config.yaml:/home/octoprint/.octoprint/config.yaml --name octoprint octoprint/octoprint 13 | - sleep 5 14 | 15 | script: 16 | - go get -t -v ./... 17 | - go test -v ./... 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.travis/config.yaml: -------------------------------------------------------------------------------- 1 | accessControl: 2 | enabled: false 3 | api: 4 | key: 48C135F39D9B4CF593C89F0B8424EA3F 5 | controls: 6 | - children: 7 | - commands: 8 | - G91 9 | - G1 X10 F3000 10 | - G90 11 | confirm: You are about to move the X axis right by 10mm with 3000mm/s. 12 | name: Move X (static) 13 | name: Example for multiple commands 14 | devel: 15 | virtualPrinter: 16 | enabled: true 17 | plugins: 18 | announcements: 19 | _config_version: 1 20 | channels: 21 | _blog: 22 | read_until: 1512119700 23 | _important: 24 | read_until: 1509547500 25 | _octopi: 26 | read_until: 1499253000 27 | _plugins: 28 | read_until: 1512172800 29 | _releases: 30 | read_until: 1513079100 31 | discovery: 32 | upnpUuid: 083dba00-d405-45e5-abc6-34846194d25a 33 | softwareupdate: 34 | _config_version: 6 35 | printerProfiles: 36 | default: _default 37 | serial: 38 | autoconnect: true 39 | baudrate: 0 40 | port: VIRTUAL 41 | server: 42 | commands: 43 | systemShutdownCommand: echo "foo" 44 | firstRun: false 45 | onlineCheck: 46 | enabled: false 47 | pluginBlacklist: 48 | enabled: false 49 | secretKey: Gi1BdGGbrO8CWPwCxgmmHSWGwcDwYuXL 50 | seenWizards: 51 | corewizard: 3 52 | cura: null 53 | system: 54 | actions: [] 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Máximo Cuadros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-octoprint [![Build Status](https://travis-ci.org/mcuadros/go-octoprint.svg?branch=master)](https://travis-ci.org/mcuadros/go-octoprint) [![GoDoc](http://godoc.org/github.com/mcuadros/go-octoprint?status.svg)](http://godoc.org/github.com/mcuadros/go-octoprint) 2 | ============================== 3 | 4 | Go library for accessing the [OctoPrint](http://octoprint.org/)'s [REST API](http://docs.octoprint.org/en/master/api/index.html). 5 | 6 | Installation 7 | ------------ 8 | 9 | The recommended way to install go-octoprint 10 | 11 | ``` 12 | go get github.com/mcuadros/go-octoprint 13 | ``` 14 | 15 | Example 16 | ------- 17 | 18 | ### Retrieving the current connection state: 19 | 20 | ```go 21 | client, _ := NewClient("", "") 22 | 23 | r := octoprint.ConnectionRequest{} 24 | s, err := r.Do(client) 25 | if err != nil { 26 | log.Error("error requesting connection state: %s", err) 27 | } 28 | 29 | fmt.Printf("Connection State: %q\n", s.Current.State) 30 | ``` 31 | 32 | 33 | ### Retrieving current temperature for bed and extruders: 34 | 35 | ```go 36 | r := octoprint.StateRequest{} 37 | s, err := r.Do(c) 38 | if err != nil { 39 | log.Error("error requesting state: %s", err) 40 | } 41 | 42 | fmt.Println("Current Temperatures:") 43 | for tool, state := range s.Temperature.Current { 44 | fmt.Printf("- %s: %.1f°C / %.1f°C\n", tool, state.Actual, state.Target) 45 | } 46 | ``` 47 | 48 | ## Implemented Methods 49 | 50 | ### [Version Information](http://docs.octoprint.org/en/master/api/version.html) 51 | - [x] GET `/api/version` 52 | 53 | ### [Apps](http://docs.octoprint.org/en/master/api/apps.html) 54 | - [ ] GET `/apps/auth` 55 | - [ ] POST `/apps/auth` 56 | 57 | ### [Connection Operations](http://docs.octoprint.org/en/master/api/connection.html) 58 | - [x] GET `/api/connection` 59 | - [x] POST `/api/connection` 60 | 61 | ### [File Operations](http://docs.octoprint.org/en/master/api/files.html) 62 | - [x] GET `/api/files 63 | - [x] GET `/api/files/` 64 | - [x] POST `/api/files/` 65 | - [x] GET `/api/files//` 66 | - [x] POST `/api/files//` (Only select command) 67 | - [x] DELETE `/api/files//` 68 | 69 | ### [Job Operations](http://docs.octoprint.org/en/master/api/job.html) 70 | - [x] POST `/api/job` 71 | - [x] GET `/api/job` 72 | 73 | ### [Languages](http://docs.octoprint.org/en/master/api/languages.html) 74 | - [ ] GET `/api/languages` 75 | - [ ] POST `/api/languages` 76 | - [ ] DELETE `/api/languages//` 77 | 78 | ### [Log file management](http://docs.octoprint.org/en/master/api/logs.html) 79 | - [ ] GET `/api/logs` 80 | - [ ] DELETE `/api/logs/` 81 | 82 | ### [Printer Operations](http://docs.octoprint.org/en/master/api/printer.html) 83 | - [x] GET `/api/printer` 84 | - [x] POST `/api/printer/printhead` 85 | - [x] POST `/api/printer/tool` 86 | - [x] GET `/api/printer/tool` 87 | - [x] POST `/api/printer/bed` 88 | - [x] GET `/api/printer/bed` 89 | - [x] POST `/api/printer/sd` 90 | - [x] GET `/api/printer/sd` 91 | - [x] POST `/api/printer/command` 92 | - [x] GET `/api/printer/command/custom` ([un-documented at REST API](https://github.com/foosel/OctoPrint/blob/7f5d03d0549bcbd26f40e7e4a3297ea5204fb1cc/src/octoprint/server/api/printer.py#L376)) 93 | 94 | ### [Printer profile operations](http://docs.octoprint.org/en/master/api/printerprofiles.html) 95 | - [ ] GET `/api/printerprofiles` 96 | - [ ] POST `/api/printerprofiles` 97 | - [ ] PATCH `/api/printerprofiles/` 98 | - [ ] DELETE `/api/printerprofiles/` 99 | 100 | ### [Settings](http://docs.octoprint.org/en/master/api/settings.html) 101 | - [x] GET `/api/settings` 102 | - [ ] POST `/api/settings` 103 | - [ ] POST `/api/settings/apikey` 104 | 105 | ### [Slicing](http://docs.octoprint.org/en/master/api/slicing.html) 106 | - [ ] GET `/api/slicing` 107 | - [ ] GET `/api/slicing//profiles` 108 | - [ ] GET `/api/slicing//profiles/` 109 | - [ ] PUT `/api/slicing//profiles/` 110 | - [ ] DELETE `/api/slicing//profiles/` 111 | 112 | ### [System](http://docs.octoprint.org/en/master/api/system.html) 113 | - [x] GET `/api/system/commands` 114 | - [ ] GET `/api/system/commands/` 115 | - [x] POST `/api/system/commands//` 116 | 117 | ### [Timelapse](http://docs.octoprint.org/en/master/api/timelapse.html) 118 | - [ ] GET `/api/timelapse` 119 | - [ ] DELETE `/api/timelapse/` 120 | - [ ] POST `/api/timelapse/unrendered/` 121 | - [ ] DELETE `/api/timelapse/unrendered/` 122 | - [ ] POST `/api/timelapse` 123 | 124 | ### [User](http://docs.octoprint.org/en/master/api/users.html) 125 | - [ ] GET `/api/users` 126 | - [ ] GET `/api/users/` 127 | - [ ] POST `/api/users` 128 | - [ ] PUT `/api/users/` 129 | - [ ] DELETE `/api/users/` 130 | - [ ] PUT `/api/users//password` 131 | - [ ] GET `/api/users//settings` 132 | - [ ] PATCH `/api/users//settings` 133 | - [ ] POST `/api/users//apikey` 134 | - [ ] DELETE `/api/users//apikey` 135 | 136 | ### [Util](http://docs.octoprint.org/en/master/api/util.html) 137 | - [ ] POST `/api/util/test` 138 | 139 | ### [Wizard](http://docs.octoprint.org/en/master/api/wizard.html) 140 | - [ ] GET `/setup/wizard` 141 | - [ ] POST `/setup/wizard` 142 | 143 | License 144 | ------- 145 | 146 | MIT, see [LICENSE](LICENSE) 147 | -------------------------------------------------------------------------------- /_examples/state/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/mcuadros/go-octoprint" 8 | "github.com/mgutz/logxi/v1" 9 | ) 10 | 11 | func main() { 12 | baseURL, apiKey := os.Args[1], os.Args[2] 13 | 14 | c := octoprint.NewClient(baseURL, apiKey) 15 | printConnectionState(c) 16 | printTemperature(c) 17 | } 18 | 19 | func printConnectionState(c *octoprint.Client) { 20 | r := octoprint.ConnectionRequest{} 21 | s, err := r.Do(c) 22 | if err != nil { 23 | log.Error("error requesting connection state: %s", err) 24 | } 25 | 26 | fmt.Printf("Connection State: %q\n", s.Current.State) 27 | } 28 | 29 | func printTemperature(c *octoprint.Client) { 30 | r := octoprint.StateRequest{} 31 | s, err := r.Do(c) 32 | if err != nil { 33 | log.Error("error requesting state: %s", err) 34 | } 35 | 36 | fmt.Println("Current Temperatures:") 37 | for tool, state := range s.Temperature.Current { 38 | fmt.Printf("- %s: %.1f°C / %.1f°C\n", tool, state.Actual, state.Target) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | // ErrUnauthorized missing or invalid API key 13 | var ErrUnauthorized = errors.New("Missing or invalid API key") 14 | 15 | // A Client manages communication with the OctoPrint API. 16 | type Client struct { 17 | // Endpoint address to the OctoPrint REST API server. 18 | Endpoint string 19 | // APIKey used to connect to the OctoPrint REST API server. 20 | APIKey string 21 | 22 | c *http.Client 23 | } 24 | 25 | // NewClient returns a new OctoPrint API client with provided base URL and API 26 | // Key. If baseURL does not have a trailing slash, one is added automatically. If 27 | // `Access Control` is enabled at OctoPrint configuration an apiKey should be 28 | // provided (http://docs.octoprint.org/en/master/api/general.html#authorization). 29 | func NewClient(endpoint, apiKey string) *Client { 30 | return &Client{ 31 | Endpoint: endpoint, 32 | APIKey: apiKey, 33 | c: &http.Client{ 34 | Transport: &http.Transport{ 35 | DisableKeepAlives: true, 36 | }, 37 | }, 38 | } 39 | } 40 | 41 | func (c *Client) doJSONRequest( 42 | method, target string, body io.Reader, m statusMapping, 43 | ) ([]byte, error) { 44 | return c.doRequest(method, target, "application/json", body, m) 45 | } 46 | 47 | func (c *Client) doRequest( 48 | method, target, contentType string, body io.Reader, m statusMapping, 49 | ) ([]byte, error) { 50 | req, err := http.NewRequest(method, joinURL(c.Endpoint, target), body) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | req.Header.Add("Host", "localhost:5000") 56 | req.Header.Add("Accept", "*/*") 57 | req.Header.Add("User-Agent", fmt.Sprintf("go-octoprint/%s", Version)) 58 | if contentType != "" { 59 | req.Header.Add("Content-Type", contentType) 60 | } 61 | 62 | req.Header.Add("X-Api-Key", c.APIKey) 63 | 64 | resp, err := c.c.Do(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return c.handleResponse(resp, m) 70 | } 71 | 72 | func (c *Client) handleResponse(r *http.Response, m statusMapping) ([]byte, error) { 73 | defer r.Body.Close() 74 | 75 | if m != nil { 76 | if err := m.Error(r.StatusCode); err != nil { 77 | return nil, err 78 | } 79 | } 80 | 81 | if r.StatusCode == 401 { 82 | return nil, ErrUnauthorized 83 | } 84 | 85 | if r.StatusCode == 204 { 86 | return nil, nil 87 | } 88 | 89 | body, err := ioutil.ReadAll(r.Body) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | if r.StatusCode >= 200 && r.StatusCode <= 209 { 95 | return body, nil 96 | } 97 | 98 | return nil, fmt.Errorf("unexpected status code: %d", r.StatusCode) 99 | } 100 | 101 | func joinURL(base, uri string) string { 102 | u, _ := url.Parse(uri) 103 | b, _ := url.Parse(base) 104 | return b.ResolveReference(u).String() 105 | } 106 | 107 | type statusMapping map[int]string 108 | 109 | func (m *statusMapping) Error(code int) error { 110 | err, ok := (*m)[code] 111 | if ok { 112 | return fmt.Errorf(err) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | var Version = "0.1" 11 | 12 | type Axis string 13 | 14 | const ( 15 | XAxis Axis = "x" 16 | YAxis Axis = "y" 17 | ZAxis Axis = "z" 18 | ) 19 | 20 | // FullStateResponse contains informantion about the current state of the printer. 21 | type FullStateResponse struct { 22 | //Temperature is the printer’s temperature state data. 23 | Temperature TemperatureState `json:"temperature"` 24 | // SD is the printer’s sd state data. 25 | SD SDState `json:"sd"` 26 | // State is the printer’s general state. 27 | State PrinterState `json:"state"` 28 | } 29 | 30 | // JobResponse is the response from a job command. 31 | type JobResponse struct { 32 | // Job contains information regarding the target of the current print job. 33 | Job JobInformation `json:"job"` 34 | // Progress contains information regarding the progress of the current job. 35 | Progress ProgressInformation `json:"progress"` 36 | } 37 | 38 | // JobInformation contains information regarding the target of the current job. 39 | type JobInformation struct { 40 | // File is the file that is the target of the current print job. 41 | File FileInformation `json:"file"` 42 | // EstimatedPrintTime is the estimated print time for the file, in seconds. 43 | EstimatedPrintTime float64 `json:"estimatedPrintTime"` 44 | // LastPrintTime is the print time of the last print of the file, in seconds. 45 | LastPrintTime float64 `json:"lastPrintTime"` 46 | // Filament contains Information regarding the estimated filament 47 | // usage of the print job. 48 | Filament struct { 49 | // Length of filament used, in mm 50 | Length float64 `json:"length"` 51 | // Volume of filament used, in cm³ 52 | Volume float64 `json:"volume"` 53 | } `json:"filament"` 54 | FilePosition uint64 `json:"filepos"` 55 | } 56 | 57 | // ProgressInformation contains information regarding the progress of the 58 | // current print job. 59 | type ProgressInformation struct { 60 | // Completion percentage of completion of the current print job. 61 | Completion float64 `json:"completion"` 62 | // FilePosition current position in the file being printed, in bytes 63 | // from the beginning. 64 | FilePosition uint64 `json:"filepos"` 65 | // PrintTime is time already spent printing, in seconds 66 | PrintTime float64 `json:"printTime"` 67 | // PrintTimeLeft is estimate of time left to print, in seconds 68 | PrintTimeLeft float64 `json:"printTimeLeft"` 69 | } 70 | 71 | // TemperatureState is the printer’s temperature state data. 72 | type TemperatureState temperatureState 73 | type temperatureState struct { 74 | // Current temperature stats. 75 | Current map[string]TemperatureData `json:"current"` 76 | // Temperature history. 77 | History []*HistoricTemperatureData `json:"history"` 78 | } 79 | 80 | func (r *TemperatureState) UnmarshalJSON(b []byte) error { 81 | var raw map[string]interface{} 82 | if err := json.Unmarshal(b, &raw); err != nil { 83 | return err 84 | } 85 | 86 | history := raw["history"] 87 | delete(raw, "history") 88 | b, _ = json.Marshal(map[string]interface{}{ 89 | "current": raw, 90 | "history": history, 91 | }) 92 | 93 | i := &temperatureState{} 94 | if err := json.Unmarshal(b, i); err != nil { 95 | return err 96 | } 97 | 98 | *r = TemperatureState(*i) 99 | return nil 100 | } 101 | 102 | // TemperatureData is temperature stats for a tool. 103 | type TemperatureData struct { 104 | // Actual current temperature. 105 | Actual float64 `json:"actual"` 106 | // Target temperature, may be nil if no target temperature is set. 107 | Target float64 `json:"target"` 108 | // Offset currently configured temperature offset to apply, will be left 109 | // out for historic temperature information. 110 | Offset float64 `json:"offset"` 111 | } 112 | 113 | // PrinterState current state of the printer. 114 | type PrinterState struct { 115 | Text string `json:"text"` 116 | Flags struct { 117 | Operations bool `json:"operational"` 118 | Paused bool `json:"paused"` 119 | Printing bool `json:"printing"` 120 | SDReady bool `json:"sdReady"` 121 | Error bool `json:"error"` 122 | Ready bool `json:"ready"` 123 | ClosedOnError bool `json:"closedOrError"` 124 | } `json:"flags"` 125 | } 126 | 127 | // SDState is the state of the sd reader. 128 | type SDState struct { 129 | Ready bool `json:"ready"` 130 | } 131 | 132 | // HistoricTemperatureData is temperature historic stats for a tool. 133 | type HistoricTemperatureData historicTemperatureData 134 | type historicTemperatureData struct { 135 | // Time of this data point. 136 | Time JSONTime `json:"time"` 137 | // Tools is temperature stats a set of tools. 138 | Tools map[string]TemperatureData `json:"tools"` 139 | } 140 | 141 | func (h *HistoricTemperatureData) UnmarshalJSON(b []byte) error { 142 | var raw map[string]interface{} 143 | if err := json.Unmarshal(b, &raw); err != nil { 144 | return err 145 | } 146 | 147 | ts := raw["time"] 148 | delete(raw, "time") 149 | b, _ = json.Marshal(map[string]interface{}{ 150 | "time": ts, 151 | "tools": raw, 152 | }) 153 | 154 | i := &historicTemperatureData{} 155 | if err := json.Unmarshal(b, i); err != nil { 156 | return err 157 | } 158 | 159 | *h = HistoricTemperatureData(*i) 160 | return nil 161 | } 162 | 163 | // VersionResponse is the response from a job command. 164 | type VersionResponse struct { 165 | // API is the API version. 166 | API string `json:"api"` 167 | // Server is the server version. 168 | Server string `json:"server"` 169 | } 170 | 171 | type ConnectionState string 172 | 173 | const ( 174 | Operational ConnectionState = "Operational" 175 | ) 176 | 177 | // The states are based on: 178 | // https://github.com/foosel/OctoPrint/blob/77753ca02602d3a798d6b0a22535e6fd69ff448a/src/octoprint/util/comm.py#L549 179 | 180 | func (s ConnectionState) IsOperational() bool { 181 | return strings.HasPrefix(string(s), "Operational") 182 | } 183 | 184 | func (s ConnectionState) IsPrinting() bool { 185 | return strings.HasPrefix(string(s), "Printing") || 186 | strings.HasPrefix(string(s), "Sending") || 187 | strings.HasPrefix(string(s), "Paused") || 188 | strings.HasPrefix(string(s), "Transfering") || 189 | strings.HasPrefix(string(s), "Paused") 190 | } 191 | 192 | func (s ConnectionState) IsOffline() bool { 193 | return strings.HasPrefix(string(s), "Offline") || 194 | strings.HasPrefix(string(s), "Closed") 195 | } 196 | 197 | func (s ConnectionState) IsError() bool { 198 | return strings.HasPrefix(string(s), "Error") || 199 | strings.HasPrefix(string(s), "Unknown") 200 | } 201 | 202 | func (s ConnectionState) IsConnecting() bool { 203 | return strings.HasPrefix(string(s), "Opening") || 204 | strings.HasPrefix(string(s), "Detecting") || 205 | strings.HasPrefix(string(s), "Connecting") || 206 | strings.HasPrefix(string(s), "Detecting") 207 | } 208 | 209 | // ConnectionResponse is the response from a connection command. 210 | type ConnectionResponse struct { 211 | Current struct { 212 | // State current state of the connection. 213 | State ConnectionState `json:"state"` 214 | // Port to connect to. 215 | Port string `json:"port"` 216 | // BaudRate speed of the connection. 217 | BaudRate int `json:"baudrate"` 218 | // PrinterProfile profile to use for connection. 219 | PrinterProfile string `json:"printerProfile"` 220 | } 221 | Options struct { 222 | // Ports list of available ports. 223 | Ports []string `json:"ports"` 224 | // BaudRates list of available speeds. 225 | BaudRates []int `json:"baudrates"` 226 | // PrinterProfile list of available profiles. 227 | PrinterProfiles []*Profile `json:"printerProfiles"` 228 | // PortPreference default port. 229 | PortPreference string `json:"portPreference"` 230 | // BaudRatePreference default speed. 231 | BaudRatePreference int `json:"baudratePreference"` 232 | // PrinterProfilePreference default profile. 233 | PrinterProfilePreference string `json:"printerProfilePreference"` 234 | // Autoconnect whether to automatically connect to the printer on 235 | // OctoPrint’s startup in the future. 236 | Autoconnect bool `json:"autoconnect"` 237 | } 238 | } 239 | 240 | // Profile describe a printer profile. 241 | type Profile struct { 242 | // ID is the identifier of the profile. 243 | ID string `json:"id"` 244 | // Name is the display name of the profile. 245 | Name string `json:"name"` 246 | } 247 | 248 | // FilesResponse is the response to a FilesRequest. 249 | type FilesResponse struct { 250 | // Files is the list of requested files. Might be an empty list if no files 251 | // are available 252 | Files []*FileInformation 253 | // Free is the amount of disk space in bytes available in the local disk 254 | // space (refers to OctoPrint’s `uploads` folder). Only returned if file 255 | // list was requested for origin `local` or all origins. 256 | Free uint64 257 | } 258 | 259 | // FileInformation contains information regarding a file. 260 | type FileInformation struct { 261 | // Name is name of the file without path. E.g. “file.gco” for a file 262 | // “file.gco” located anywhere in the file system. 263 | Name string `json:"name"` 264 | // Path is the path to the file within the location. E.g. 265 | //“folder/subfolder/file.gco” for a file “file.gco” located within “folder” 266 | // and “subfolder” relative to the root of the location. 267 | Path string `json:"path"` 268 | // Type of file. model or machinecode. Or folder if it’s a folder, in 269 | // which case the children node will be populated. 270 | Type string `json:"type"` 271 | // TypePath path to type of file in extension tree. E.g. `["model", "stl"]` 272 | // for .stl files, or `["machinecode", "gcode"]` for .gcode files. 273 | // `["folder"]` for folders. 274 | TypePath []string `json:"typePath"` 275 | // Hash is the MD5 hash of the file. Only available for `local` files. 276 | Hash string `json:"hash"` 277 | // Size of the file in bytes. Only available for `local` files or `sdcard` 278 | // files if the printer supports file sizes for sd card files. 279 | Size uint64 `json:"size"` 280 | // Date when this file was uploaded. Only available for `local` files. 281 | Date JSONTime `json:"date"` 282 | // Origin of the file, `local` when stored in OctoPrint’s `uploads` folder, 283 | // `sdcard` when stored on the printer’s SD card (if available) 284 | Origin string `json:"origin"` 285 | // Refs references relevant to this file, left out in abridged versio 286 | Refs Reference `json:"refs"` 287 | // GCodeAnalysis information from the analysis of the GCODE file, if 288 | // available. Left out in abridged version. 289 | GCodeAnalysis GCodeAnalysisInformation `json:"gcodeAnalysis"` 290 | // Print information from the print stats of a file. 291 | Print PrintStats `json:"print"` 292 | } 293 | 294 | // IsFolder it returns true if the file is a folder. 295 | func (f *FileInformation) IsFolder() bool { 296 | if len(f.TypePath) == 1 && f.TypePath[0] == "folder" { 297 | return true 298 | } 299 | 300 | return false 301 | } 302 | 303 | // Reference of a file. 304 | type Reference struct { 305 | // Resource that represents the file or folder (e.g. for issuing commands 306 | // to or for deleting) 307 | Resource string `json:"resource"` 308 | // Download URL for the file. Never present for folders. 309 | Download string `json:"download"` 310 | // Model from which this file was generated (e.g. an STL, currently not 311 | // used). Never present for folders. 312 | Model string `json:"model"` 313 | } 314 | 315 | // GCodeAnalysisInformation Information from the analysis of the GCODE file. 316 | type GCodeAnalysisInformation struct { 317 | // EstimatedPrintTime is the estimated print time of the file, in seconds. 318 | EstimatedPrintTime float64 `json:"estimatedPrintTime"` 319 | // Filament estimated usage of filament 320 | Filament struct { 321 | // Length estimated of filament used, in mm 322 | Length uint32 `json:"length"` 323 | // Volume estimated of filament used, in cm³ 324 | Volume float64 `json:"volume"` 325 | } `json:"filament"` 326 | } 327 | 328 | // PrintStats information from the print stats of a file. 329 | type PrintStats struct { 330 | // Failure number of failed prints. 331 | Failure int `json:"failure"` 332 | // Success number of success prints. 333 | Success int `json:"success"` 334 | // Last print information. 335 | Last struct { 336 | // Date of the last print. 337 | Date JSONTime `json:"date"` 338 | // Success or not. 339 | Success bool `json:"success"` 340 | } `json:"last"` 341 | } 342 | 343 | // UploadFileResponse is the response to a UploadFileRequest. 344 | type UploadFileResponse struct { 345 | // Abridged information regarding the file that was just uploaded. If only 346 | // uploaded to local this will only contain the local property. If uploaded 347 | // to SD card, this will contain both local and sdcard properties. Only 348 | // contained if a file was uploaded, not present if only a new folder was 349 | // created. 350 | File struct { 351 | // Local is the information regarding the file that was just uploaded 352 | // to the local storage. 353 | Local *FileInformation `json:"local"` 354 | // SDCard is the information regarding the file that was just uploaded 355 | // to the printer’s SD card. 356 | SDCard *FileInformation `json:"sdcard"` 357 | } `json:"files"` 358 | // Done whether any file processing after upload has already finished or 359 | // not, e.g. due to first needing to perform a slicing step. Clients may 360 | // use this information to direct progress displays related to the upload. 361 | Done bool `json:"done"` 362 | } 363 | 364 | // SystemCommandsResponse is the response to a SystemCommandsRequest. 365 | type SystemCommandsResponse struct { 366 | Core []*CommandDefinition `json:"core"` 367 | Custom []*CommandDefinition `json:"custom"` 368 | } 369 | 370 | // CommandSource is the source of the command definition. 371 | type CommandSource string 372 | 373 | const ( 374 | // Core for system actions defined by OctoPrint itself. 375 | Core CommandSource = "core" 376 | // Custom for custom system commands defined by the user through `config.yaml`. 377 | Custom CommandSource = "custom" 378 | ) 379 | 380 | // CommandDefinition describe a system command. 381 | type CommandDefinition struct { 382 | // Name of the command to display in the System menu. 383 | Name string `json:"name"` 384 | // Command is the full command line to execute for the command. 385 | Command string `json:"command"` 386 | // Action is an identifier to refer to the command programmatically. The 387 | // special `action` string divider signifies a `divider` in the menu. 388 | Action string `json:"action"` 389 | // Confirm if present and set, this text will be displayed to the user in a 390 | // confirmation dialog they have to acknowledge in order to really execute 391 | // the command. 392 | RawConfirm json.RawMessage `json:"confirm"` 393 | Confirm string `json:"-"` 394 | // Async whether to execute the command asynchronously or wait for its 395 | // result before responding to the HTTP execution request. 396 | Async bool `json:"async"` 397 | // Ignore whether to ignore the return code of the command’s execution. 398 | Ignore bool `json:"ignore"` 399 | // Source of the command definition. 400 | Source CommandSource `json:"source"` 401 | // Resource is the URL of the command to use for executing it. 402 | Resource string `json:"resource"` 403 | } 404 | 405 | type JSONTime struct{ time.Time } 406 | 407 | func (t JSONTime) MarshalJSON() ([]byte, error) { 408 | return []byte(strconv.FormatInt(time.Time(t.Time).Unix(), 10)), nil 409 | } 410 | 411 | func (t *JSONTime) UnmarshalJSON(s []byte) (err error) { 412 | r := strings.Replace(string(s), `"`, ``, -1) 413 | if r == "null" { 414 | return nil 415 | } 416 | 417 | q, err := strconv.ParseInt(r, 10, 64) 418 | if err != nil { 419 | return err 420 | } 421 | 422 | t.Time = time.Unix(q, 0) 423 | return 424 | } 425 | 426 | // CustomCommandsResponse is the response to a CustomCommandsRequest. 427 | type CustomCommandsResponse struct { 428 | Controls []*ControlContainer `json:"controls"` 429 | } 430 | 431 | // ControlContainer describes a control container. 432 | type ControlContainer struct { 433 | // Name to display above the container, basically a section header. 434 | Name string `json:"name"` 435 | // Children a list of children controls or containers contained within this 436 | // container. 437 | Children []*ControlDefinition `json:"children"` 438 | // Layout to use for laying out the contained children, either from top to 439 | // bottom (`vertical`) or from left to right (`horizontal``). Defaults to a 440 | // vertical layout. 441 | Layout string `json:"layout"` 442 | } 443 | 444 | // ControlDefinition describe a system control. 445 | type ControlDefinition struct { 446 | // Name of the control, will be displayed either on the button if it’s a 447 | // control sending a command or as a label for controls which only display 448 | // output. 449 | Name string `json:"name"` 450 | // Command a single GCODE command to send to the printer. Will be rendered 451 | // as a button which sends the command to the printer upon click. The button 452 | // text will be the value of the `name` attribute. Mutually exclusive with 453 | // `commands` and `script`. The rendered button be disabled if the printer 454 | // is currently offline or printing or alternatively if the requirements 455 | // defined via the `enabled` attribute are not met. 456 | Command string `json:"command"` 457 | // Command a list of GCODE commands to send to the printer. Will be rendered 458 | // as a button which sends the commands to the printer upon click. The 459 | // button text will be the value of the `name` attribute. Mutually exclusive 460 | // with `command` and `script`. The rendered button will be disabled if the 461 | // printer is currently offline or printing or alternatively if the 462 | // requirements defined via the `enabled` attribute are not met. 463 | Commands []string `json:"commands"` 464 | // Script is the name of a full blown GCODE script to send to the printer. 465 | // Will be rendered as a button which sends the script to the printer upon 466 | // click. The button text will be the value of the name attribute. Mutually 467 | // exclusive with `command` and `commands`. The rendered button will be 468 | // disabled if the printer is currently offline or printing or alternatively 469 | // if the requirements defined via the `enabled`` attribute are not met. 470 | // 471 | // Values of input parameters will be available in the template context 472 | // under the `parameter` variable (e.g. an input parameter speed will be 473 | // available in the script template as parameter.speed). On top of that all 474 | // other variables defined in the GCODE template context will be available. 475 | Script string `json:"script"` 476 | // JavaScript snippet to be executed when the button rendered for `command` 477 | // or `commands` is clicked. This allows to override the direct sending of 478 | // the command or commands to the printer with more sophisticated behaviour. 479 | // The JavaScript snippet is `eval`’d and processed in a context where the 480 | // control it is part of is provided as local variable `data` and the 481 | // `ControlViewModel` is available as self. 482 | JavasScript string `json:"javascript"` 483 | // Enabled a JavaScript snippet to be executed when the button rendered for 484 | // `command` or `commands` is clicked. This allows to override the direct 485 | // sending of the command or commands to the printer with more sophisticated 486 | // behaviour. The JavaScript snippet is `eval`’d and processed in a context 487 | // where the control it is part of is provided as local variable `data` and 488 | // the `ControlViewModel` is available as `self`. 489 | Enabled bool `json:"enabled"` 490 | // Input a list of definitions of input parameters for a command or 491 | // commands, to be rendered as additional input fields. 492 | Input *ControlInput `json:"input"` 493 | // Regex a regular expression to match against lines received from the 494 | // printer to retrieve information from it (e.g. specific output). Together 495 | // with template this allows rendition of received data from the printer 496 | // within the UI. 497 | Regex string `json:"regex"` 498 | // Template to use for rendering the match of `regex`. May contain 499 | // placeholders in Python Format String Syntax[1] for either named groups 500 | // within the regex (e.g. `Temperature: {temperature}` for a regex 501 | // `T:\s*(?P\d+(\.\d*)`) or positional groups within the regex 502 | // (e.g. `Position: X={0}, Y={1}, Z={2}, E={3}` for a regex 503 | // `X:([0-9.]+) Y:([0-9.]+) Z:([0-9.]+) E:([0-9.]+)`). 504 | // https://docs.python.org/2/library/string.html#format-string-syntax 505 | Template string `json:"template"` 506 | // Confirm a text to display to the user to confirm his button press. Can 507 | // be used with sensitive custom controls like changing EEPROM values in 508 | // order to prevent accidental clicks. 509 | Confirm string `json:"confirm"` 510 | } 511 | 512 | // ControlInput a list of definitions of input parameters for a command or 513 | // commands, to be rendered as additional input fields. 514 | type ControlInput struct { 515 | // Name to display for the input field. 516 | Name string `json:"name"` 517 | // Parameter name for the input field, used as a placeholder in 518 | // command/commands. 519 | Parameter string `json:"parameter"` 520 | // Default value for the input field. 521 | Default interface{} `json:"default"` 522 | // Slider if defined instead of an input field a slider control will be 523 | // rendered. 524 | Slider struct { 525 | // Minimum value of the slider, defaults to 0. 526 | Min int `json:"min"` 527 | // Maximum value of the slider, defaults to 255. 528 | Maximum int `json:"max"` 529 | // Step size per slider “tick”, defaults to 1. 530 | Step int `json:"step"` 531 | } `json:"slider"` 532 | } 533 | 534 | // Settings are the current configuration of OctoPrint. 535 | type Settings struct { 536 | // API REST API settings. 537 | API *APIConfig `json:"api"` 538 | // Features settings to enable or disable OctoPrint features. 539 | Feature *FeaturesConfig `json:"feature"` 540 | //Folder settings to set custom paths for folders used by OctoPrint. 541 | Folder *FolderConfig `json:"folder"` 542 | // Serial settings to configure the serial connection to the printer. 543 | Serial *SerialConfig `json:"serial"` 544 | // Server settings to configure the server. 545 | Server *ServerConfig `json:"server"` 546 | // Temperature profiles which will be displayed in the temperature tab. 547 | Temperature *TemperatureConfig `json:"temperature"` 548 | // TerminalFilters to display in the terminal tab for filtering certain 549 | // lines from the display terminal log. 550 | TerminalFilters []*TerminalFilter `json:"terminalFilters"` 551 | // Webcam settings to configure webcam support. 552 | Webcam *WebcamConfig `json:"webcam"` 553 | 554 | // Un-handled values 555 | Appearance interface{} `json:"appearance"` 556 | Plugins interface{} `json:"plugins"` 557 | Printer interface{} `json:"printer"` 558 | } 559 | 560 | // APIConfig REST API settings. 561 | type APIConfig struct { 562 | // Enabled whether to enable the API. 563 | Enabled bool `json:"enabled"` 564 | // Key current API key needed for accessing the API 565 | Key string `json:"key"` 566 | } 567 | 568 | // FeaturesConfig settings to enable or disable OctoPrint features. 569 | type FeaturesConfig struct { 570 | // SizeThreshold maximum size a GCODE file may have to automatically be 571 | // loaded into the viewer, defaults to 20MB. Maps to 572 | // gcodeViewer.sizeThreshold in config.yaml. 573 | SizeThreshold uint64 574 | // MobileSizeThreshold maximum size a GCODE file may have on mobile devices 575 | // to automatically be loaded into the viewer, defaults to 2MB. Maps to 576 | // gcodeViewer.mobileSizeThreshold in config.yaml. 577 | MobileSizeThreshold uint64 `json:"mobileSizeThreshold"` 578 | // TemperatureGraph whether to enable the temperature graph in the UI or not. 579 | TemperatureGraph bool `json:"temperatureGraph"` 580 | // WaitForStart specifies whether OctoPrint should wait for the start 581 | // response from the printer before trying to send commands during connect. 582 | WaitForStart bool `json:"waitForStart"` 583 | // AlwaysSendChecksum specifies whether OctoPrint should send linenumber + 584 | // checksum with every printer command. Needed for successful communication 585 | // with Repetier firmware. 586 | AlwaysSendChecksum bool `json:"alwaysSendChecksum"` 587 | NeverSendChecksum bool `json:"neverSendChecksum"` 588 | // SDSupport specifies whether support for SD printing and file management 589 | // should be enabled 590 | SDSupport bool `json:"sdSupport"` 591 | // SDAlwaysAvailable whether to always assume that an SD card is present in 592 | // the printer. Needed by some firmwares which don't report the SD card 593 | // status properly. 594 | SDAlwaysAvailable bool `json:"sdAlwaysAvailable"` 595 | // SDReleativePath Specifies whether firmware expects relative paths for 596 | // selecting SD files. 597 | SDRelativePath bool `json:"sdRelativePath"` 598 | // SwallowOkAfterResend whether to ignore the first ok after a resend 599 | // response. Needed for successful communication with Repetier firmware. 600 | SwallowOkAfterResend bool `json:"swallowOkAfterResend"` 601 | // RepetierTargetTemp whether the printer sends repetier style target 602 | // temperatures in the format `TargetExtr0:` instead of 603 | // attaching that information to the regular M105 responses. 604 | RepetierTargetTemp bool `json:"repetierTargetTemp"` 605 | // ExternalHeatupDetection whether to enable external heatup detection (to 606 | // detect heatup triggered e.g. through the printer's LCD panel or while 607 | // printing from SD) or not. Causes issues with Repetier's "first ok then 608 | // response" approach to communication, so disable for printers running 609 | // Repetier firmware. 610 | ExternalHeatupDetection bool `json:"externalHeatupDetection"` 611 | // KeyboardControl whether to enable the keyboard control feature in the 612 | // control tab. 613 | KeyboardControl bool `json:"keyboardControl"` 614 | // PollWatched whether to actively poll the watched folder (true) or to rely 615 | // on the OS's file system notifications instead (false). 616 | PollWatched bool `json:"pollWatched"` 617 | // IgnoreIdenticalResends whether to ignore identical resends from the 618 | // printer (true, repetier) or not (false). 619 | IgnoreIdenticalResends bool `json:"ignoreIdenticalResends"` 620 | // ModelSizeDetection whether to enable model size detection and warning 621 | // (true) or not (false) 622 | ModelSizeDetection bool `json:"modelSizeDetection"` 623 | // FirmwareDetection whether to attempt to auto detect the firmware of the 624 | // printer and adjust settings accordingly (true) or not and rely on manual 625 | // configuration (false) 626 | FirmwareDetection bool `json:"firmwareDetection"` 627 | // PrintCancelConfirmation whether to show a confirmation on print 628 | // cancelling (true) or not (false). 629 | PrintCancelConfirmation bool `json:"printCancelConfirmation"` 630 | // BlockWhileDwelling whether to block all sending to the printer while a G4 631 | // (dwell) command is active (true, repetier) or not (false). 632 | BlockWhileDwelling bool `json:"blockWhileDwelling"` 633 | } 634 | 635 | // FolderConfig settings to set custom paths for folders used by OctoPrint. 636 | type FolderConfig struct { 637 | // Uploads absolute path where to store gcode uploads. Defaults to the 638 | // uploads folder in the OctoPrint settings folder. 639 | Uploads string `json:"uploads"` 640 | // Timelapse absolute path where to store finished timelapse recordings. 641 | // Defaults to the timelapse folder in the OctoPrint settings dir. 642 | Timelapse string `json:"timelapse"` 643 | // TimelapseTmp absolute path where to store temporary timelapse files. 644 | // Defaults to the timelapse/tmp folder in the OctoPrint settings dir Maps 645 | // to folder.timelapse_tmp in config.yaml. 646 | TimelapseTmp string `json:"timelapseTmp"` 647 | // Logs absolute path where to store log files. Defaults to the logs folder 648 | // in the OctoPrint settings dir 649 | Logs string `json:"logs"` 650 | // Watched absolute path to a folder being watched for new files which then 651 | // get automatically added to OctoPrint (and deleted from that folder). 652 | // Can e.g. be used to define a folder which can then be mounted from remote 653 | // machines and used as local folder for quickly adding downloaded and/or 654 | // sliced objects to print in the future. 655 | Watched string `json:"watched"` 656 | } 657 | 658 | // SerialConfig settings to configure the serial connection to the printer. 659 | type SerialConfig struct { 660 | // Port is the default serial port, defaults to unset (= AUTO) 661 | Port string `json:"port"` 662 | // Baudrate is the default baudrate, defaults to unset (= AUTO) 663 | Baudrate int `json:"baudrate"` 664 | // Available serial ports 665 | PortOptions []string `json:"portOptions"` 666 | // Available serial baudrates 667 | BaudrateOptions []int `json:"baudrateOptions"` 668 | // Autoconnect whether to automatically connect to the printer on server 669 | // startup (if available). 670 | Autoconnect bool `json:"autoconnect"` 671 | // TimeoutConnection for waiting to establish a connection with the selected 672 | // port, in seconds. Defaults to 2 sec. Maps to serial.timeout.connection in 673 | // config.yaml 674 | TimeoutConnection float64 `json:"timeoutConnection"` 675 | // TimeoutDetection for waiting for a response from the currently tested 676 | // port during autodetect, in seconds. Defaults to 0.5 sec Maps to 677 | // serial.timeout.detection in config.yaml 678 | TimeoutDetection float64 `json:"timeoutDetection"` 679 | // TimeoutCommunication during serial communication, in seconds. Defaults to 680 | // 30 sec. Maps to serial.timeout.communication in config.yaml 681 | TimeoutCommunication float64 `json:"timeoutCommunication"` 682 | // TimeoutTemperature after which to query temperature when no target is 683 | // set. Maps to serial.timeout.temperature in config.yaml 684 | TimeoutTemperature float64 `json:"timeoutTemperature"` 685 | // TimeoutTemperatureTargetSet after which to query temperature when a 686 | // target is set. Maps to serial.timeout.temperatureTargetSet in config.yaml 687 | TimeoutTemperatureTargetSet float64 `json:"timeoutTemperatureTargetSet"` 688 | // TimeoutSDStatus after which to query the SD status while SD printing. 689 | // Maps to serial.timeout.sdStatus in config.yaml 690 | TimeoutSDStatus float64 `json:"timeoutSdStatus"` 691 | // Log whether to log whole communication to serial.log (warning: might 692 | // decrease performance) 693 | Log bool `json:"log"` 694 | // AdditionalPorts use this to define additional patterns to consider for 695 | // serial port listing. Must be a valid "glob" pattern (see 696 | // http://docs.python.org/2/library/glob.html). Defaults to not set. 697 | AdditionalPorts []string `json:"additionalPorts"` 698 | // AdditionalBaudrates use this to define additional baud rates to offer for 699 | // connecting to serial ports. Must be a valid integer. Defaults to not set 700 | AdditionalBaudrates []int `json:"additionalBaudrates"` 701 | // LongRunningCommands which are known to take a long time to be 702 | // acknowledged by the firmware. E.g. homing, dwelling, auto leveling etc. 703 | LongRunningCommands []string `json:"longRunningCommands"` 704 | // ChecksumRequiringCommands which need to always be send with a checksum. 705 | // Defaults to only M110 706 | ChecksumRequiringCommands []string `json:"checksumRequiringCommands"` 707 | // HelloCommand to send in order to initiate a handshake with the printer. 708 | // Defaults to "M110 N0" which simply resets the line numbers in the 709 | // firmware and which should be acknowledged with a simple "ok". 710 | HelloCommand string `json:"helloCommand"` 711 | // IgnoreErrorsFromFirmware whether to completely ignore errors from the 712 | // firmware or not 713 | IgnoreErrorsFromFirmware bool `json:"ignoreErrorsFromFirmware"` 714 | // DisconnectOnErrors whether to disconnect on errors or not. 715 | DisconnectOnErrors bool `json:"disconnectOnErrors"` 716 | // TriggerOkForM29 whether to "manually" trigger an ok for M29 (a lot of 717 | // versions of this command are buggy and the responds skips on the ok) 718 | TriggerOkForM29 bool `json:"triggerOkForM29"` 719 | // SupportResendsWIthoutOk whether to support resends without follow-up ok 720 | // or not. 721 | SupportResendsWIthoutOk string `json:"supportResendsWIthoutOk"` 722 | // Maps to serial.maxCommunicationTimeouts.idle in config.yaml 723 | MaxTimeoutsIdle float64 `json:"maxTimeoutsIdle"` 724 | // MaxTimeoutsPrinting maximum number of consecutive communication timeouts 725 | // after which the printer will be considered dead and OctoPrint disconnects 726 | // with an error. Maps to serial.maxCommunicationTimeouts.printing in 727 | // config.yaml 728 | MaxTimeoutsPrinting float64 `json:"maxTimeoutsPrinting"` 729 | // MaxTimeoutsPrinting maximum number of consecutive communication timeouts 730 | // after which the printer will be considered dead and OctoPrint disconnects 731 | // with an error. Maps to serial.maxCommunicationTimeouts.log in config.yaml 732 | MaxTimeoutsLong float64 `json:"maxTimeoutsLong"` 733 | } 734 | 735 | // ServerConfig settings to configure the server. 736 | type ServerConfig struct { 737 | // Commands to restart/shutdown octoprint or the system it's running on. 738 | Commands struct { 739 | // ServerRestartCommand to restart OctoPrint, defaults to being unset 740 | ServerRestartCommand string `json:"serverRestartCommand"` 741 | //SystemRestartCommand to restart the system OctoPrint is running on, 742 | // defaults to being unset 743 | SystemRestartCommand string `json:"systemRestartCommand"` 744 | // SystemShutdownCommand Command to shut down the system OctoPrint is 745 | // running on, defaults to being unset 746 | SystemShutdownCommand string `json:"systemShutdownCommand"` 747 | } `json:"commands"` 748 | // Diskspace settings of when to display what disk space warning 749 | Diskspace struct { 750 | // Warning threshold (bytes) after which to consider disk space becoming 751 | // sparse, defaults to 500MB. 752 | Warning uint64 `json:"warning"` 753 | // Critical threshold (bytes) after which to consider disk space becoming 754 | // critical, defaults to 200MB. 755 | Critical uint64 `json:"critical"` 756 | } `json:"diskspace"` 757 | // OnlineCheck configuration of the regular online connectivity check. 758 | OnlineCheck struct { 759 | // Enabled whether the online check is enabled, defaults to false due to 760 | // valid privacy concerns. 761 | Enabled bool `json:"enabled"` 762 | // Interval in which to check for online connectivity (in seconds) 763 | Interval int `json:"interval"` 764 | // Host DNS host against which to check (default: 8.8.8.8 aka Google's DNS) 765 | Host string `json:"host"` 766 | // DNS port against which to check (default: 53 - the default DNS port) 767 | Port int `json:"port"` 768 | } `json:"onlineCheck"` 769 | // PluginBlacklist configuration of the plugin blacklist 770 | PluginBlacklist struct { 771 | // Enabled whether use of the blacklist is enabled, defaults to false 772 | Enabled bool `json:"enabled"` 773 | /// URL from which to fetch the blacklist 774 | URL string `json:"url"` 775 | // TTL is time to live of the cached blacklist, in secs (default: 15mins) 776 | TTL int `json:"ttl"` 777 | } `json:"pluginBlacklist"` 778 | } 779 | 780 | // TemperatureConfig temperature profiles which will be displayed in the 781 | // temperature tab. 782 | type TemperatureConfig struct { 783 | // Graph cutoff in minutes. 784 | Cutoff int `json:"cutoff"` 785 | // Profiles which will be displayed in the temperature tab. 786 | Profiles []*TemperatureProfile `json:"profiles"` 787 | // SendAutomatically enable this to have temperature fine adjustments you 788 | // do via the + or - button be sent to the printer automatically. 789 | SendAutomatically bool `json:"sendAutomatically"` 790 | // SendAutomaticallyAfter OctoPrint will use this delay to limit the number 791 | // of sent temperature commands should you perform multiple fine adjustments 792 | // in a short time. 793 | SendAutomaticallyAfter float64 `json:"sendAutomaticallyAfter"` 794 | } 795 | 796 | // TerminalFilter to display in the terminal tab for filtering certain lines 797 | // from the display terminal log. 798 | type TerminalFilter struct { 799 | Name string `json:"name"` 800 | RegEx string `json:"regex"` 801 | } 802 | 803 | // WebcamConfig settings to configure webcam support. 804 | type WebcamConfig struct { 805 | // StreamUrl use this option to enable display of a webcam stream in the 806 | // UI, e.g. via MJPG-Streamer. Webcam support will be disabled if not 807 | // set. Maps to webcam.stream in config.yaml. 808 | StreamURL string `json:"streamUrl"` 809 | // SnapshotURL use this option to enable timelapse support via snapshot, 810 | // e.g. via MJPG-Streamer. Timelapse support will be disabled if not set. 811 | // Maps to webcam.snapshot in config.yaml. 812 | SnapshotURL string `json:"snapshotUrl"` 813 | // FFmpegPath path to ffmpeg binary to use for creating timelapse 814 | // recordings. Timelapse support will be disabled if not set. Maps to 815 | // webcam.ffmpeg in config.yaml. 816 | FFmpegPath string `json:"ffmpegPath"` 817 | // Bitrate to use for rendering the timelapse video. This gets directly 818 | // passed to ffmpeg. 819 | Bitrate string `json:"bitrate"` 820 | // FFmpegThreads number of how many threads to instruct ffmpeg to use for 821 | // encoding. Defaults to 1. Should be left at 1 for RPi1. 822 | FFmpegThreads int `json:"ffmpegThreads"` 823 | // Watermark whether to include a "created with OctoPrint" watermark in the 824 | // generated timelapse movies. 825 | Watermark bool `json:"watermark"` 826 | // FlipH whether to flip the webcam horizontally. 827 | FlipH bool `json:"flipH"` 828 | // FlipV whether to flip the webcam vertically. 829 | FlipV bool `json:"flipV"` 830 | // Rotate90 whether to rotate the webcam 90° counter clockwise. 831 | Rotate90 bool `json:"rotate90"` 832 | } 833 | 834 | // TemperatureProfile describes the temperature profile preset for a given 835 | // material. 836 | type TemperatureProfile struct { 837 | Name string `json:"name"` 838 | Bed float64 `json:"bed"` 839 | Extruder float64 `json:"extruder"` 840 | } 841 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestHistoricTemperatureData(t *testing.T) { 11 | js := []byte(`{ 12 | "time": 1395651928, 13 | "tool0": { 14 | "actual": 214.8821, 15 | "target": 220.0 16 | }, 17 | "tool1": { 18 | "actual": 25.3, 19 | "target": null 20 | } 21 | }`) 22 | 23 | h := &HistoricTemperatureData{} 24 | 25 | err := json.Unmarshal(js, h) 26 | assert.NoError(t, err) 27 | 28 | assert.Len(t, h.Tools, 2) 29 | assert.False(t, h.Time.IsZero()) 30 | assert.Equal(t, h.Tools["tool0"].Target, 220.) 31 | assert.Equal(t, h.Tools["tool1"].Actual, 25.3) 32 | } 33 | 34 | func TestTemperatureState(t *testing.T) { 35 | js := []byte(`{ 36 | "tool0": { 37 | "actual": 214.8821, 38 | "target": 220.0, 39 | "offset": 0 40 | }, 41 | "tool1": { 42 | "actual": 25.3, 43 | "target": null, 44 | "offset": 0 45 | }, 46 | "history": [ 47 | { 48 | "time": 1395651928, 49 | "tool0": { 50 | "actual": 214.8821, 51 | "target": 220.0 52 | }, 53 | "tool1": { 54 | "actual": 25.3, 55 | "target": null 56 | } 57 | }, 58 | { 59 | "time": 1395651926, 60 | "tool0": { 61 | "actual": 212.32, 62 | "target": 220.0 63 | }, 64 | "tool1": { 65 | "actual": 25.1 66 | } 67 | } 68 | ] 69 | }`) 70 | 71 | r := &TemperatureState{} 72 | 73 | err := json.Unmarshal(js, r) 74 | assert.NoError(t, err) 75 | 76 | assert.Len(t, r.Current, 2) 77 | assert.Equal(t, r.Current["tool0"].Actual, 214.8821) 78 | assert.Equal(t, r.Current["tool1"].Actual, 25.3) 79 | 80 | assert.Len(t, r.History, 2) 81 | assert.Equal(t, r.History[0].Tools["tool0"].Actual, 214.8821) 82 | assert.Equal(t, r.History[1].Tools["tool0"].Actual, 212.32) 83 | } 84 | 85 | func TestFullStateResponse(t *testing.T) { 86 | js := []byte(` 87 | { 88 | "temperature":{ 89 | "tool0":{ 90 | "actual":214.8821, 91 | "target":220.0, 92 | "offset":0 93 | }, 94 | "tool1":{ 95 | "actual":25.3, 96 | "target":null, 97 | "offset":0 98 | }, 99 | "bed":{ 100 | "actual":50.221, 101 | "target":70.0, 102 | "offset":5 103 | }, 104 | "history":[{ 105 | "time":1395651928, 106 | "tool0":{ 107 | "actual":214.8821, 108 | "target":220.0 109 | }, 110 | "tool1":{ 111 | "actual":25.3, 112 | "target":null 113 | }, 114 | "bed":{ 115 | "actual":50.221, 116 | "target":70.0 117 | } 118 | },{ 119 | "time":1395651926, 120 | "tool0":{ 121 | "actual":212.32, 122 | "target":220.0 123 | }, 124 | "tool1":{ 125 | "actual":25.1, 126 | "target":null 127 | }, 128 | "bed":{ 129 | "actual":49.1123, 130 | "target":70.0 131 | } 132 | }] 133 | }, 134 | "sd":{ 135 | "ready":true 136 | }, 137 | "state":{ 138 | "text":"Operational", 139 | "flags":{ 140 | "operational":true, 141 | "paused":false, 142 | "printing":false, 143 | "sdReady":true, 144 | "error":false, 145 | "ready":true, 146 | "closedOrError":false 147 | } 148 | } 149 | } 150 | `) 151 | 152 | r := &FullStateResponse{} 153 | 154 | err := json.Unmarshal(js, r) 155 | assert.NoError(t, err) 156 | 157 | assert.Equal(t, r.State.Text, "Operational") 158 | assert.True(t, r.State.Flags.Ready) 159 | assert.True(t, r.SD.Ready) 160 | assert.Len(t, r.Temperature.Current, 3) 161 | assert.Len(t, r.Temperature.History, 2) 162 | } 163 | 164 | func TestFileInformation_IsFolder(t *testing.T) { 165 | f := &FileInformation{TypePath: []string{"folder"}} 166 | assert.True(t, f.IsFolder()) 167 | 168 | f = &FileInformation{} 169 | assert.False(t, f.IsFolder()) 170 | } 171 | 172 | func TestJSONTime_UnmarshalJSONWithNull(t *testing.T) { 173 | time := &JSONTime{} 174 | err := time.UnmarshalJSON([]byte("null")) 175 | assert.NoError(t, err) 176 | } 177 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | ) 8 | 9 | const URIConnection = "/api/connection" 10 | 11 | var ConnectionErrors = statusMapping{ 12 | 400: "The selected port or baudrate for a connect command are not part of the available option", 13 | } 14 | 15 | // ConnectionRequest Retrieve the current connection settings, including 16 | // information regarding the available baudrates and serial ports and the 17 | // current connection state. 18 | type ConnectionRequest struct{} 19 | 20 | // Do sends an API request and returns the API response. 21 | func (cmd *ConnectionRequest) Do(c *Client) (*ConnectionResponse, error) { 22 | b, err := c.doJSONRequest("GET", URIConnection, nil, nil) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | r := &ConnectionResponse{} 28 | if err := json.Unmarshal(b, r); err != nil { 29 | return nil, err 30 | } 31 | 32 | return r, err 33 | } 34 | 35 | // ConnectRequest sets the given target temperature on the printer’s tools. 36 | type ConnectRequest struct { 37 | // Port specific port to connect to. If not set the current `portPreference` 38 | // will be used, or if no preference is available auto detection will be 39 | // attempted. 40 | Port string `json:"port,omitempty"` 41 | // BaudRate specific baudrate to connect with. If not set the current 42 | // `baudratePreference` will be used, or if no preference is available auto 43 | // detection will be attempted. 44 | BaudRate int `json:"baudrate,omitempty"` 45 | // PrinterProfile specific printer profile to use for connection. If not set 46 | // the current default printer profile will be used. 47 | PrinterProfile string `json:"printerProfile,omitempty"` 48 | // Save whether to save the request’s port and baudrate settings as new 49 | // preferences. 50 | Save bool `json:"save"` 51 | // Autoconnect whether to automatically connect to the printer on 52 | // OctoPrint’s startup in the future. 53 | Autoconnect bool `json:"autoconnect"` 54 | } 55 | 56 | // Do sends an API request and returns an error if any. 57 | func (cmd *ConnectRequest) Do(c *Client) error { 58 | b := bytes.NewBuffer(nil) 59 | if err := cmd.encode(b); err != nil { 60 | return err 61 | } 62 | 63 | _, err := c.doJSONRequest("POST", URIConnection, b, ConnectionErrors) 64 | return err 65 | } 66 | 67 | func (cmd *ConnectRequest) encode(w io.Writer) error { 68 | return json.NewEncoder(w).Encode(struct { 69 | Command string `json:"command"` 70 | ConnectRequest 71 | }{ 72 | Command: "connect", 73 | ConnectRequest: *cmd, 74 | }) 75 | } 76 | 77 | // DisconnectRequest instructs OctoPrint to disconnect from the printer. 78 | type DisconnectRequest struct{} 79 | 80 | // Do sends an API request and returns an error if any. 81 | func (cmd *DisconnectRequest) Do(c *Client) error { 82 | payload := map[string]string{"command": "disconnect"} 83 | 84 | b := bytes.NewBuffer(nil) 85 | if err := json.NewEncoder(b).Encode(payload); err != nil { 86 | return err 87 | } 88 | 89 | _, err := c.doJSONRequest("POST", URIConnection, b, ConnectionErrors) 90 | return err 91 | } 92 | 93 | // FakesACKRequest fakes an acknowledgment message for OctoPrint in case one got 94 | // lost on the serial line and the communication with the printer since stalled. 95 | // 96 | // This should only be used in “emergencies” (e.g. to save prints), the reason 97 | // for the lost acknowledgment should always be properly investigated and 98 | // removed instead of depending on this “symptom solver”. 99 | type FakesACKRequest struct{} 100 | 101 | // Do sends an API request and returns an error if any. 102 | func (cmd *FakesACKRequest) Do(c *Client) error { 103 | payload := map[string]string{"command": "fake_ack"} 104 | 105 | b := bytes.NewBuffer(nil) 106 | if err := json.NewEncoder(b).Encode(payload); err != nil { 107 | return err 108 | } 109 | 110 | _, err := c.doJSONRequest("POST", URIConnection, b, ConnectionErrors) 111 | return err 112 | } 113 | -------------------------------------------------------------------------------- /files.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "mime/multipart" 9 | ) 10 | 11 | type Location string 12 | 13 | const ( 14 | URIFiles = "/api/files" 15 | 16 | Local Location = "local" 17 | SDCard Location = "sdcard" 18 | ) 19 | 20 | var ( 21 | FilesLocationGETErrors = statusMapping{ 22 | 404: "Location is neither local nor sdcard", 23 | } 24 | FilesLocationPOSTErrors = statusMapping{ 25 | 400: "No file or foldername are included in the request, userdata was provided but could not be parsed as JSON or the request is otherwise invalid.", 26 | 404: "Location is neither local nor sdcard or trying to upload to SD card and SD card support is disabled", 27 | 409: "The upload of the file would override the file that is currently being printed or if an upload to SD card was requested and the printer is either not operational or currently busy with a print job.", 28 | 415: "The file is neither a gcode nor an stl file (or it is an stl file but slicing support is disabled)", 29 | 500: "The upload failed internally", 30 | } 31 | FilesLocationPathPOSTErrors = statusMapping{ 32 | 400: "The command is unknown or the request is otherwise invalid", 33 | 415: "A slice command was issued against something other than an STL file.", 34 | 404: "Location is neither local nor sdcard or the requested file was not found", 35 | 409: "Selected file is supposed to start printing directly but the printer is not operational or if a file to be sliced is supposed to be selected or start printing directly but the printer is not operational or already printing.", 36 | } 37 | FilesLocationDeleteErrors = statusMapping{ 38 | 404: "Location is neither local nor sdcard", 39 | 409: "The file to be deleted is currently being printed", 40 | } 41 | ) 42 | 43 | // FileRequest retrieves the selected file’s or folder’s information. 44 | type FileRequest struct { 45 | // Location of the file for which to retrieve the information, either 46 | // `local` or `sdcard`. 47 | Location Location 48 | // Filename of the file for which to retrieve the information 49 | Filename string 50 | // Recursive if set to true, return all files and folders recursively. 51 | // Otherwise only return items on same level. 52 | Recursive bool 53 | } 54 | 55 | // Do sends an API request and returns the API response 56 | func (cmd *FileRequest) Do(c *Client) (*FileInformation, error) { 57 | uri := fmt.Sprintf("%s/%s/%s?recursive=%t", URIFiles, 58 | cmd.Location, cmd.Filename, cmd.Recursive, 59 | ) 60 | 61 | b, err := c.doJSONRequest("GET", uri, nil, FilesLocationGETErrors) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | r := &FileInformation{} 67 | if err := json.Unmarshal(b, r); err != nil { 68 | return nil, err 69 | } 70 | 71 | return r, err 72 | } 73 | 74 | // FilesRequest retrieve information regarding all files currently available and 75 | // regarding the disk space still available locally in the system. 76 | type FilesRequest struct { 77 | // Location is the target location . 78 | Location Location 79 | // Recursive if set to true, return all files and folders recursively. 80 | // Otherwise only return items on same level. 81 | Recursive bool 82 | } 83 | 84 | // Do sends an API request and returns the API response. 85 | func (cmd *FilesRequest) Do(c *Client) (*FilesResponse, error) { 86 | uri := fmt.Sprintf("%s?recursive=%t", URIFiles, cmd.Recursive) 87 | if cmd.Location != "" { 88 | uri = fmt.Sprintf("%s/%s?recursive=%t", URIFiles, cmd.Location, cmd.Recursive) 89 | } 90 | 91 | b, err := c.doJSONRequest("GET", uri, nil, FilesLocationGETErrors) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | r := &FilesResponse{} 97 | if err := json.Unmarshal(b, r); err != nil { 98 | return nil, err 99 | } 100 | 101 | return r, err 102 | } 103 | 104 | // UploadFileRequest uploads a file to the selected location or create a new 105 | // empty folder on it. 106 | type UploadFileRequest struct { 107 | // Location is the target location to which to upload the file. Currently 108 | // only `local` and `sdcard` are supported here, with local referring to 109 | // OctoPrint’s `uploads` folder and `sdcard` referring to the printer’s 110 | // SD card. If an upload targets the SD card, it will also be stored 111 | // locally first. 112 | Location Location 113 | // Select whether to select the file directly after upload (true) or not 114 | // (false). Optional, defaults to false. Ignored when creating a folder. 115 | Select bool 116 | //Print whether to start printing the file directly after upload (true) or 117 | // not (false). If set, select is implicitely true as well. Optional, 118 | // defaults to false. Ignored when creating a folder. 119 | Print bool 120 | b *bytes.Buffer 121 | w *multipart.Writer 122 | } 123 | 124 | // AddFile adds a new file to be uploaded from a given reader. 125 | func (req *UploadFileRequest) AddFile(filename string, r io.Reader) error { 126 | w, err := req.writer().CreateFormFile("file", filename) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | _, err = io.Copy(w, r) 132 | return err 133 | 134 | } 135 | 136 | func (req *UploadFileRequest) writer() *multipart.Writer { 137 | if req.w == nil { 138 | req.b = bytes.NewBuffer(nil) 139 | req.w = multipart.NewWriter(req.b) 140 | } 141 | 142 | return req.w 143 | } 144 | 145 | // AddFolder adds a new folder to be created. 146 | func (req *UploadFileRequest) AddFolder(folder string) error { 147 | return req.writer().WriteField("foldername", folder) 148 | } 149 | 150 | // Do sends an API request and returns the API response. 151 | func (req *UploadFileRequest) Do(c *Client) (*UploadFileResponse, error) { 152 | req.addSelectPrintAndClose() 153 | 154 | uri := fmt.Sprintf("%s/%s", URIFiles, req.Location) 155 | b, err := c.doRequest("POST", uri, req.w.FormDataContentType(), req.b, FilesLocationPOSTErrors) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | r := &UploadFileResponse{} 161 | if err := json.Unmarshal(b, r); err != nil { 162 | return nil, err 163 | } 164 | 165 | return r, err 166 | } 167 | 168 | func (req *UploadFileRequest) addSelectPrintAndClose() error { 169 | err := req.writer().WriteField("select", fmt.Sprintf("%t", req.Select)) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | err = req.writer().WriteField("print", fmt.Sprintf("%t", req.Print)) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | return req.writer().Close() 180 | } 181 | 182 | // DeleteFileRequest delete the selected path on the selected location. 183 | type DeleteFileRequest struct { 184 | // Location is the target location on which to delete the file, either 185 | // `local` (for OctoPrint’s uploads folder) or \sdcard\ for the printer’s 186 | // SD card (if available) 187 | Location Location 188 | // Path of the file to delete 189 | Path string 190 | } 191 | 192 | // Do sends an API request and returns error if any. 193 | func (req *DeleteFileRequest) Do(c *Client) error { 194 | uri := fmt.Sprintf("%s/%s/%s", URIFiles, req.Location, req.Path) 195 | if _, err := c.doJSONRequest("DELETE", uri, nil, FilesLocationDeleteErrors); err != nil { 196 | return err 197 | } 198 | 199 | return nil 200 | } 201 | 202 | // SelectFileRequest selects a file for printing. 203 | type SelectFileRequest struct { 204 | // Location is target location on which to send the command for is located, 205 | // either local (for OctoPrint’s uploads folder) or sdcard for the 206 | // printer’s SD card (if available) 207 | Location Location `json:"-"` 208 | // Path of the file for which to issue the command 209 | Path string `json:"-"` 210 | // Print, if set to true the file will start printing directly after 211 | // selection. If the printer is not operational when this parameter is 212 | // present and set to true, the request will fail with a response of 213 | // 409 Conflict. 214 | Print bool `json:"print"` 215 | } 216 | 217 | // Do sends an API request and returns an error if any. 218 | func (cmd *SelectFileRequest) Do(c *Client) error { 219 | b := bytes.NewBuffer(nil) 220 | if err := cmd.encode(b); err != nil { 221 | return err 222 | } 223 | 224 | uri := fmt.Sprintf("%s/%s/%s", URIFiles, cmd.Location, cmd.Path) 225 | _, err := c.doJSONRequest("POST", uri, b, FilesLocationPathPOSTErrors) 226 | return err 227 | } 228 | 229 | func (cmd *SelectFileRequest) encode(w io.Writer) error { 230 | return json.NewEncoder(w).Encode(struct { 231 | Command string `json:"command"` 232 | SelectFileRequest 233 | }{ 234 | Command: "select", 235 | SelectFileRequest: *cmd, 236 | }) 237 | } 238 | -------------------------------------------------------------------------------- /files_test.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestUploadFileRequest_Do(t *testing.T) { 11 | cli := NewClient("http://localhost:5000", "") 12 | 13 | r := &UploadFileRequest{Location: Local} 14 | err := r.AddFile("foo.gcode", bytes.NewBufferString("foo")) 15 | assert.NoError(t, err) 16 | 17 | state, err := r.Do(cli) 18 | assert.NoError(t, err) 19 | assert.Equal(t, "foo.gcode", state.File.Local.Name) 20 | 21 | err = (&DeleteFileRequest{Location: Local, Path: "foo.gcode"}).Do(cli) 22 | assert.NoError(t, err) 23 | } 24 | 25 | func TestUploadFileRequest_DoWithFolder(t *testing.T) { 26 | cli := NewClient("http://localhost:5000", "") 27 | 28 | r := &UploadFileRequest{Location: Local} 29 | err := r.AddFolder("qux") 30 | assert.NoError(t, err) 31 | 32 | state, err := r.Do(cli) 33 | assert.NoError(t, err) 34 | assert.Equal(t, true, state.Done) 35 | } 36 | 37 | func TestFilesRequest_Do(t *testing.T) { 38 | cli := NewClient("http://localhost:5000", "") 39 | 40 | ur := &UploadFileRequest{Location: Local} 41 | err := ur.AddFile("foo.gcode", bytes.NewBufferString("foo")) 42 | assert.NoError(t, err) 43 | 44 | _, err = ur.Do(cli) 45 | assert.NoError(t, err) 46 | 47 | files, err := (&FilesRequest{}).Do(cli) 48 | assert.NoError(t, err) 49 | 50 | assert.True(t, len(files.Files) >= 1) 51 | err = (&DeleteFileRequest{Location: Local, Path: "foo.gcode"}).Do(cli) 52 | assert.NoError(t, err) 53 | return 54 | 55 | r := &FileRequest{Location: Local, Filename: "foo.gcode"} 56 | file, err := r.Do(cli) 57 | assert.NoError(t, err) 58 | 59 | assert.Equal(t, "foo.gcode", file.Name) 60 | 61 | err = (&DeleteFileRequest{Location: Local, Path: "foo.gcode"}).Do(cli) 62 | assert.NoError(t, err) 63 | } 64 | 65 | func TestSelectFileRequest_Do(t *testing.T) { 66 | cli := NewClient("http://localhost:5000", "") 67 | 68 | ur := &UploadFileRequest{Location: Local} 69 | err := ur.AddFile("foo2.gcode", bytes.NewBufferString("foo")) 70 | assert.NoError(t, err) 71 | _, err = ur.Do(cli) 72 | assert.NoError(t, err) 73 | 74 | r := &SelectFileRequest{Location: Local, Path: "foo2.gcode"} 75 | err = r.Do(cli) 76 | assert.NoError(t, err) 77 | } 78 | 79 | func xxxTestFilesRequest_DoWithLocation(t *testing.T) { 80 | cli := NewClient("http://localhost:5000", "") 81 | 82 | files, err := (&FilesRequest{Location: SDCard}).Do(cli) 83 | assert.NoError(t, err) 84 | 85 | assert.Len(t, files.Files, 0) 86 | } 87 | -------------------------------------------------------------------------------- /job.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | ) 8 | 9 | const JobTool = "/api/job" 10 | 11 | var JobToolErrors = statusMapping{ 12 | 409: "Printer is not operational or the current print job state does not match the preconditions for the command.", 13 | } 14 | 15 | // JobRequest retrieve information about the current job (if there is one). 16 | type JobRequest struct{} 17 | 18 | // Do sends an API request and returns the API response. 19 | func (cmd *JobRequest) Do(c *Client) (*JobResponse, error) { 20 | b, err := c.doJSONRequest("GET", JobTool, nil, nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | r := &JobResponse{} 26 | if err := json.Unmarshal(b, r); err != nil { 27 | return nil, err 28 | } 29 | 30 | return r, err 31 | } 32 | 33 | // StartRequest starts the print of the currently selected file. 34 | type StartRequest struct{} 35 | 36 | // Do sends an API request and returns an error if any. 37 | func (cmd *StartRequest) Do(c *Client) error { 38 | payload := map[string]string{"command": "start"} 39 | 40 | b := bytes.NewBuffer(nil) 41 | if err := json.NewEncoder(b).Encode(payload); err != nil { 42 | return err 43 | } 44 | 45 | _, err := c.doJSONRequest("POST", JobTool, b, JobToolErrors) 46 | return err 47 | } 48 | 49 | // CancelRequest cancels the current print job. 50 | type CancelRequest struct{} 51 | 52 | // Do sends an API request and returns an error if any. 53 | func (cmd *CancelRequest) Do(c *Client) error { 54 | payload := map[string]string{"command": "cancel"} 55 | 56 | b := bytes.NewBuffer(nil) 57 | if err := json.NewEncoder(b).Encode(payload); err != nil { 58 | return err 59 | } 60 | 61 | _, err := c.doJSONRequest("POST", JobTool, b, JobToolErrors) 62 | return err 63 | } 64 | 65 | // RestartRequest restart the print of the currently selected file from the 66 | // beginning. There must be an active print job for this to work and the print 67 | // job must currently be paused 68 | type RestartRequest struct{} 69 | 70 | // Do sends an API request and returns an error if any. 71 | func (cmd *RestartRequest) Do(c *Client) error { 72 | payload := map[string]string{"command": "restart"} 73 | 74 | b := bytes.NewBuffer(nil) 75 | if err := json.NewEncoder(b).Encode(payload); err != nil { 76 | return err 77 | } 78 | 79 | _, err := c.doJSONRequest("POST", JobTool, b, JobToolErrors) 80 | return err 81 | } 82 | 83 | type PauseAction string 84 | 85 | const ( 86 | // Pause the current job if it’s printing, does nothing if it’s already paused. 87 | Pause PauseAction = "pause" 88 | // Resume the current job if it’s paused, does nothing if it’s printing. 89 | Resume PauseAction = "resume" 90 | // Toggle the pause state of the job, pausing it if it’s printing and 91 | // resuming it if it’s currently paused. 92 | Toggle PauseAction = "toggle" 93 | ) 94 | 95 | // PauseRequest pauses/resumes/toggles the current print job. 96 | type PauseRequest struct { 97 | // Action specifies which action to take. 98 | // In order to stay backwards compatible to earlier iterations of this API, 99 | // the default action to take if no action parameter is supplied is to 100 | // toggle the print job status. 101 | Action PauseAction `json:"action"` 102 | } 103 | 104 | // Do sends an API request and returns an error if any. 105 | func (cmd *PauseRequest) Do(c *Client) error { 106 | b := bytes.NewBuffer(nil) 107 | if err := cmd.encode(b); err != nil { 108 | return err 109 | } 110 | 111 | _, err := c.doJSONRequest("POST", JobTool, b, JobToolErrors) 112 | return err 113 | } 114 | 115 | func (cmd *PauseRequest) encode(w io.Writer) error { 116 | return json.NewEncoder(w).Encode(struct { 117 | Command string `json:"command"` 118 | PauseRequest 119 | }{ 120 | Command: "pause", 121 | PauseRequest: *cmd, 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /printer.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | URIPrinter = "/api/printer" 13 | URIPrintHead = "/api/printer/printhead" 14 | URIPrintTool = "/api/printer/tool" 15 | URIPrintBed = "/api/printer/bed" 16 | URIPrintSD = "/api/printer/sd" 17 | URICommand = "/api/printer/command" 18 | URICommandCustom = "/api/printer/command/custom" 19 | ) 20 | 21 | var ( 22 | PrintErrors = statusMapping{ 23 | 409: "Printer is not operational", 24 | } 25 | PrintHeadJobErrors = statusMapping{ 26 | 400: "Invalid axis specified, invalid value for travel amount for a jog command or factor for feed rate or otherwise invalid request", 27 | 409: "Printer is not operational or currently printing", 28 | } 29 | PrintToolErrors = statusMapping{ 30 | 400: "Targets or offsets contains a property or tool contains a value not matching the format tool{n}, the target/offset temperature, extrusion amount or flow rate factor is not a valid number or outside of the supported range, or if the request is otherwise invalid", 31 | 409: "Printer is not operational", 32 | } 33 | PrintBedErrors = statusMapping{ 34 | 409: "Printer is not operational or the selected printer profile does not have a heated bed.", 35 | } 36 | PrintSDErrors = statusMapping{ 37 | 404: "SD support has been disabled in OctoPrint’s settings.", 38 | 409: "SD card has not been initialized.", 39 | } 40 | ) 41 | 42 | // StateRequest retrieves the current state of the printer. 43 | type StateRequest struct { 44 | // History if true retrieve the temperature history. 45 | History bool 46 | // Limit limtis amount of returned history data points. 47 | Limit int 48 | // Exclude list of fields to exclude from the response (e.g. if not 49 | // needed by the client). Valid values to supply here are `temperature`, 50 | // `sd` and `state`. 51 | Exclude []string 52 | } 53 | 54 | // Do sends an API request and returns the API response. 55 | func (cmd *StateRequest) Do(c *Client) (*FullStateResponse, error) { 56 | uri := fmt.Sprintf("%s?history=%t&limit=%d&exclude=%s", URIPrinter, 57 | cmd.History, cmd.Limit, strings.Join(cmd.Exclude, ","), 58 | ) 59 | 60 | b, err := c.doJSONRequest("GET", uri, nil, PrintErrors) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | r := &FullStateResponse{} 66 | if err := json.Unmarshal(b, r); err != nil { 67 | return nil, err 68 | } 69 | 70 | return r, err 71 | } 72 | 73 | // PrintHeadJogRequest jogs the print head (relatively) by a defined amount in 74 | // one or more axes. 75 | type PrintHeadJogRequest struct { 76 | // X is the amount distance to travel in mm or coordinate to jog print head 77 | // on x axis. 78 | X int `json:"x,omitempty"` 79 | // Y is the amount distance to travel in mm or coordinate to jog print head 80 | // on y axis. 81 | Y int `json:"y,omitempty"` 82 | // Z is the amount distance to travel in mm.or coordinate to jog print head 83 | // on x axis. 84 | Z int `json:"z,omitempty"` 85 | // Absolute is whether to move relative to current position (provided axes 86 | // values are relative amounts) or to absolute position (provided axes 87 | // values are coordinates) 88 | Absolute bool `json:"absolute"` 89 | // Speed at which to move in mm/s. If not provided, minimum speed for all 90 | // selected axes from printer profile will be used. 91 | Speed int `json:"speed,omitempty"` 92 | } 93 | 94 | // Do sends an API request and returns an error if any. 95 | func (cmd *PrintHeadJogRequest) Do(c *Client) error { 96 | b := bytes.NewBuffer(nil) 97 | if err := cmd.encode(b); err != nil { 98 | return err 99 | } 100 | 101 | _, err := c.doJSONRequest("POST", URIPrintHead, b, PrintHeadJobErrors) 102 | 103 | return err 104 | } 105 | 106 | func (cmd *PrintHeadJogRequest) encode(w io.Writer) error { 107 | return json.NewEncoder(w).Encode(struct { 108 | Command string `json:"command"` 109 | PrintHeadJogRequest 110 | }{ 111 | Command: "jog", 112 | PrintHeadJogRequest: *cmd, 113 | }) 114 | } 115 | 116 | // PrintHeadHomeRequest homes the print head in all of the given axes. 117 | type PrintHeadHomeRequest struct { 118 | // Axes is a list of axes which to home. 119 | Axes []Axis `json:"axes"` 120 | } 121 | 122 | // Do sends an API request and returns an error if any. 123 | func (cmd *PrintHeadHomeRequest) Do(c *Client) error { 124 | b := bytes.NewBuffer(nil) 125 | if err := cmd.encode(b); err != nil { 126 | return err 127 | } 128 | 129 | _, err := c.doJSONRequest("POST", URIPrintHead, b, PrintHeadJobErrors) 130 | return err 131 | } 132 | 133 | func (cmd *PrintHeadHomeRequest) encode(w io.Writer) error { 134 | return json.NewEncoder(w).Encode(struct { 135 | Command string `json:"command"` 136 | PrintHeadHomeRequest 137 | }{ 138 | Command: "home", 139 | PrintHeadHomeRequest: *cmd, 140 | }) 141 | } 142 | 143 | // ToolStateRequest retrieves the current temperature data (actual, target and 144 | // offset) plus optionally a (limited) history (actual, target, timestamp) for 145 | // all of the printer’s available tools. 146 | type ToolStateRequest struct { 147 | // History if true retrieve the temperature history. 148 | History bool 149 | // Limit limtis amount of returned history data points. 150 | Limit int 151 | } 152 | 153 | // Do sends an API request and returns the API response. 154 | func (cmd *ToolStateRequest) Do(c *Client) (*TemperatureState, error) { 155 | uri := fmt.Sprintf("%s?history=%t&limit=%d", URIPrintTool, cmd.History, cmd.Limit) 156 | b, err := c.doJSONRequest("GET", uri, nil, nil) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | r := &TemperatureState{} 162 | if err := json.Unmarshal(b, &r); err != nil { 163 | return nil, err 164 | } 165 | 166 | return r, err 167 | } 168 | 169 | // ToolTargetRequest sets the given target temperature on the printer’s tools. 170 | type ToolTargetRequest struct { 171 | // Target temperature(s) to set, key must match the format tool{n} with n 172 | // being the tool’s index starting with 0. 173 | Targets map[string]float64 `json:"targets"` 174 | } 175 | 176 | // Do sends an API request and returns an error if any. 177 | func (cmd *ToolTargetRequest) Do(c *Client) error { 178 | b := bytes.NewBuffer(nil) 179 | if err := cmd.encode(b); err != nil { 180 | return err 181 | } 182 | 183 | _, err := c.doJSONRequest("POST", URIPrintTool, b, PrintToolErrors) 184 | return err 185 | } 186 | 187 | func (cmd *ToolTargetRequest) encode(w io.Writer) error { 188 | return json.NewEncoder(w).Encode(struct { 189 | Command string `json:"command"` 190 | ToolTargetRequest 191 | }{ 192 | Command: "target", 193 | ToolTargetRequest: *cmd, 194 | }) 195 | } 196 | 197 | // ToolOffsetRequest sets the given temperature offset on the printer’s tools. 198 | type ToolOffsetRequest struct { 199 | // Offset is offset(s) to set, key must match the format tool{n} with n 200 | // being the tool’s index starting with 0. 201 | Offsets map[string]float64 `json:"offsets"` 202 | } 203 | 204 | // Do sends an API request and returns an error if any. 205 | func (cmd *ToolOffsetRequest) Do(c *Client) error { 206 | b := bytes.NewBuffer(nil) 207 | if err := cmd.encode(b); err != nil { 208 | return err 209 | } 210 | 211 | _, err := c.doJSONRequest("POST", URIPrintTool, b, PrintToolErrors) 212 | return err 213 | } 214 | 215 | func (cmd *ToolOffsetRequest) encode(w io.Writer) error { 216 | return json.NewEncoder(w).Encode(struct { 217 | Command string `json:"command"` 218 | ToolOffsetRequest 219 | }{ 220 | Command: "offset", 221 | ToolOffsetRequest: *cmd, 222 | }) 223 | } 224 | 225 | // ToolExtrudeRequest extrudes the given amount of filament from the currently 226 | // selected tool. 227 | type ToolExtrudeRequest struct { 228 | // Amount is the amount of filament to extrude in mm. May be negative to 229 | // retract. 230 | Amount int `json:"amount"` 231 | } 232 | 233 | // Do sends an API request and returns an error if any. 234 | func (cmd *ToolExtrudeRequest) Do(c *Client) error { 235 | b := bytes.NewBuffer(nil) 236 | if err := cmd.encode(b); err != nil { 237 | return err 238 | } 239 | 240 | _, err := c.doJSONRequest("POST", URIPrintTool, b, PrintToolErrors) 241 | return err 242 | } 243 | 244 | func (cmd *ToolExtrudeRequest) encode(w io.Writer) error { 245 | return json.NewEncoder(w).Encode(struct { 246 | Command string `json:"command"` 247 | ToolExtrudeRequest 248 | }{ 249 | Command: "extrude", 250 | ToolExtrudeRequest: *cmd, 251 | }) 252 | } 253 | 254 | // ToolSelectRequest selects the printer’s current tool. 255 | type ToolSelectRequest struct { 256 | // Tool to select, format tool{n} with n being the tool’s index starting 257 | // with 0. 258 | Tool string `json:"tool"` 259 | } 260 | 261 | // Do sends an API request and returns an error if any. 262 | func (cmd *ToolSelectRequest) Do(c *Client) error { 263 | b := bytes.NewBuffer(nil) 264 | if err := cmd.encode(b); err != nil { 265 | return err 266 | } 267 | 268 | _, err := c.doJSONRequest("POST", URIPrintTool, b, PrintToolErrors) 269 | return err 270 | } 271 | 272 | func (cmd *ToolSelectRequest) encode(w io.Writer) error { 273 | return json.NewEncoder(w).Encode(struct { 274 | Command string `json:"command"` 275 | ToolSelectRequest 276 | }{ 277 | Command: "select", 278 | ToolSelectRequest: *cmd, 279 | }) 280 | } 281 | 282 | // ToolFlowrateRequest changes the flow rate factor to apply to extrusion of 283 | // the tool. 284 | type ToolFlowrateRequest struct { 285 | // Factor is the new factor, percentage as integer, between 75 and 125%. 286 | Factor int `json:"factor"` 287 | } 288 | 289 | // Do sends an API request and returns an error if any. 290 | func (cmd *ToolFlowrateRequest) Do(c *Client) error { 291 | b := bytes.NewBuffer(nil) 292 | if err := cmd.encode(b); err != nil { 293 | return err 294 | } 295 | 296 | _, err := c.doJSONRequest("POST", URIPrintTool, b, PrintToolErrors) 297 | return err 298 | } 299 | 300 | func (cmd *ToolFlowrateRequest) encode(w io.Writer) error { 301 | return json.NewEncoder(w).Encode(struct { 302 | Command string `json:"command"` 303 | ToolFlowrateRequest 304 | }{ 305 | Command: "flowrate", 306 | ToolFlowrateRequest: *cmd, 307 | }) 308 | } 309 | 310 | // BedStateRequest retrieves the current temperature data (actual, target and 311 | // offset) plus optionally a (limited) history (actual, target, timestamp) for 312 | // the printer’s heated bed. 313 | // 314 | // It’s also possible to retrieve the temperature history by supplying the 315 | // history query parameter set to true. The amount of returned history data 316 | // points can be limited using the limit query parameter. 317 | type BedStateRequest struct { 318 | // History if true retrieve the temperature history. 319 | History bool 320 | // Limit limtis amount of returned history data points. 321 | Limit int 322 | } 323 | 324 | // Do sends an API request and returns the API response. 325 | func (cmd *BedStateRequest) Do(c *Client) (*TemperatureState, error) { 326 | uri := fmt.Sprintf("%s?history=%t&limit=%d", URIPrintBed, cmd.History, cmd.Limit) 327 | b, err := c.doJSONRequest("GET", uri, nil, PrintBedErrors) 328 | if err != nil { 329 | return nil, err 330 | } 331 | 332 | r := &TemperatureState{} 333 | if err := json.Unmarshal(b, &r); err != nil { 334 | return nil, err 335 | } 336 | 337 | return r, err 338 | } 339 | 340 | // BedTargetRequest sets the given target temperature on the printer’s bed. 341 | type BedTargetRequest struct { 342 | // Target temperature to set. 343 | Target float64 `json:"target"` 344 | } 345 | 346 | // Do sends an API request and returns an error if any. 347 | func (cmd *BedTargetRequest) Do(c *Client) error { 348 | b := bytes.NewBuffer(nil) 349 | if err := cmd.encode(b); err != nil { 350 | return err 351 | } 352 | 353 | _, err := c.doJSONRequest("POST", URIPrintBed, b, PrintBedErrors) 354 | return err 355 | } 356 | 357 | func (cmd *BedTargetRequest) encode(w io.Writer) error { 358 | return json.NewEncoder(w).Encode(struct { 359 | Command string `json:"command"` 360 | BedTargetRequest 361 | }{ 362 | Command: "target", 363 | BedTargetRequest: *cmd, 364 | }) 365 | } 366 | 367 | // BedOffsetRequest sets the given temperature offset on the printer’s bed. 368 | type BedOffsetRequest struct { 369 | // Offset is offset to set. 370 | Offset int `json:"offset"` 371 | } 372 | 373 | // Do sends an API request and returns an error if any. 374 | func (cmd *BedOffsetRequest) Do(c *Client) error { 375 | b := bytes.NewBuffer(nil) 376 | if err := cmd.encode(b); err != nil { 377 | return err 378 | } 379 | 380 | _, err := c.doJSONRequest("POST", URIPrintTool, b, PrintToolErrors) 381 | return err 382 | } 383 | 384 | func (cmd *BedOffsetRequest) encode(w io.Writer) error { 385 | return json.NewEncoder(w).Encode(struct { 386 | Command string `json:"command"` 387 | BedOffsetRequest 388 | }{ 389 | Command: "offset", 390 | BedOffsetRequest: *cmd, 391 | }) 392 | } 393 | 394 | // CommandRequest sends any command to the printer via the serial interface. 395 | // Should be used with some care as some commands can interfere with or even 396 | // stop a running print job. 397 | type CommandRequest struct { 398 | // Commands list of commands to send to the printer. 399 | Commands []string `json:"commands"` 400 | } 401 | 402 | // Do sends an API request and returns an error if any. 403 | func (cmd *CommandRequest) Do(c *Client) error { 404 | b := bytes.NewBuffer(nil) 405 | if err := json.NewEncoder(b).Encode(cmd); err != nil { 406 | return err 407 | } 408 | 409 | _, err := c.doJSONRequest("POST", URICommand, b, nil) 410 | return err 411 | } 412 | 413 | // CustomCommandsRequest retrieves all configured system controls. 414 | type CustomCommandsRequest struct{} 415 | 416 | // Do sends an API request and returns the API response. 417 | func (cmd *CustomCommandsRequest) Do(c *Client) (*CustomCommandsResponse, error) { 418 | b, err := c.doJSONRequest("GET", URICommandCustom, nil, nil) 419 | if err != nil { 420 | return nil, err 421 | } 422 | 423 | r := &CustomCommandsResponse{} 424 | if err := json.Unmarshal(b, r); err != nil { 425 | return nil, err 426 | } 427 | 428 | return r, err 429 | } 430 | 431 | // SDStateRequest retrieves the current state of the printer’s SD card. For this 432 | // request no authentication is needed. 433 | type SDStateRequest struct{} 434 | 435 | // Do sends an API request and returns the API response. 436 | func (cmd *SDStateRequest) Do(c *Client) (*SDState, error) { 437 | b, err := c.doJSONRequest("GET", URIPrintSD, nil, PrintSDErrors) 438 | if err != nil { 439 | return nil, err 440 | } 441 | 442 | r := &SDState{} 443 | if err := json.Unmarshal(b, r); err != nil { 444 | return nil, err 445 | } 446 | 447 | return r, err 448 | } 449 | 450 | // SDInitRequest initializes the printer’s SD card, making it available for use. 451 | // This also includes an initial retrieval of the list of files currently stored 452 | // on the SD card. 453 | type SDInitRequest struct{} 454 | 455 | // Do sends an API request and returns an error if any. 456 | func (cmd *SDInitRequest) Do(c *Client) error { 457 | return doCommandRequest(c, URIPrintSD, "init", PrintSDErrors) 458 | } 459 | 460 | // SDRefreshRequest Refreshes the list of files stored on the printer’s SD card. 461 | type SDRefreshRequest struct{} 462 | 463 | // Do sends an API request and returns an error if any. 464 | func (cmd *SDRefreshRequest) Do(c *Client) error { 465 | return doCommandRequest(c, URIPrintSD, "refresh", PrintSDErrors) 466 | } 467 | 468 | // SDReleaseRequest releases the SD card from the printer. The reverse operation 469 | // to init. After issuing this command, the SD card won’t be available anymore, 470 | // hence and operations targeting files stored on it will fail. 471 | type SDReleaseRequest struct{} 472 | 473 | // Do sends an API request and returns an error if any. 474 | func (cmd *SDReleaseRequest) Do(c *Client) error { 475 | return doCommandRequest(c, URIPrintSD, "release", PrintSDErrors) 476 | } 477 | 478 | // doCommandRequest can be used in any operation where the only required field 479 | // is the `command` field. 480 | func doCommandRequest(c *Client, uri, command string, m statusMapping) error { 481 | v := map[string]string{"command": command} 482 | 483 | b := bytes.NewBuffer(nil) 484 | if err := json.NewEncoder(b).Encode(v); err != nil { 485 | return err 486 | } 487 | 488 | _, err := c.doJSONRequest("POST", uri, b, m) 489 | return err 490 | } 491 | -------------------------------------------------------------------------------- /printer_test.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestStateRequest_Do(t *testing.T) { 11 | cli := NewClient("http://localhost:5000", "") 12 | 13 | r := &StateRequest{} 14 | state, err := r.Do(cli) 15 | assert.NoError(t, err) 16 | 17 | assert.Equal(t, "Operational", state.State.Text) 18 | assert.Len(t, state.Temperature.Current, 2) 19 | assert.Len(t, state.Temperature.History, 0) 20 | } 21 | 22 | func TestStateRequest_DoWithHistory(t *testing.T) { 23 | cli := NewClient("http://localhost:5000", "") 24 | 25 | r := &StateRequest{History: true} 26 | state, err := r.Do(cli) 27 | assert.NoError(t, err) 28 | 29 | assert.Equal(t, "Operational", state.State.Text) 30 | assert.Len(t, state.Temperature.Current, 2) 31 | assert.True(t, len(state.Temperature.History) > 0) 32 | } 33 | 34 | func TestStateRequest_DoWithExclude(t *testing.T) { 35 | cli := NewClient("http://localhost:5000", "") 36 | 37 | r := &StateRequest{Exclude: []string{"temperature"}} 38 | state, err := r.Do(cli) 39 | assert.NoError(t, err) 40 | 41 | assert.Equal(t, "Operational", state.State.Text) 42 | assert.Len(t, state.Temperature.Current, 0) 43 | } 44 | 45 | func TestSDInitRequest_Do(t *testing.T) { 46 | cli := NewClient("http://localhost:5000", "") 47 | 48 | r := &SDInitRequest{} 49 | err := r.Do(cli) 50 | assert.NoError(t, err) 51 | 52 | time.Sleep(50 * time.Millisecond) 53 | 54 | state, err := (&SDStateRequest{}).Do(cli) 55 | assert.NoError(t, err) 56 | assert.True(t, state.Ready) 57 | } 58 | 59 | func TestSDReleaseRequest_Do(t *testing.T) { 60 | cli := NewClient("http://localhost:5000", "") 61 | 62 | r := &SDReleaseRequest{} 63 | err := r.Do(cli) 64 | assert.NoError(t, err) 65 | 66 | state, err := (&SDStateRequest{}).Do(cli) 67 | assert.NoError(t, err) 68 | assert.False(t, state.Ready) 69 | } 70 | 71 | func TestSDRefreshRequest_Do(t *testing.T) { 72 | cli := NewClient("http://localhost:5000", "") 73 | 74 | r := &SDRefreshRequest{} 75 | err := r.Do(cli) 76 | assert.NoError(t, err) 77 | 78 | state, err := (&SDStateRequest{}).Do(cli) 79 | assert.NoError(t, err) 80 | assert.False(t, state.Ready) 81 | } 82 | 83 | func TestCustomCommandsRequest_Do(t *testing.T) { 84 | cli := NewClient("http://localhost:5000", "") 85 | 86 | r := &CustomCommandsRequest{} 87 | s, err := r.Do(cli) 88 | assert.NoError(t, err) 89 | 90 | assert.Len(t, s.Controls, 1) 91 | assert.Equal(t, s.Controls[0].Name, "Example for multiple commands") 92 | assert.Len(t, s.Controls[0].Children, 1) 93 | assert.Equal(t, s.Controls[0].Children[0].Name, "Move X (static)") 94 | assert.Len(t, s.Controls[0].Children[0].Commands, 3) 95 | } 96 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import "encoding/json" 4 | 5 | const URISettings = "/api/settings" 6 | 7 | // SettingsRequest retrieves the current configuration of OctoPrint. 8 | type SettingsRequest struct{} 9 | 10 | // Do sends an API request and returns the API response. 11 | func (cmd *SettingsRequest) Do(c *Client) (*Settings, error) { 12 | b, err := c.doJSONRequest("GET", URISettings, nil, nil) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | r := &Settings{} 18 | if err := json.Unmarshal(b, r); err != nil { 19 | return nil, err 20 | } 21 | 22 | return r, err 23 | } 24 | -------------------------------------------------------------------------------- /settings_test.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSettingsRequest_Do(t *testing.T) { 10 | cli := NewClient("http://localhost:5000", "") 11 | 12 | r := &SettingsRequest{} 13 | settings, err := r.Do(cli) 14 | assert.NoError(t, err) 15 | 16 | assert.Equal(t, settings.API.Enabled, true) 17 | } 18 | -------------------------------------------------------------------------------- /system.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | var ExecuteErrors = statusMapping{ 9 | 404: "The command could not be found for source and action", 10 | 500: "The command didn’t define a command to execute, the command returned a non-zero return code and ignore was not true or some other internal server error occurred", 11 | } 12 | 13 | const URISystemCommands = "/api/system/commands" 14 | 15 | // SystemCommandsRequest retrieves all configured system commands. 16 | type SystemCommandsRequest struct{} 17 | 18 | // Do sends an API request and returns the API response. 19 | func (cmd *SystemCommandsRequest) Do(c *Client) (*SystemCommandsResponse, error) { 20 | b, err := c.doJSONRequest("GET", URISystemCommands, nil, nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | r := &SystemCommandsResponse{} 26 | if err := json.Unmarshal(b, r); err != nil { 27 | return nil, err 28 | } 29 | for i := range r.Core { 30 | x := r.Core[i] 31 | if err2 := json.Unmarshal(x.RawConfirm, x.Confirm); err2 != nil { 32 | x.Confirm = "" 33 | } 34 | } 35 | for i := range r.Custom { 36 | x := r.Custom[i] 37 | if err2 := json.Unmarshal(x.RawConfirm, x.Confirm); err2 != nil { 38 | x.Confirm = "" 39 | } 40 | } 41 | 42 | return r, err 43 | } 44 | 45 | // SystemExecuteCommandRequest retrieves all configured system commands. 46 | type SystemExecuteCommandRequest struct { 47 | // Source for which to list commands. 48 | Source CommandSource `json:"source"` 49 | // Action is the identifier of the command, action from its definition. 50 | Action string `json:"action"` 51 | } 52 | 53 | // Do sends an API request and returns an error if any. 54 | func (cmd *SystemExecuteCommandRequest) Do(c *Client) error { 55 | uri := fmt.Sprintf("%s/%s/%s", URISystemCommands, cmd.Source, cmd.Action) 56 | _, err := c.doJSONRequest("POST", uri, nil, ExecuteErrors) 57 | return err 58 | } 59 | -------------------------------------------------------------------------------- /system_test.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSystemCommandsRequest_Do(t *testing.T) { 10 | cli := NewClient("http://localhost:5000", "") 11 | 12 | r := &SystemCommandsRequest{} 13 | state, err := r.Do(cli) 14 | assert.NoError(t, err) 15 | 16 | assert.Len(t, state.Core, 1) 17 | assert.Len(t, state.Custom, 0) 18 | assert.Equal(t, "shutdown", state.Core[0].Action) 19 | } 20 | 21 | func TestSystemExecuteCommandRequest_Do(t *testing.T) { 22 | cli := NewClient("http://localhost:5000", "") 23 | 24 | r := &SystemExecuteCommandRequest{} 25 | err := r.Do(cli) 26 | assert.Error(t, err) 27 | 28 | r = &SystemExecuteCommandRequest{Source: Core, Action: "shutdown"} 29 | err = r.Do(cli) 30 | assert.NoError(t, err) 31 | } 32 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package octoprint 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | const URIVersion = "/api/version" 8 | 9 | // VersionRequest retrieve information regarding server and API version. 10 | type VersionRequest struct{} 11 | 12 | // Do sends an API request and returns the API response. 13 | func (cmd *VersionRequest) Do(c *Client) (*VersionResponse, error) { 14 | b, err := c.doJSONRequest("GET", URIVersion, nil, nil) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | r := &VersionResponse{} 20 | if err := json.Unmarshal(b, r); err != nil { 21 | return nil, err 22 | } 23 | 24 | return r, err 25 | } 26 | --------------------------------------------------------------------------------