Skip to content

Commit

Permalink
feat: Run ShakeTune as an in-process Klipper module
Browse files Browse the repository at this point in the history
  • Loading branch information
ozelentok committed May 5, 2024
1 parent ab56008 commit f7609c9
Show file tree
Hide file tree
Showing 20 changed files with 107 additions and 63 deletions.
2 changes: 1 addition & 1 deletion K-ShakeTune/K-SnT_axes_map.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ gcode:
ACCELEROMETER_MEASURE CHIP={accel_chip} NAME=axemap

RESPOND MSG="Analysis of the movements..."
RUN_SHELL_COMMAND CMD=shaketune PARAMS="--type axesmap --accel {accel|int} --chip_name {accel_chip}"
SHAKETUNE_POSTPROCESS PARAMS="--type axesmap --accel {accel|int} --chip_name {accel_chip}"

# Restore the previous acceleration values
SET_VELOCITY_LIMIT ACCEL={old_accel} MINIMUM_CRUISE_RATIO={old_cruise_ratio} SQUARE_CORNER_VELOCITY={old_sqv}
Expand Down
4 changes: 2 additions & 2 deletions K-ShakeTune/K-SnT_axis.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ gcode:

RESPOND MSG="X axis frequency profile generation..."
RESPOND MSG="This may take some time (1-3min)"
RUN_SHELL_COMMAND CMD=shaketune PARAMS="--type shaper --scv {scv} {% if max_sm is not none %}--max_smoothing {max_sm}{% endif %} {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
SHAKETUNE_POSTPROCESS PARAMS="--type shaper --scv {scv} {% if max_sm is not none %}--max_smoothing {max_sm}{% endif %} {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
{% endif %}

{% if Y %}
Expand All @@ -50,5 +50,5 @@ gcode:

RESPOND MSG="Y axis frequency profile generation..."
RESPOND MSG="This may take some time (1-3min)"
RUN_SHELL_COMMAND CMD=shaketune PARAMS="--type shaper --scv {scv} {% if max_sm is not none %}--max_smoothing {max_sm}{% endif %} {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
SHAKETUNE_POSTPROCESS PARAMS="--type shaper --scv {scv} {% if max_sm is not none %}--max_smoothing {max_sm}{% endif %} {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
{% endif %}
2 changes: 1 addition & 1 deletion K-ShakeTune/K-SnT_belts.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ gcode:

RESPOND MSG="Belts comparative frequency profile generation..."
RESPOND MSG="This may take some time (3-5min)"
RUN_SHELL_COMMAND CMD=shaketune PARAMS="--type belts {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
SHAKETUNE_POSTPROCESS PARAMS="--type belts {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
2 changes: 1 addition & 1 deletion K-ShakeTune/K-SnT_vibrations.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,6 @@ gcode:

RESPOND MSG="Machine vibrations profile generation..."
RESPOND MSG="This may take some time (3-5min)"
RUN_SHELL_COMMAND CMD=shaketune PARAMS="--type vibrations --accel {accel|int} --kinematics {kinematics} {% if metadata %}--metadata {metadata}{% endif %} --chip_name {accel_chip} {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"
SHAKETUNE_POSTPROCESS PARAMS="--type vibrations --accel {accel|int} --kinematics {kinematics} {% if metadata %}--metadata {metadata}{% endif %} --chip_name {accel_chip} {% if keep_csv %}--keep_csv{% endif %} --keep_results {keep_results}"

RESTORE_GCODE_STATE NAME=CREATE_VIBRATIONS_PROFILE
6 changes: 0 additions & 6 deletions K-ShakeTune/shaketune_cmd.cfg

This file was deleted.

