Skip to content

Commit

Permalink
🔀 Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
cp2004 committed Jul 15, 2020
2 parents 6640c83 + 5a1c1de commit 8d022c8
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 41 deletions.
80 changes: 52 additions & 28 deletions octoprint_ws281x_led_status/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
MP_CONTEXT = get_context('fork')
PI_REGEX = r"(?<=Raspberry Pi)(.*)(?=Model)"
_PROC_DT_MODEL_PATH = "/proc/device-tree/model"
BLOCKING_TEMP_GCODES = ["M109","M190"]
BLOCKING_TEMP_GCODES = ["M109", "M190"]

STANDARD_EFFECT_NICE_NAMES = {
'Solid Color': 'solid',
Expand Down Expand Up @@ -42,17 +42,17 @@ class WS281xLedStatusPlugin(octoprint.plugin.StartupPlugin,
'PrintDone': 'success',
'PrintPaused': 'paused'
}
current_effect_thread = None # thread object
current_state = None # Idle, startup, progress etc. Used to put the old effect back on settings change
current_effect_process = None # multiprocessing Process object
current_state = None # Idle, startup, progress etc. Used to put the old effect back on settings change/light switch
effect_queue = MP_CONTEXT.Queue() # pass name of effects here

SETTINGS = {} # Filled in on startup
PI_MODEL = None
PI_MODEL = None # Filled in on startup

heating = False
heating = False # True when heating is detected, options below are helpers for tracking heatup.
temp_target = 0
current_heater_heating = None
tool_to_target = 0
tool_to_target = 0 # Overridden by the plugin settings

# Asset plugin
def get_assets(self):
Expand All @@ -71,9 +71,9 @@ def on_after_startup(self):
# Shutdown plugin
def on_shutdown(self):
self._logger.info("RGB LED Status runner stopped")
if self.current_effect_thread is not None:
if self.current_effect_process is not None:
self.effect_queue.put("KILL")
self.current_effect_thread.join()
self.current_effect_process.join()

# Settings plugin
def on_settings_save(self, data):
Expand Down Expand Up @@ -131,7 +131,11 @@ def get_settings_defaults(self):
progress_heatup_color='#ff0000',
progress_heatup_tool_enabled=True,
progress_heatup_bed_enabled=True,
progress_heatup_tool_key=0
progress_heatup_tool_key=0,

active_hours_enabled=False,
active_hours_start="09:00",
active_hours_stop="21:00"
)

# Template plugin
Expand All @@ -145,7 +149,10 @@ def get_template_vars(self):

# Wizard plugin bits
def is_wizard_required(self):
return not any(self.get_wizard_details())
for item in self.get_wizard_details().values():
if not item:
return True
return False

def get_wizard_details(self):
return dict(
Expand All @@ -160,7 +167,8 @@ def get_wizard_version(self):
return 1

def on_wizard_finish(self, handled):
self._logger.info("You will need to restart your Pi for the changes to take effect") # TODO make this a popup? not very useful here
self._logger.info("You will need to restart your Pi for the changes to take effect")
# TODO make this a popup? not very useful here

# Simple API plugin
def get_api_commands(self):
Expand All @@ -173,17 +181,29 @@ def get_api_commands(self):
)

