1+ /* ___ _ ____ _ _ _ ____ ___
2+ / _ \ ___| |_ ___ | _ \ _ __(_)_ __ | |_ / \ | _ \_ _|
3+ | | | |/ __| __/ _ \| |_) | '__| | '_ \| __| / _ \ | |_) | |
4+ | |_| | (__| || (_) | __/| | | | | | | |_ / ___ \| __/| |
5+ \___/ \___|\__\___/|_| |_| |_|_| |_|\__/_/ \_\_| |___|
6+ .......By Stephen Ludgate https://www.chunkymedia.co.uk.......
7+ .......Redesigned for Prusa Link by Marius Tetard
8+ .......Updated for API Key Auth....... 08/2025....
9+
10+ */
11+
12+ #include " PrusaLinkAPI.h"
13+ #include " Arduino.h"
14+
15+ // The Base64 helper function is no longer needed and has been removed.
16+
17+ PrusaLinkApi::PrusaLinkApi (void ){
18+ if (_debug)
19+ Serial.println (" Be sure to Call init to setup and start the PrusaLinkApi instance" );
20+ }
21+
22+ PrusaLinkApi::PrusaLinkApi (Client &client, IPAddress prusaLinkIp, int prusaLinkPort, const char * apiKey) {
23+ init (client, prusaLinkIp, prusaLinkPort, apiKey);
24+ }
25+
26+ void PrusaLinkApi::init (Client &client, IPAddress prusaLinkIp, int prusaLinkPort, const char * apiKey) {
27+ _client = &client;
28+ _prusaLinkIp = prusaLinkIp;
29+ _prusaLinkPort = prusaLinkPort;
30+ _usingIpAddress = true ;
31+ _apiKey = apiKey; // Store the API Key
32+ }
33+
34+ PrusaLinkApi::PrusaLinkApi (Client &client, char *prusaLinkUrl, int prusaLinkPort, const char * apiKey) {
35+ init (client, prusaLinkUrl, prusaLinkPort, apiKey);
36+ }
37+
38+ void PrusaLinkApi::init (Client &client, char *prusaLinkUrl, int prusaLinkPort, const char * apiKey) {
39+ _client = &client;
40+ _prusaLinkUrl = prusaLinkUrl;
41+ _prusaLinkPort = prusaLinkPort;
42+ _usingIpAddress = false ;
43+ _apiKey = apiKey; // Store the API Key
44+ }
45+
46+ String PrusaLinkApi::sendRequestToPrusaLink (String type, String command, const char *data) {
47+ if (_debug)
48+ Serial.println (" PrusaLinkApi::sendRequestToPrusaLink() CALLED" );
49+
50+ if ((type != " GET" ) && (type != " POST" ) && (type != " DELETE" )) {
51+ if (_debug)
52+ Serial.println (" PrusaLinkApi::sendRequestToPrusaLink() Only GET, POST & DELETE are supported... exiting." );
53+ return " " ;
54+ }
55+
56+ String statusCode = " " ;
57+ String headers = " " ;
58+ String body = " " ;
59+ bool finishedStatusCode = false ;
60+ bool finishedHeaders = false ;
61+ bool currentLineIsBlank = true ;
62+ int ch_count = 0 ;
63+ unsigned long now;
64+
65+ bool connected;
66+
67+ if (_usingIpAddress)
68+ connected = _client->connect (_prusaLinkIp, _prusaLinkPort);
69+ else
70+ connected = _client->connect (_prusaLinkUrl, _prusaLinkPort);
71+
72+ if (connected) {
73+ if (_debug)
74+ Serial.println (" .... connected to server" );
75+
76+ _client->println (type + " " + command + " HTTP/1.1" );
77+ _client->print (" Host: " );
78+ if (_usingIpAddress)
79+ _client->println (_prusaLinkIp);
80+ else
81+ _client->println (_prusaLinkUrl);
82+
83+ // Send the API Key in the X-Api-Key header
84+ _client->print (" X-Api-Key: " );
85+ _client->println (_apiKey);
86+
87+ _client->println (" User-Agent: " + String (USER_AGENT));
88+ _client->println (" Connection: close" );
89+ if (data != NULL ) {
90+ _client->println (" Content-Type: application/json" );
91+ _client->print (" Content-Length: " );
92+ _client->println (strlen (data));
93+ _client->println ();
94+ _client->println (data);
95+ } else {
96+ _client->println ();
97+ }
98+
99+ now = millis ();
100+ while (millis () - now < PLAPI_TIMEOUT) {
101+ while (_client->available ()) {
102+ char c = _client->read ();
103+
104+ if (_debug)
105+ Serial.print (c);
106+
107+ if (!finishedStatusCode) {
108+ if (c == ' \n ' )
109+ finishedStatusCode = true ;
110+ else
111+ statusCode = statusCode + c;
112+ }
113+
114+ if (!finishedHeaders) {
115+ if (c == ' \n ' ) {
116+ if (currentLineIsBlank)
117+ finishedHeaders = true ;
118+ }
119+ headers = headers + c;
120+ } else {
121+ body = body + c;
122+ }
123+ if (c == ' \n ' )
124+ currentLineIsBlank = true ;
125+ else if (c != ' \r ' ) {
126+ currentLineIsBlank = false ;
127+ }
128+ }
129+ }
130+ } else {
131+ if (_debug) {
132+ Serial.println (" connection failed" );
133+ }
134+ }
135+
136+ closeClient ();
137+
138+ httpStatusCode = extractHttpCode (statusCode, body);
139+ if (_debug) {
140+ Serial.print (" \n httpCode:" );
141+ Serial.println (httpStatusCode);
142+ }
143+ if (httpStatusCode < 200 || httpStatusCode >= 300 ){httpErrorBody = body;}
144+
145+ return body;
146+ }
147+
148+ String PrusaLinkApi::sendGetToPrusaLink (String endpoint) {
149+ if (_debug)
150+ Serial.println (" PrusaLinkApi::sendGetToPrusaLink() CALLED" );
151+
152+ return sendRequestToPrusaLink (" GET" , endpoint, NULL );
153+ }
154+
155+ String PrusaLinkApi::sendPostToPrusaLink (String endpoint, const char *postData) {
156+ if (_debug)
157+ Serial.println (" PrusaLinkApi::sendPostToPrusaLink() CALLED" );
158+ return sendRequestToPrusaLink (" POST" , endpoint, postData);
159+ }
160+
161+ String PrusaLinkApi::sendDeleteToPrusaLink (String endpoint) {
162+ if (_debug)
163+ Serial.println (" PrusaLinkApi::sendDeleteToPrusaLink() CALLED" );
164+ return sendRequestToPrusaLink (" DELETE" , endpoint, NULL );
165+ }
166+
167+
168+ bool PrusaLinkApi::getPrinterStatus () {
169+ String response = sendGetToPrusaLink (" /api/v1/status" );
170+
171+ StaticJsonDocument<JSONDOCUMENT_SIZE> root;
172+ DeserializationError error = deserializeJson (root, response);
173+
174+ if (error) {
175+ if (_debug) Serial.println (" Failed to parse printer status" );
176+ return false ;
177+ }
178+
179+ const char * stateStr = root[" printer" ][" state" ] | " UNKNOWN" ;
180+ strncpy (printerStats.printerState , stateStr, sizeof (printerStats.printerState ) - 1 );
181+ printerStats.printerState [sizeof (printerStats.printerState ) - 1 ] = ' \0 ' ;
182+
183+ printerStats.printerStatePrinting = (strcmp (printerStats.printerState , " PRINTING" ) == 0 );
184+ printerStats.printerStatePaused = (strcmp (printerStats.printerState , " PAUSED" ) == 0 );
185+ printerStats.printerStateError = (strcmp (printerStats.printerState , " ERROR" ) == 0 ) || (strcmp (printerStats.printerState , " ATTENTION" ) == 0 );
186+ printerStats.printerStateFinished = (strcmp (printerStats.printerState , " FINISHED" ) == 0 );
187+ printerStats.printerStateReady = (strcmp (printerStats.printerState , " IDLE" ) == 0 );
188+ printerStats.printerStateBusy = (strcmp (printerStats.printerState , " BUSY" ) == 0 );
189+
190+ printerStats.printerBedTempActual = root[" printer" ][" temp_bed" ];
191+ printerStats.printerBedTempTarget = root[" printer" ][" target_bed" ];
192+
193+ printerStats.printerTool0TempActual = root[" printer" ][" temp_nozzle" ];
194+ printerStats.printerTool0TempTarget = root[" printer" ][" target_nozzle" ];
195+
196+ return true ;
197+ }
198+
199+ bool PrusaLinkApi::getJobInfo () {
200+ String response = sendGetToPrusaLink (" /api/v1/job" );
201+
202+ StaticJsonDocument<JSONDOCUMENT_SIZE> root;
203+ DeserializationError error = deserializeJson (root, response);
204+
205+ if (error || !root.containsKey (" progress" )) {
206+ if (_debug && error) Serial.println (" Failed to parse job info" );
207+ if (_debug && !root.containsKey (" progress" )) Serial.println (" No active job" );
208+ return false ;
209+ }
210+
211+ const char * filenameStr = root[" file" ][" display_name" ] | " No file" ;
212+ strncpy (jobInfo.jobFileName , filenameStr, sizeof (jobInfo.jobFileName ) - 1 );
213+ jobInfo.jobFileName [sizeof (jobInfo.jobFileName ) - 1 ] = ' \0 ' ;
214+
215+ jobInfo.progressCompletion = root[" progress" ];
216+ jobInfo.progressPrintTime = root[" time_printing" ];
217+ jobInfo.progressPrintTimeLeft = root[" time_remaining" ];
218+
219+ return true ;
220+ }
221+
222+ bool PrusaLinkApi::printerCommand (const char * gcodeCommand) {
223+ char postData[POSTDATA_GCODE_SIZE];
224+ snprintf (postData, POSTDATA_GCODE_SIZE, " {\" command\" : \" %s\" }" , gcodeCommand);
225+ sendPostToPrusaLink (" /api/v1/printer/command" , postData);
226+ return (httpStatusCode == 204 );
227+ }
228+
229+ void PrusaLinkApi::closeClient () { _client->stop (); }
230+
231+ int PrusaLinkApi::extractHttpCode (String statusCode, String body) {
232+ if (_debug) {
233+ Serial.print (" \n Status code to extract: " );
234+ Serial.println (statusCode);
235+ }
236+ int firstSpace = statusCode.indexOf (" " );
237+ int lastSpace = statusCode.lastIndexOf (" " );
238+ if (firstSpace > -1 && lastSpace > -1 && firstSpace != lastSpace) {
239+ String statusCodeExtract = statusCode.substring (firstSpace + 1 , lastSpace);
240+ int statusCodeInt = statusCodeExtract.toInt ();
241+ if (_debug and statusCodeInt != 200 and statusCodeInt != 201 and statusCodeInt != 202 and statusCodeInt != 204 ) {
242+ Serial.print (" \n SERVER RESPONSE CODE: " + statusCode);
243+ if (body != " " )
244+ Serial.println (" - " + body);
245+ else
246+ Serial.println ();
247+ }
248+ return statusCodeInt;
249+ } else
250+ return -1 ;
251+ }
0 commit comments