Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 155 additions & 4 deletions octoprint_livegcodecontrol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,174 @@
import octoprint.plugin
import logging # Import the logging module
import re # Import the regular expression module
import threading
import time
import math
import flask

class LedWorker(threading.Thread):
def __init__(self, printer, logger):
super(LedWorker, self).__init__()
self._printer = printer
self._logger = logger
self.daemon = True
self.running = True
self.paused = False

# Configuration
self.colors = ["#FF0000", "#0000FF"]
self.mode = "spatial_wave"
self.speed = 150

# Internal State
self.led_count = 30

def update_config(self, payload):
if "colors" in payload:
self.colors = payload["colors"]
if "mode" in payload:
self.mode = payload["mode"]
if "speed" in payload:
self.speed = payload["speed"]
self._logger.info(f"LedWorker config updated: {payload}")

def run(self):
self._logger.info("LedWorker started")
while self.running:
if self.paused:
time.sleep(1)
continue

try:
is_printing = self._printer.is_printing()

# Adaptive Frequency
if is_printing:
delay = 0.6 # 600ms Safe Mode
else:
delay = 0.05 # 50ms Idle Mode

self.process_frame(is_printing)
time.sleep(delay)

except Exception as e:
self._logger.error(f"LedWorker error: {e}")
time.sleep(1)

def process_frame(self, is_printing):
# Bandwidth Safety / Fallback logic
current_mode = self.mode
if is_printing and self.mode in ["spatial_wave"]: # Add other spatial modes here
current_mode = "solid" # Downgrade to global fade/solid

commands = []

if current_mode == "solid":
# Global Fade (Single M150)
# Assuming first color is primary
color = self.colors[0] if self.colors else "#FFFFFF"
r, g, b = self.hex_to_rgb(color)
commands.append(f"M150 R{r} U{g} B{b}")

elif current_mode == "spatial_wave":
# Multiple M150 commands
# Example wave effect
t = time.time()
for i in range(self.led_count):
phase = (t / (20000.0 / (self.speed or 1))) + (i / 5.0)
r = int(math.sin(phase) * 127 + 128)
b = int(math.cos(phase) * 127 + 128)
commands.append(f"M150 I{i} R{r} U0 B{b}")

# Inject G-code
if commands:
# In a real scenario, you might batch these or send individually
# OctoPrint's send_cmd doesn't support lists for single command, but self._printer.commands does
self._printer.commands(commands, tags=set(["suppress_log"]))

def hex_to_rgb(self, hex_val):
hex_val = hex_val.lstrip('#')
lv = len(hex_val)
return tuple(int(hex_val[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))

def stop(self):
self.running = False


class LiveGCodeControlPlugin(octoprint.plugin.SettingsPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.TemplatePlugin):
octoprint.plugin.TemplatePlugin,
octoprint.plugin.SimpleApiPlugin,
octoprint.plugin.EventHandlerPlugin):

def __init__(self):
# Initialize the logger
self._logger = logging.getLogger("octoprint.plugins.livegcodecontrol")
self._logger.info("LiveGCodeControlPlugin: Initializing...")
self.active_rules = [] # Initialize active_rules
self.last_matched_rule_pattern = None # Initialize last matched rule pattern
self.led_worker = None

def on_after_startup(self):
self._logger.info("LiveGCodeControlPlugin: Starting LedWorker...")
self.led_worker = LedWorker(self._printer, self._logger)
self.led_worker.start()

##~~ SimpleApiPlugin mixin

def on_api_get(self, request):
return flask.jsonify(presets=self._settings.get(["neoflux_presets"]))

def on_api_command(self, command, data):
if command == "update_led_config":
if self.led_worker:
self.led_worker.update_config(data.get('payload', {}))

elif command == "save_preset":
name = data.get("name")
config = data.get("config")
if name and config:
presets = self._settings.get(["neoflux_presets"])
presets[name] = config
self._settings.set(["neoflux_presets"], presets)
self._settings.save()
self._logger.info(f"Saved preset: {name}")

elif command == "delete_preset":
name = data.get("name")
if name:
presets = self._settings.get(["neoflux_presets"])
if name in presets:
del presets[name]
self._settings.set(["neoflux_presets"], presets)
self._settings.save()
self._logger.info(f"Deleted preset: {name}")

def get_api_commands(self):
return dict(
update_led_config=["payload"],
save_preset=["name", "config"],
delete_preset=["name"]
)

def on_event(self, event, payload):
events_mapping = self._settings.get(["neoflux_events"])
if event in events_mapping:
preset_name = events_mapping[event]
presets = self._settings.get(["neoflux_presets"])
if preset_name in presets:
config = presets[preset_name]
if self.led_worker:
self._logger.info(f"Applying preset '{preset_name}' for event '{event}'")
self.led_worker.update_config(config)

##~~ SettingsPlugin mixin

def get_settings_defaults(self):
return dict(
rules=[] # Default empty list for rules
rules=[], # Default empty list for rules
neoflux_presets={},
neoflux_events={}
)

def on_settings_initialized(self):
Expand All @@ -44,8 +195,8 @@ def get_assets(self):
# Define your plugin's asset files to automatically include in the
# core UI here.
return dict(
js=["js/livegcodecontrol.js"],
css=["css/livegcodecontrol.css"],
js=["js/livegcodecontrol.js", "js/neoflux_ui.js"],
css=["css/livegcodecontrol.css", "css/neoflux.css"],
less=["less/livegcodecontrol.less"]
)

Expand Down
78 changes: 78 additions & 0 deletions octoprint_livegcodecontrol/static/css/neoflux.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* Neoflux Cyberpunk Styling */
#neoflux-container {
background-color: #0d0d0d;
color: #00ffea;
font-family: 'Courier New', Courier, monospace;
padding: 20px;
border: 2px solid #00ffea;
box-shadow: 0 0 15px #00ffea;
}

#neoflux-container h2 {
text-shadow: 0 0 10px #00ffea;
border-bottom: 1px solid #00ffea;
padding-bottom: 10px;
}

.neoflux-control-panel {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 20px;
}

.neoflux-preview {
flex: 1;
min-width: 300px;
border: 1px dashed #ff00ff;
padding: 10px;
background-color: #1a1a1a;
}

.neoflux-controls {
flex: 1;
min-width: 300px;
}

canvas#neoflux-canvas {
width: 100%;
height: auto;
background-color: #000;
box-shadow: 0 0 10px #ff00ff;
}

.neoflux-btn {
background-color: #000;
color: #ff00ff;
border: 1px solid #ff00ff;
padding: 10px 20px;
cursor: pointer;
font-size: 1.2em;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 2px;
}

.neoflux-btn:hover {
background-color: #ff00ff;
color: #000;
box-shadow: 0 0 15px #ff00ff;
}

.neoflux-input-group {
margin-bottom: 15px;
}

.neoflux-input-group label {
display: block;
margin-bottom: 5px;
color: #00ffea;
}

.neoflux-input {
background-color: #222;
border: 1px solid #00ffea;
color: #fff;
padding: 8px;
width: 100%;
}
Loading