Skip to content

Commit 7589c36

Browse files
committed
Adding a simple local web api to report the plugin's state.
1 parent 7bcf3bb commit 7589c36

File tree

9 files changed

+149
-2
lines changed

9 files changed

+149
-2
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
"localauth",
132132
"localfs",
133133
"localip",
134+
"localwebapi",
134135
"Mailsail",
135136
"mainbaord",
136137
"Mainboard",

bambu_octoeverywhere/bambuclient.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from linux_host.config import Config
1414
from linux_host.networksearch import NetworkSearch
15+
from linux_host.localwebapi import LocalWebApi
1516

1617
from .bambucloud import BambuCloud, LoginStatus
1718
from .bambumodels import BambuState, BambuVersion
@@ -207,6 +208,7 @@ def _ClientWorker(self):
207208
# Random other errors.
208209
Sentry.OnException(f"Failed to connect to the Bambu printer {ipOrHostname}:{self.PortStr}. We will retry in a bit.", e)
209210

211+
LocalWebApi.Get().SetPrinterConnectionState(False)
210212
# Sleep for a bit between tries.
211213
# The main consideration here is to not log too much when the printer is off. But we do still want to connect quickly, when it's back on.
212214
# Note that the system might also do a printer scan after many failed attempts, which can be CPU intensive.
@@ -332,6 +334,7 @@ def _OnSubscribe(self, client:Any, userdata:Any, mid:Any, reason_code_list:List[
332334

333335
# At this point, we know the connection was successful, the access code is correct, and the SN is correct.
334336
self.ConsecutivelyFailedConnectionAttempts = 0
337+
LocalWebApi.Get().SetPrinterConnectionState(True)
335338

336339
# Sub success! Force a full state sync.
337340
self._ForceStateSyncAsync()

bambu_octoeverywhere/bambuhost.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from linux_host.secrets import Secrets
2626
from linux_host.version import Version
2727
from linux_host.logger import LoggerInit
28+
from linux_host.localwebapi import LocalWebApi
2829

2930
from .bambucloud import BambuCloud
3031
from .bambuclient import BambuClient
@@ -115,6 +116,9 @@ def RunBlocking(self, configPath:str, localStorageDir:str, repoRoot:str, devConf
115116
# Init the mdns client
116117
MDns.Init(self.Logger, localStorageDir)
117118

119+
# Init the local web api. This will only start a thread if it's setup to run in the config.
120+
LocalWebApi.Init(self.Logger, printerId, self.Config)
121+
118122
# Init device id
119123
DeviceId.Init(self.Logger)
120124

@@ -258,6 +262,7 @@ def ShowUiPopup(self, title:str, text:str, msgType:str, actionText:Optional[str]
258262
#
259263
def OnPrimaryConnectionEstablished(self, octoKey:str, connectedAccounts:List[str]) -> None:
260264
self.Logger.info("Primary Connection To OctoEverywhere Established - We Are Ready To Go!")
265+
LocalWebApi.Get().OnPrimaryConnectionEstablished(len(connectedAccounts) > 0)
261266

262267
# Give the octoKey to who needs it.
263268
if self.NotificationHandler is not None:

elegoo_octoeverywhere/elegooclient.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from linux_host.config import Config
1717
from linux_host.networksearch import NetworkSearch
18+
from linux_host.localwebapi import LocalWebApi
1819

1920
from .elegoomodels import PrinterState, PrinterAttributes
2021
from .interfaces import IStateTranslator, IFileManager, IWebsocketMux
@@ -336,6 +337,8 @@ def _ClientWorker(self):
336337
except Exception as e:
337338
Sentry.OnException("Elegoo client exception in main WS loop.", e)
338339

340+
LocalWebApi.Get().SetPrinterConnectionState(False)
341+
339342
# Sleep for a bit between tries.
340343
# The main consideration here is to not log too much when the printer is off. But we do still want to connect quickly, when it's back on.
341344
# Note that the system might also do a printer scan after many failed attempts, which can be CPU intensive.
@@ -365,6 +368,7 @@ def _CleanupStateOnDisconnect(self):
365368
# Fired when the websocket is connected.
366369
def _OnWsConnect(self, ws:IWebSocketClient):
367370
self.Logger.info("Connection to the Elegoo printer established!")
371+
LocalWebApi.Get().SetPrinterConnectionState(True)
368372

369373
# Set the connected flag now, so we can send messages.
370374
self.WebSocketConnected = True

elegoo_octoeverywhere/elegoohost.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from linux_host.secrets import Secrets
2525
from linux_host.version import Version
2626
from linux_host.logger import LoggerInit
27-
27+
from linux_host.localwebapi import LocalWebApi
2828

2929
from .slipstream import Slipstream
3030
from .elegooclient import ElegooClient
@@ -116,6 +116,9 @@ def RunBlocking(self, configPath:str, localStorageDir:str, repoRoot:str, devConf
116116
# Init the mdns client
117117
MDns.Init(self.Logger, localStorageDir)
118118

119+
# Init the local web api. This will only start a thread if it's setup to run in the config.
120+
LocalWebApi.Init(self.Logger, printerId, self.Config)
121+
119122
# Init device id
120123
DeviceId.Init(self.Logger)
121124

@@ -257,6 +260,7 @@ def ShowUiPopup(self, title:str, text:str, msgType:str, actionText:Optional[str]
257260
#
258261
def OnPrimaryConnectionEstablished(self, octoKey:str, connectedAccounts:List[str]) -> None:
259262
self.Logger.info("Primary Connection To OctoEverywhere Established - We Are Ready To Go!")
263+
LocalWebApi.Get().OnPrimaryConnectionEstablished(len(connectedAccounts) > 0)
260264

261265
# Give the octoKey to who needs it.
262266
self.NotificationHandler.SetOctoKey(octoKey)

linux_host/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class Config:
3030
GeneralSection = "general"
3131
GeneralBedCooldownThresholdTempC = "bed_cooldown_threshold_temp_celsius"
3232
GeneralBedCooldownThresholdTempCDefault = 40.0
33-
33+
GeneralLocalWebApiPort = "local_web_api_port"
34+
GeneralLocalWebApiBindIp = "local_web_api_bind_ip"
3435

3536
#
3637
# Used for the local Moonraker plugin and companions.

linux_host/localwebapi.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import json
2+
import time
3+
import logging
4+
import threading
5+
6+
from http.server import HTTPServer, BaseHTTPRequestHandler
7+
8+
from octoeverywhere.sentry import Sentry
9+
10+
from linux_host.config import Config
11+
12+
# Creates a simple web server for plugins and systems to use.
13+
class LocalWebApi:
14+
15+
# The singleton instance of the LocalWebApi.
16+
_Instance:"LocalWebApi" = None #pyright: ignore[reportAssignmentType]
17+
18+
@staticmethod
19+
def Init(logger:logging.Logger, pluginId:str, config:Config) -> None:
20+
LocalWebApi._Instance = LocalWebApi(logger, pluginId, config)
21+
22+
23+
@staticmethod
24+
def Get() -> "LocalWebApi":
25+
return LocalWebApi._Instance
26+
27+
28+
def __init__(self, logger:logging.Logger, pluginId:str, config:Config) -> None:
29+
self.Logger = logger
30+
self.PluginId = pluginId
31+
self.WebServerThread = None
32+
33+
# Indicates if the plugin is connected to OctoEverywhere.
34+
self.IsConnectedToOctoEverywhere = False
35+
# Indicates if the plugin is connected to the account.
36+
self.IsAccountLinked = False
37+
# Indicates if the plugin is connected to the printer.
38+
self.IsConnectedToPrinter = False
39+
40+
# The existence of the port enables or disables the web server.
41+
self.HttpPort = config.GetInt(Config.GeneralSection, Config.GeneralLocalWebApiPort, None)
42+
self.HostName = config.GetStr(Config.GeneralSection, Config.GeneralLocalWebApiBindIp, None)
43+
if self.HostName is None:
44+
self.HostName = "0.0.0.0"
45+
self._Start()
46+
47+
48+
# Called when we are connected and we know if there's an account setup with this addon
49+
def OnPrimaryConnectionEstablished(self, hasConnectedAccount:bool):
50+
self.IsAccountLinked = hasConnectedAccount
51+
self.IsConnectedToOctoEverywhere = True
52+
53+
54+
# Called when the OctoEverywhere connection is lost.
55+
def OnOctoEverywhereConnectionLost(self):
56+
self.IsConnectedToOctoEverywhere = False
57+
58+
59+
# Called when the printer connection changes
60+
def SetPrinterConnectionState(self, isConnected:bool):
61+
self.IsConnectedToPrinter = isConnected
62+
63+
64+
def _Start(self):
65+
if self.HttpPort is None or self.HttpPort <= 0:
66+
self.Logger.debug("Local web api is disabled, because the port is not set.")
67+
return
68+
# Start the web server worker thread.
69+
self.WebServerThread = threading.Thread(target=self._WebServerWorker)
70+
self.WebServerThread.start()
71+
72+
73+
def _WebServerWorker(self):
74+
backoff = 0
75+
while True:
76+
# Try to run the webserver forever.
77+
webServer = None
78+
try:
79+
if self.HostName is None or self.HttpPort is None:
80+
self.Logger.debug("Local web api is disabled, because the host name or port is not set.")
81+
return
82+
self.Logger.debug(f"Web Server Starting {self.HostName}:{self.HttpPort}")
83+
webServer = HTTPServer((self.HostName, self.HttpPort), LocalWebApi.WebServerHandler)
84+
self.Logger.debug(f"Web Server Started {self.HostName}:{self.HttpPort}")
85+
webServer.serve_forever()
86+
except Exception as e:
87+
self.Logger.error("Web server exception. "+str(e))
88+
89+
# If we fail, close it.
90+
try:
91+
if webServer is not None:
92+
webServer.server_close()
93+
except Exception as e:
94+
Sentry.OnException("Failed to close the addon webserver.", e)
95+
96+
# Try again after some time.
97+
backoff = min(backoff + 1, 20)
98+
time.sleep(backoff * 0.5)
99+
100+
101+
class WebServerHandler(BaseHTTPRequestHandler):
102+
# Silence access logs
103+
def log_message(self, format, *args): #pylint: disable=redefined-builtin
104+
pass
105+
106+
def do_GET(self):
107+
self.send_response(200)
108+
self.send_header("Content-type", "application/json")
109+
self.end_headers()
110+
111+
# Build the JSON response
112+
localWebApi = LocalWebApi.Get()
113+
response = {
114+
"PluginId": localWebApi.PluginId,
115+
"IsConnectedToOctoEverywhere": localWebApi.IsConnectedToOctoEverywhere,
116+
"IsConnectedToPrinter": localWebApi.IsConnectedToPrinter,
117+
"IsAccountLinked": localWebApi.IsAccountLinked,
118+
}
119+
120+
# Write the response
121+
self.wfile.write(bytes(json.dumps(response), 'utf-8'))

moonraker_octoeverywhere/moonrakerclient.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from octoeverywhere.interfaces import IWebSocketClient, IPrinterStateReporter, WebSocketOpCode
2121

2222
from linux_host.config import Config
23+
from linux_host.localwebapi import LocalWebApi
2324

2425
from .filemetadatacache import FileMetadataCache
2526
from .moonrakercredentialmanager import MoonrakerCredentialManager
@@ -335,6 +336,7 @@ def _WebSocketSend(self, jsonStr:str) -> bool:
335336
# This is called on a background thread, so we can block this.
336337
def _OnWsOpenAndKlippyReady(self) -> None:
337338
self.Logger.info("Moonraker client setting up default notification hooks")
339+
LocalWebApi.Get().SetPrinterConnectionState(True)
338340
# First, we need to setup our notification subs
339341
# https://moonraker.readthedocs.io/en/latest/web_api/#subscribe-to-printer-object-status
340342
# https://moonraker.readthedocs.io/en/latest/printer_objects/
@@ -508,6 +510,7 @@ def _WebSocketWorkerThread(self) -> None:
508510

509511
# Inform that we lost the connection.
510512
self.Logger.info("Moonraker client websocket connection lost. We will try to restart it soon.")
513+
LocalWebApi.Get().SetPrinterConnectionState(False)
511514

512515
# Set that the websocket is disconnected.
513516
with self.WebSocketLock:

moonraker_octoeverywhere/moonrakerhost.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from linux_host.secrets import Secrets
2626
from linux_host.version import Version
2727
from linux_host.logger import LoggerInit
28+
from linux_host.localwebapi import LocalWebApi
2829

2930
from .smartpause import SmartPause
3031
from .uipopupinvoker import UiPopupInvoker
@@ -139,6 +140,9 @@ def RunBlocking(self, klipperConfigDir:str, isCompanionMode:bool, localStorageDi
139140
# Init the mdns client
140141
MDns.Init(self.Logger, localStorageDir)
141142

143+
# Init the local web api. This will only start a thread if it's setup to run in the config.
144+
LocalWebApi.Init(self.Logger, printerId, self.Config)
145+
142146
# Init device id
143147
DeviceId.Init(self.Logger)
144148

@@ -303,6 +307,7 @@ def Rekey(self, reason:str) -> None:
303307
#
304308
def OnPrimaryConnectionEstablished(self, octoKey:str, connectedAccounts:List[str]) -> None:
305309
self.Logger.info("Primary Connection To OctoEverywhere Established - We Are Ready To Go!")
310+
LocalWebApi.Get().OnPrimaryConnectionEstablished(len(connectedAccounts) > 0)
306311

307312
# Check if this printer is unlinked, if so add a message to the log to help the user setup the printer if desired.
308313
# This would be if the skipped the printer link or missed it in the setup script.

0 commit comments

Comments
 (0)