def on_api_command(self, command, data):
api_to_command = { # -S for sudo commands means accept password from stdin instead of terminal, see https://www.sudo.ws/man/1.8.13/sudo.man.html#S
api_to_command = {
# -S for sudo commands means accept password from stdin, see https://www.sudo.ws/man/1.8.13/sudo.man.html#S
'adduser': ['sudo', '-S', 'adduser', 'pi', 'gpio'],
'enable_spi': ['sudo', '-S', 'bash', '-c', 'echo \'dtparam=spi=on\' >> /boot/config.txt'],
'set_core_freq': ['sudo', '-S', 'bash', '-c', 'echo \'core_freq=500\' >> /boot/config.txt' if self.PI_MODEL == '4' else 'echo \'core_freq=250\' >> /boot/config.txt'],
'set_core_freq': ['sudo', '-S', 'bash', '-c',
'echo \'core_freq=500\' >> /boot/config.txt' if self.PI_MODEL == '4' else 'echo \'core_freq=250\' >> /boot/config.txt'],
'set_core_freq_min': ['sudo', '-S', 'bash', '-c', 'echo \'core_freq_min=500\' >> /boot/config.txt' if self.PI_MODEL == '4' else 'echo \'core_freq_min=250\' >> /boot/config.txt'],
'spi_buffer_increase': ['sudo', '-S', 'sed', '-i', '$ s/$/ spidev.bufsiz=32768/', '/boot/cmdline.txt']
}
stdout, error = self.run_system_command(api_to_command[command], data.get('password'))
return self.build_response(error)
api_command_validator = {
'adduser' : self.is_adduser_done,
'enable_spi': self.is_spi_enabled,
'set_core_freq': self.is_core_freq_set,
'set_core_freq_min': self.is_core_freq_min_set,
'spi_buffer_increase': self.is_spi_buffer_increased
}
if not api_command_validator[command]():
stdout, error = self.run_system_command(api_to_command[command], data.get('password'))
else:
error = None
return self.api_cmd_response(error)

def build_response(self, errors=None):
def api_cmd_response(self, errors=None):
from flask import jsonify
details = self.get_wizard_details()
details.update(errors=errors)
Expand Down Expand Up @@ -216,7 +236,7 @@ def is_adduser_done(self):
def is_spi_enabled(self):
with io.open('/boot/config.txt') as file:
for line in file:
if 'dtparam=spi=on' in line:
if line.startswith('dtparam=spi=on'):
return True
return False

Expand All @@ -228,19 +248,19 @@ def is_spi_buffer_increased(self):
return False

def is_core_freq_set(self):
if self.PI_MODEL == '4':
return True
if self.PI_MODEL == '4': # Pi 4's default is 500, which is compatible with SPI.
return True # any change to core_freq is ignored on a Pi 4, so let's not bother.
with io.open('/boot/config.txt') as file:
for line in file:
if 'core_freq=250' in line:
if line.startswith('core_freq=250'):
return True
return False

def is_core_freq_min_set(self):
if int(self.PI_MODEL) == 4:
with io.open('/boot/config.txt') as file:
if int(self.PI_MODEL) == 4: # Pi 4 has a variable clock speed, which messes up SPI timing
with io.open('/boot/config.txt') as file: # This is only required on pi 4, not other models.
for line in file:
if 'core_freq_min=500' in line:
if line.startswith('core_freq_min=500'):
return True
return False
else:
Expand All @@ -266,6 +286,10 @@ def refresh_settings(self):
if not self.tool_to_target:
self.tool_to_target = 0

self.SETTINGS['active_start'] = self._settings.get(['active_hours_start']) if self._settings.get(['active_hours_enabled']) else None
self.SETTINGS['active_stop'] = self._settings.get(['active_hours_stop']) if self._settings.get(['active_hours_enabled']) else None


self.SETTINGS['strip'] = {}
for setting in STRIP_SETTINGS:
if setting == 'led_invert': # Boolean settings
Expand Down Expand Up @@ -297,12 +321,12 @@ def restart_strip(self):

def start_effect_process(self):
# Start effect runner here
self.current_effect_thread = MP_CONTEXT.Process(
self.current_effect_process = MP_CONTEXT.Process(
target=effect_runner,
name="RGB LED Status Effect Process",
args=(self._logger, self.effect_queue, self.SETTINGS, self.current_state),
daemon=True)
self.current_effect_thread.start()
self.current_effect_process.start()
self._logger.info("RGB LED Status runner started")

def stop_effect_process(self):
Expand All @@ -311,10 +335,10 @@ def stop_effect_process(self):
As this can potentially hang the server for a fraction of a second while the final frame of the effect runs,
it is not called often - only on update of settings & shutdown.
"""
self._logger.info("RGB LED Status runner stopped")
if self.current_effect_thread is not None:
if self.current_effect_process is not None:
self.effect_queue.put("KILL")
self.current_effect_thread.join()
self.current_effect_process.join()
self._logger.info("RGB LED Status runner stopped")

def update_effect(self, mode_name, value=None):
"""
Expand Down
47 changes: 36 additions & 11 deletions octoprint_ws281x_led_status/effect_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def on_exit(led_strip):
print("[RUNNER] Exiting effect runner")
return

start_time = all_settings['active_start'].split(":") if all_settings['active_start'] else None
end_time = all_settings['active_stop'].split(":") if all_settings['active_stop'] else None

msg = previous_state
try:
while True:
Expand All @@ -76,25 +79,32 @@ def on_exit(led_strip):
print("[RUNNER] Received KILL message")
on_exit(strip)
return
elif msg_split[0] in MODES:
effect_settings = all_settings[msg_split[0]] # dict containing 'enabled', 'effect', 'color', 'delay'/'base'
if 'progress' in msg:
value = msg_split[1]
EFFECTS[msg_split[0]](strip, queue, int(value), hex_to_rgb(effect_settings['color']),
hex_to_rgb(effect_settings['base']), all_settings['strip']['led_brightness'])
else:
EFFECTS[effect_settings['effect']](strip, queue, hex_to_rgb(effect_settings['color']),
effect_settings['delay'], all_settings['strip']['led_brightness'])
else:
time.sleep(0.1)
else:
if check_times(start_time, end_time) and msg_split[0] in MODES:
effect_settings = all_settings[msg_split[0]] # dict containing 'enabled', 'effect', 'color', 'delay'/'base'
if 'progress' in msg:
value = msg_split[1]
EFFECTS[msg_split[0]](strip, queue, int(value), hex_to_rgb(effect_settings['color']),
hex_to_rgb(effect_settings['base']), all_settings['strip']['led_brightness'])
else:
EFFECTS[effect_settings['effect']](strip, queue, hex_to_rgb(effect_settings['color']),
effect_settings['delay'], all_settings['strip']['led_brightness'])

elif not check_times(start_time, end_time):
EFFECTS['solid'](strip, queue, [0, 0, 0])
time.sleep(0.1)
else:
time.sleep(0.1)
elif check_times(start_time, end_time):
effect_settings = all_settings['startup']
if effect_settings['enabled']:
# Run startup effect (We haven't got a message yet)
EFFECTS[effect_settings['effect']](strip, queue, hex_to_rgb(effect_settings['color']),
effect_settings['delay'], all_settings['strip']['led_brightness'])
if not queue.empty():
time.sleep(0.1)
else:
time.sleep(0.1)
except KeyboardInterrupt:
on_exit(strip)
return
Expand All @@ -121,6 +131,21 @@ def start_strip(logger, strip_settings):
return None


def check_times(start_time, end_time):
"""
Return true if time is after start, before end, false if outside
Times should be list, format [hh,mm]
"""
if not start_time or not end_time:
return True
current_time = time.ctime(time.time()).split()[3].split(":")
ct_mins = (int(current_time[0]) * 60) + int(current_time[1])
st_mins = (int(start_time[0]) * 60) + int(start_time[1])
et_mins = (int(end_time[0]) * 60) + int(end_time[1])

return st_mins <= ct_mins < et_mins


class FakeStrip:
def __init__(self, num, pin, freq_hz, dma, invert, brightness, channel, strip_type):
self.pixels = num
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
<p>Got a great feature in mind? I want to hear it! I'm open to feature requests, if you want to contribute have a look over <a href="https://github.com/cp2004/OctoPrint-WS281x_LED_Status">on the repository</a> for details</p>
<p>Charlie</p>
<br>
<p><i>Version 0.1.2</i></p>
<p><i>Version 0.2.0</i></p>
13 changes: 13 additions & 0 deletions octoprint_ws281x_led_status/templates/settings_overview_tab.jinja2
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
<div class=""> <!-- TODO Some Custom CSS? Looks good as is but maybe could be better -->
<div data-bind="visible: settings.plugins.ws281x_led_status.active_hours_enabled()">
<span class="fa-lg">
<span class="fa fa-clock-o" style="color: limegreen"></span>
<span>Active time: <span data-bind="text: settings.plugins.ws281x_led_status.active_hours_start"></span> - <span data-bind="text: settings.plugins.ws281x_led_status.active_hours_stop"></span></span>
</span>
</div>
<div data-bind="visible: !settings.plugins.ws281x_led_status.active_hours_enabled()">
<span class="fa-lg">
<span class="fa fa-clock-o" style="color: darkred"></span>
<span>Active times disabled</span>
</span>
</div>
<h4>Enabled effects</h4>
<div data-bind="visible: settings.plugins.ws281x_led_status.idle_enabled()">
<span class="fa-lg" title="Idle effect enabled">
Expand Down Expand Up @@ -89,3 +101,4 @@
</span>
</div>
</div>

14 changes: 14 additions & 0 deletions octoprint_ws281x_led_status/templates/settings_time_tab.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<h5>Timer configuration</h5>
<form class="form-horizontal">
<label class="checkbox inline">
<input type="checkbox" data-bind="checked: settings.plugins.ws281x_led_status.active_hours_enabled">Enable active times
</label>
<div class="form-inline" data-bind="visible: settings.plugins.ws281x_led_status.active_hours_enabled">
<label class="inline"> Start time </label>
<input type="time" class="input-small" data-bind="value: settings.plugins.ws281x_led_status.active_hours_start">
<label class="inline"> End time </label>
<input type="time" class="input-small" data-bind="value: settings.plugins.ws281x_led_status.active_hours_stop">
</div>
<p class="help-block">The LED strip will turn on at the start time, off at the end time. Potentially useful if you don't want them on overnight.</p>
<p class="help-block">Note this currently doesn't support the start time being later than end time(eg. 19:00 to 13:00), your LEDs will always be off.</p>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<li class="active"><a href="#tabOverview" data-toggle="tab">Overview</a></li>
<li><a href="#tabPrinting" data-toggle="tab">Printing effects</a></li>
<li><a href="#tabProgress" data-toggle="tab">Progress effects</a></li>
<li><a href="#tabTimer" data-toggle="tab">Timer</a></li>
<li><a href="#tabAbout" data-toggle="tab">About</a></li>
</ul>
<div class="tab-content">
Expand All @@ -19,6 +20,9 @@
<div class="tab-pane" id="tabProgress">
{% include 'settings_progress_effects_tab.jinja2' %}
</div>
<div class="tab-pane" id="tabTimer">
{% include 'settings_time_tab.jinja2' %}
</div>
<div class="tab-pane" id="tabAbout">
{% include 'settings_about_tab.jinja2' %}
</div>
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
plugin_name = "OctoPrint-WS281x LED Status"

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "0.1.2"
plugin_version = "0.2.0"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
Expand Down

0 comments on commit 8d022c8

Please sign in to comment.