3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ Check out the **[detailed documentation of the Shake&Tune module here](./docs/RE
|:----------------:|:------------:|:---------------------:|
| [<img src="./docs/images/belts_example.png">](./docs/macros/belts_tuning.md) | [<img src="./docs/images/axis_example.png">](./docs/macros/axis_tuning.md) | [<img src="./docs/images/vibrations_example.png">](./docs/macros/vibrations_profile.md) |

> **Note**:
>
> Be aware that Shake&Tune uses the [Gcode shell command plugin](https://github.com/dw-0/kiauh/blob/master/docs/gcode_shell_command.md) under the hood to call the Python scripts that generate the graphs. While my scripts should be safe, the Gcode shell command plugin also has great potential for abuse if not used carefully for other purposes, since it opens shell access from Klipper.

## Installation

Expand Down
33 changes: 8 additions & 25 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ MOONRAKER_CONFIG="${HOME}/printer_data/config/moonraker.conf"
KLIPPER_PATH="${HOME}/klipper"

K_SHAKETUNE_PATH="${HOME}/klippain_shaketune"
K_SHAKETUNE_VENV_PATH="${HOME}/klippain_shaketune-env"

set -eu
export LC_ALL=C
Expand Down Expand Up @@ -39,7 +38,7 @@ function is_package_installed {
}

function install_package_requirements {
packages=("python3-venv" "libopenblas-dev" "libatlas-base-dev")
packages=("libopenblas-dev" "libatlas-base-dev")
packages_to_install=""

for package in "${packages[@]}"; do
Expand Down Expand Up @@ -75,39 +74,24 @@ function check_download {
fi
}

function setup_venv {
if [ ! -d "${K_SHAKETUNE_VENV_PATH}" ]; then
echo "[SETUP] Creating Python virtual environment..."
python3 -m venv "${K_SHAKETUNE_VENV_PATH}"
else
echo "[SETUP] Virtual environment already exists. Continuing..."
fi

source "${K_SHAKETUNE_VENV_PATH}/bin/activate"
echo "[SETUP] Installing/Updating K-Shake&Tune dependencies..."
pip install --upgrade pip
pip install -r "${K_SHAKETUNE_PATH}/requirements.txt"
deactivate
printf "\n"
}

function link_extension {
echo "[INSTALL] Linking scripts to your config directory..."

if [ -d "${HOME}/klippain_config" ] && [ -f "${USER_CONFIG_PATH}/.VERSION" ]; then
echo "[INSTALL] Klippain full installation found! Linking module to the script folder of Klippain"
ln -frsn ${K_SHAKETUNE_PATH}/K-ShakeTune ${USER_CONFIG_PATH}/scripts/K-ShakeTune
else
echo "[INSTALL] Klippain not found! Linking module to the config folder of Klipper"
ln -frsn ${K_SHAKETUNE_PATH}/K-ShakeTune ${USER_CONFIG_PATH}/K-ShakeTune
fi
}

function link_gcodeshellcommandpy {
if [ ! -f "${KLIPPER_PATH}/klippy/extras/gcode_shell_command.py" ]; then
echo "[INSTALL] Downloading gcode_shell_command.py Klipper extension needed for this module"
wget -P ${KLIPPER_PATH}/klippy/extras https://raw.githubusercontent.com/Frix-x/klippain/main/scripts/gcode_shell_command.py
function link_module {
if [ ! -d "${KLIPPER_PATH}/klippy/extras/shaketune" ]; then
echo "[INSTALL] Linking Shake&Tune module to Klipper extras"
ln -frsn ${K_SHAKETUNE_PATH}/src ${KLIPPER_PATH}/klippy/extras/shaketune
else
printf "[INSTALL] gcode_shell_command.py Klipper extension is already installed. Continuing...\n\n"
printf "[INSTALL] Klippain Shake&Tune Klipper module is already installed. Continuing...\n\n"
fi
}

Expand Down Expand Up @@ -138,9 +122,8 @@ printf "=============================================\n\n"
# Run steps
preflight_checks
check_download
setup_venv
link_extension
link_module
add_updater
link_gcodeshellcommandpy
restart_klipper
restart_moonraker
90 changes: 68 additions & 22 deletions src/is_workflow.py → shaketune/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,30 @@
############################################
# Written by Frix_x#0161 #

# This script is designed to be used with gcode_shell_commands directly from Klipper
# This script is designed to be run from inside Klipper Console
# Use the provided Shake&Tune macros instead!


import abc
import argparse
import os
import shutil
import tarfile
import threading
import traceback
from datetime import datetime
from pathlib import Path
from typing import Callable, Optional
from typing import Callable, List, Optional

from git import GitCommandError, Repo
from matplotlib.figure import Figure

import src.helpers.filemanager as fm
from src.graph_creators.analyze_axesmap import axesmap_calibration
from src.graph_creators.graph_belts import belts_calibration
from src.graph_creators.graph_shaper import shaper_calibration
from src.graph_creators.graph_vibrations import vibrations_profile
from src.helpers.locale_utils import print_with_c_locale
from src.helpers.motorlogparser import MotorLogParser
from .graph_creators.analyze_axesmap import axesmap_calibration
from .graph_creators.graph_belts import belts_calibration
from .graph_creators.graph_shaper import shaper_calibration
from .graph_creators.graph_vibrations import vibrations_profile
from .helpers import filemanager as fm
from .helpers.locale_utils import print_with_c_locale, set_shaketune_output_func
from .helpers.motorlogparser import MotorLogParser


class Config:
Expand All @@ -43,6 +44,8 @@ def get_results_folder(type: str) -> Path:
@staticmethod
def get_git_version() -> str:
try:
from git import GitCommandError, Repo

# Get the absolute path of the script, resolving any symlinks
# Then get 1 times to parent dir to be at the git root folder
script_path = Path(__file__).resolve()
Expand All @@ -58,7 +61,7 @@ def get_git_version() -> str:
return 'unknown'

@staticmethod
def parse_arguments() -> argparse.Namespace:
def parse_arguments(params: Optional[List] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description='Shake&Tune graphs generation script')
parser.add_argument(
'-t',
Expand Down Expand Up @@ -131,7 +134,7 @@ def parse_arguments() -> argparse.Namespace:
parser.add_argument('--dpi', type=int, default=150, dest='dpi', help='DPI of the output PNG files')
parser.add_argument('-v', '--version', action='version', version=f'Shake&Tune {Config.get_git_version()}')

return parser.parse_args()
return parser.parse_args(params)


class GraphCreator(abc.ABC):
Expand Down Expand Up @@ -341,16 +344,21 @@ def clean_old_files(self, keep_results: int = 3) -> None:
tar_file.unlink(missing_ok=True)


class AxesMapFinder:
def __init__(self, accel: float, chip_name: str):
self._accel = accel
self._chip_name = chip_name
class AxesMapFinder(GraphCreator):
def __init__(self, keep_csv: bool = False, dpi: int = 150):
super().__init__(keep_csv, dpi)

self._graph_date = datetime.now().strftime('%Y%m%d_%H%M%S')

self._type = 'axesmap'
self._folder = Config.RESULTS_BASE_FOLDER

self._accel = None
self._chip_name = None

def configure(self, accel: int, chip_name: str) -> None:
self._accel = accel
self._chip_name = chip_name

def find_axesmap(self) -> None:
tmp_folder = Path('/tmp')
globbed_files = list(tmp_folder.glob(f'{self._chip_name}-*.csv'))
Expand All @@ -371,9 +379,16 @@ def find_axesmap(self) -> None:
with result_filename.open('w') as f:
f.write(results)

print_with_c_locale(f'Detected axes_map: {results}')

def main():
options = Config.parse_arguments()
def create_graph(self) -> None:
self.find_axesmap()

def clean_old_files(self, keep_results: int) -> None:
pass


def create_graph(options: argparse.Namespace) -> None:
fm.ensure_folders_exist(
folders=[Config.RESULTS_BASE_FOLDER / subfolder for subfolder in Config.RESULTS_SUBFOLDERS.values()]
)
Expand All @@ -387,7 +402,7 @@ def main():
VibrationsGraphCreator,
lambda gc: gc.configure(options.kinematics, options.accel_used, options.chip_name, options.metadata),
),
'axesmap': (AxesMapFinder, None),
'axesmap': (AxesMapFinder, lambda gc: gc.configure(options.accel_used, options.chip_name)),
}

creator_info = graph_creators.get(options.type)
Expand Down Expand Up @@ -422,5 +437,36 @@ def main():
print_with_c_locale(f'Cleaned output folder to keep only the last {options.keep_results} results!')


if __name__ == '__main__':
main()
class ShakeTune:
def __init__(self, config) -> None:
self._printer = config.get_printer()
self._gcode = self._printer.lookup_object('gcode')
self.timeout = config.getfloat('timeout', 2.0, above=0.0)

self._gcode.register_command(
'SHAKETUNE_POSTPROCESS',
self.cmd_SHAKETUNE_POSTPROCESS,
desc='PostProcess ShakeTune data for graph creation',
)

def shaketune_thread(self, options):
set_shaketune_output_func(self._gcode.respond_info)
os.nice(20)
create_graph(options)

def cmd_SHAKETUNE_POSTPROCESS(self, gcmd) -> None:
options = Config.parse_arguments(gcmd.get('PARAMS').split())
t = threading.Thread(target=self.shaketune_thread, args=(options,))
t.start()

reactor = self._printer.get_reactor()
event_time = reactor.monotonic()
end_time = event_time + self.timeout
while event_time < end_time:
event_time = reactor.pause(event_time + 0.05)
if not t.is_alive():
break


def load_config(config) -> ShakeTune:
return ShakeTune(config)
10 changes: 10 additions & 0 deletions shaketune/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from . import Config, create_graph


def main() -> None:
options = Config.parse_arguments()
create_graph(options)


if __name__ == '__main__':
main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from pathlib import Path

import numpy as np
from git import GitCommandError, Repo
from scipy.signal import spectrogram


Expand Down Expand Up @@ -69,6 +68,7 @@ def get_git_version():
try:
# Get the absolute path of the script, resolving any symlinks
# Then get 2 times to parent dir to be at the git root folder
from git import GitCommandError, Repo
script_path = Path(__file__).resolve()
repo_path = script_path.parents[1]
repo = Repo(repo_path)
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
# Written by Frix_x#0161 #


import io
import locale
from typing import Callable, Optional

KLIPPER_CONSOLE_OUTPUT_FUNC: Optional[Callable[[str], None]] = None


def set_shaketune_output_func(func: Callable[[str], None]):
global KLIPPER_CONSOLE_OUTPUT_FUNC
KLIPPER_CONSOLE_OUTPUT_FUNC = func


# Set the best locale for time and date formating (generation of the titles)
Expand All @@ -27,7 +36,12 @@ def print_with_c_locale(*args, **kwargs):
'Warning: Failed to set a basic locale. Special characters may not display correctly in Klipper console:', e
)
finally:
print(*args, **kwargs) # Proceed with printing regardless of locale setting success
if not KLIPPER_CONSOLE_OUTPUT_FUNC:
print(*args, **kwargs) # Proceed with printing regardless of locale setting success
else:
with io.StringIO() as mem_output:
print(*args, file=mem_output, **kwargs)
KLIPPER_CONSOLE_OUTPUT_FUNC(mem_output.getvalue())
try:
locale.setlocale(locale.LC_ALL, original_locale)
except locale.Error as e:
Expand Down
File renamed without changes.

0 comments on commit f7609c9

Please sign in to comment.