First commit

This commit is contained in:
Cyril 2024-03-10 21:45:05 +01:00
commit 7693c29676
102 changed files with 11831 additions and 0 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
ko_fi: guilouz

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.DS_Store
desktop.ini

674
LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

9
README.md Normal file
View file

@ -0,0 +1,9 @@
# Creality Helper Script
<img width="1102" src="https://github.com/Guilouz/Creality-K1-Series/blob/main/docs/assets/img/home/home.png">
## Wiki
Guide is to use it is available here: [Wiki](https://guilouz.github.io/Creality-K1-Series/)
<br />

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,14 @@
########################################
# Buzzer Support
########################################
[gcode_shell_command beep]
command: aplay /usr/data/helper-script/files/buzzer-support/beep.mp3
timeout: 2
verbose: False
[gcode_macro BEEP]
gcode:
RUN_SHELL_COMMAND CMD=beep
RUN_SHELL_COMMAND CMD=beep
RUN_SHELL_COMMAND CMD=beep

View file

@ -0,0 +1,115 @@
########################################
# Camera Settings Control
########################################
[delayed_gcode LOAD_CAM_SETTINGS]
initial_duration: 2
gcode:
CAM_BRIGHTNESS BRIGHTNESS=0
CAM_CONTRAST CONTRAST=32
CAM_SATURATION SATURATION=56
CAM_HUE HUE=0
CAM_WHITE_BALANCE_TEMPERATURE_AUTO WHITE_BALANCE_TEMPERATURE_AUTO=1
CAM_GAMMA GAMMA=80
CAM_GAIN GAIN=0
CAM_POWER_LINE_FREQUENCY POWER_LINE_FREQUENCY=1
CAM_WHITE_BALANCE_TEMPERATURE WHITE_BALANCE_TEMPERATURE=4600
CAM_SHARPNESS SHARPNESS=3
CAM_BACKLIGHT_COMPENSATION BACKLIGHT_COMPENSATION=1
CAM_EXPOSURE_AUTO EXPOSURE_AUTO=3
CAM_EXPOSURE_AUTO_PRIORITY EXPOSURE_AUTO_PRIORITY=0
CAM_AUTO_FOCUS FOCUS_AUTO=0
[gcode_shell_command v4l2-ctl]
command: v4l2-ctl
timeout: 5.0
verbose: True
[gcode_macro CAM_SETTINGS]
gcode:
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 -l"
[gcode_macro CAM_BRIGHTNESS]
description: min=-64 / max=64
gcode:
{% set brightness = params.BRIGHTNESS|default(0) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl brightness="{brightness}
[gcode_macro CAM_CONTRAST]
description: min=0 / max=64
gcode:
{% set contrast = params.CONTRAST|default(32) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl contrast="{contrast}
[gcode_macro CAM_SATURATION]
description: min=0 / max=128
gcode:
{% set saturation = params.SATURATION|default(56) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl saturation="{saturation}
[gcode_macro CAM_HUE]
description: min=-40 / max=40
gcode:
{% set hue = params.HUE|default(0) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl hue="{hue}
[gcode_macro CAM_WHITE_BALANCE_TEMPERATURE_AUTO]
description: disable=0 / enable=1
gcode:
{% set white_balance_temperature_auto = params.WHITE_BALANCE_TEMPERATURE_AUTO|default(1) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl white_balance_temperature_auto="{white_balance_temperature_auto}
[gcode_macro CAM_GAMMA]
description: min=72 / max=500
gcode:
{% set gamma = params.GAMMA|default(80) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl gamma="{gamma}
[gcode_macro CAM_GAIN]
description: min=0 / max=100
gcode:
{% set gain = params.GAIN|default(0) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl gain="{gain}
[gcode_macro CAM_POWER_LINE_FREQUENCY]
description: min=0 / max=2
gcode:
{% set power_line_frequency = params.POWER_LINE_FREQUENCY|default(1) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl power_line_frequency="{power_line_frequency}
[gcode_macro CAM_WHITE_BALANCE_TEMPERATURE]
description: min=2800 / max=6500
gcode:
{% set white_balance_temperature = params.WHITE_BALANCE_TEMPERATURE|default(4600) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl white_balance_temperature="{white_balance_temperature}
[gcode_macro CAM_SHARPNESS]
description: min=0 / max=6
gcode:
{% set sharpness = params.SHARPNESS|default(3) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl sharpness="{sharpness}
[gcode_macro CAM_BACKLIGHT_COMPENSATION]
description: min=0 / max=2
gcode:
{% set backlight_compensation = params.BACKLIGHT_COMPENSATION|default(1) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl backlight_compensation="{backlight_compensation}
[gcode_macro CAM_EXPOSURE_AUTO]
description: manual=1 / auto=3
gcode:
{% set exposure_auto = params.EXPOSURE_AUTO|default(3) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl exposure_auto="{exposure_auto}
[gcode_macro CAM_EXPOSURE_AUTO_PRIORITY]
description: disable=0 / enable=1
gcode:
{% set exposure_auto_priority = params.EXPOSURE_AUTO_PRIORITY|default(0) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl exposure_auto_priority="{exposure_auto_priority}
[gcode_macro CAM_AUTO_FOCUS]
description: disable=0 / enable=1
gcode:
{% set focus_auto = params.AUTO_FOCUS|default(0) %}
RUN_SHELL_COMMAND CMD=v4l2-ctl PARAMS="-d /dev/video4 --set-ctrl focus_auto="{focus_auto}

58
files/entware/generic.sh Executable file
View file

@ -0,0 +1,58 @@
#!/bin/sh
unset LD_LIBRARY_PATH
unset LD_PRELOAD
LOADER=ld.so.1
GLIBC=2.27
echo -e "Info: Removing old directories..."
rm -rf /opt
rm -rf /usr/data/opt
echo -e "Info: Creating directory..."
mkdir -p /usr/data/opt
echo -e "Info: Linking folder..."
ln -nsf /usr/data/opt /opt
echo -e "Info: Creating subdirectories..."
for folder in bin etc lib/opkg tmp var/lock
do
mkdir -p /usr/data/opt/$folder
done
echo -e "Info: Downloading opkg package manager..."
chmod 755 /usr/data/helper-script/files/fixes/curl
URL="http://www.openk1.org/static/entware/mipselsf-k3.4/installer"
/usr/data/helper-script/files/fixes/curl -L "$URL/opkg" -o "/opt/bin/opkg"
/usr/data/helper-script/files/fixes/curl -L "$URL/opkg.conf" -o "/opt/etc/opkg.conf"
echo -e "Info: Applying permissions..."
chmod 755 /opt/bin/opkg
chmod 777 /opt/tmp
echo -e "Info: Installing basic packages..."
/opt/bin/opkg update
/opt/bin/opkg install entware-opt
echo -e "Info: Installing SFTP server support..."
/opt/bin/opkg install openssh-sftp-server; ln -s /opt/libexec/sftp-server /usr/libexec/sftp-server
echo -e "Info: Configuring files..."
for file in passwd group shells shadow gshadow; do
if [ -f /etc/$file ]; then
ln -sf /etc/$file /opt/etc/$file
else
[ -f /opt/etc/$file.1 ] && cp /opt/etc/$file.1 /opt/etc/$file
fi
done
[ -f /etc/localtime ] && ln -sf /etc/localtime /opt/etc/localtime
echo -e "Info: Applying changes in system profile..."
echo 'export PATH="/opt/bin:/opt/sbin:$PATH"' > /etc/profile.d/entware.sh
echo -e "Info: Adding startup script..."
echo '#!/bin/sh\n/opt/etc/init.d/rc.unslung "$1"' > /etc/init.d/S50unslung
chmod 755 /etc/init.d/S50unslung

BIN
files/fixes/curl Executable file

Binary file not shown.

549
files/fixes/gcode.py Normal file
View file

@ -0,0 +1,549 @@
# Parse gcode commands
#
# Copyright (C) 2016-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, re, logging, collections, shlex
from extras.tool import reportInformation
class CommandError(Exception):
pass
Coord = collections.namedtuple('Coord', ('x', 'y', 'z', 'e'))
class GCodeCommand:
error = CommandError
def __init__(self, gcode, command, commandline, params, need_ack):
self._command = command
self._commandline = commandline
self._params = params
self._need_ack = need_ack
# Method wrappers
self.respond_info = gcode.respond_info
self.respond_raw = gcode.respond_raw
def get_command(self):
return self._command
def get_commandline(self):
return self._commandline
def get_command_parameters(self):
return self._params
def get_raw_command_parameters(self):
command = self._command
if command.startswith("M117 ") or command.startswith("M118 "):
command = command[:4]
rawparams = self._commandline
urawparams = rawparams.upper()
if not urawparams.startswith(command):
rawparams = rawparams[urawparams.find(command):]
end = rawparams.rfind('*')
if end >= 0:
rawparams = rawparams[:end]
rawparams = rawparams[len(command):]
if rawparams.startswith(' '):
rawparams = rawparams[1:]
return rawparams
def ack(self, msg=None):
if not self._need_ack:
return False
ok_msg = "ok"
if msg:
ok_msg = "ok %s" % (msg,)
self.respond_raw(ok_msg)
self._need_ack = False
return True
# Parameter parsing helpers
class sentinel: pass
def get(self, name, default=sentinel, parser=str, minval=None, maxval=None,
above=None, below=None):
value = self._params.get(name)
if value is None:
if default is self.sentinel:
raise self.error("""{"code":"key251", "msg":"Error on '%s': missing %s", "values":["%s",%s"]}"""
% (self._commandline, name, self._commandline, name))
return default
try:
value = parser(value)
except:
raise self.error(
"""{"code":"key171", "msg": "Unable to parse '%s' as a %s", "values": ["%s", "%s"]}""" % (self._commandline, value,
self._commandline, value)
)
if minval is not None and value < minval:
raise self.error("""{"code":"key252","msg":"Error on '%s': %s must have minimum of %s","values":["%s","%s","%s"]}"""
% (self._commandline, name, minval, self._commandline, name, minval))
if maxval is not None and value > maxval:
raise self.error("""{"code":"key253", "msg":"Error on '%s': %s must have maximumof %s", "values":["%s","%s","%s"]}"""
% (self._commandline, name, maxval, self._commandline, name, maxval))
if above is not None and value <= above:
raise self.error("""{"code":"key254", "msg":"Error on '%s': %s must be above %s", "values":["%s","%s","%s"]}"""
% (self._commandline, name, above, self._commandline, name, above))
if below is not None and value >= below:
raise self.error("""{"code":"key255", "msg":"Error on '%s': %s must be below %s", "values":["%s","%s","%s"]}"""
% (self._commandline, name, below, self._commandline, name, below))
return value
def get_int(self, name, default=sentinel, minval=None, maxval=None):
return self.get(name, default, parser=int, minval=minval, maxval=maxval)
def get_float(self, name, default=sentinel, minval=None, maxval=None,
above=None, below=None):
return self.get(name, default, parser=float, minval=minval,
maxval=maxval, above=above, below=below)
# Parse and dispatch G-Code commands
class GCodeDispatch:
error = CommandError
Coord = Coord
def __init__(self, printer):
self.printer = printer
self.is_fileinput = not not printer.get_start_args().get("debuginput")
printer.register_event_handler("klippy:ready", self._handle_ready)
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
printer.register_event_handler("klippy:disconnect",
self._handle_disconnect)
# Command handling
self.is_printer_ready = False
self.mutex = printer.get_reactor().mutex()
self.output_callbacks = []
self.base_gcode_handlers = self.gcode_handlers = {}
self.ready_gcode_handlers = {}
self.mux_commands = {}
self.gcode_help = {}
# Register commands needed before config file is loaded
handlers = ['M110', 'M112', 'M115',
'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP']
for cmd in handlers:
func = getattr(self, 'cmd_' + cmd)
desc = getattr(self, 'cmd_' + cmd + '_help', None)
self.register_command(cmd, func, True, desc)
self.last_temperature_info = "/usr/data/creality/userdata/config/temperature_info.json"
self.exclude_object_info = "/usr/data/creality/userdata/config/exclude_object_info.json"
def is_traditional_gcode(self, cmd):
# A "traditional" g-code command is a letter and followed by a number
try:
cmd = cmd.upper().split()[0]
val = float(cmd[1:])
return cmd[0].isupper() and cmd[1].isdigit()
except:
return False
def register_command(self, cmd, func, when_not_ready=False, desc=None):
if func is None:
old_cmd = self.ready_gcode_handlers.get(cmd)
if cmd in self.ready_gcode_handlers:
del self.ready_gcode_handlers[cmd]
if cmd in self.base_gcode_handlers:
del self.base_gcode_handlers[cmd]
return old_cmd
if cmd in self.ready_gcode_handlers:
raise self.printer.config_error(
"""{"code":"key57", "msg":"gcode command %s already registered", "values": ["%s"]}""" % (cmd, cmd))
if not self.is_traditional_gcode(cmd):
origfunc = func
func = lambda params: origfunc(self._get_extended_params(params))
self.ready_gcode_handlers[cmd] = func
if when_not_ready:
self.base_gcode_handlers[cmd] = func
if desc is not None:
self.gcode_help[cmd] = desc
def register_mux_command(self, cmd, key, value, func, desc=None):
prev = self.mux_commands.get(cmd)
if prev is None:
handler = lambda gcmd: self._cmd_mux(cmd, gcmd)
self.register_command(cmd, handler, desc=desc)
self.mux_commands[cmd] = prev = (key, {})
prev_key, prev_values = prev
if prev_key != key:
raise self.printer.config_error(
"""{"code":"key58", "msg":"mux command %s %s %s may have only one key (%s)", "values": ["%s", "%s", "%s", "%s"]}""" % (
cmd, key, value, prev_key, cmd, key, value, prev_key))
if value in prev_values:
raise self.printer.config_error(
"""{"code":"key59", "msg":"mux command %s %s %s already registered (%s)", "values": ["%s", "%s", "%s", "%s"]}""" % (
cmd, key, value, prev_values, cmd, key, value, prev_values))
prev_values[value] = func
def get_command_help(self):
return dict(self.gcode_help)
def register_output_handler(self, cb):
self.output_callbacks.append(cb)
def _handle_shutdown(self):
if not self.is_printer_ready:
return
self.is_printer_ready = False
self.gcode_handlers = self.base_gcode_handlers
self._respond_state("Shutdown")
def _handle_disconnect(self):
self._respond_state("Disconnect")
def _handle_ready(self):
self.is_printer_ready = True
self.gcode_handlers = self.ready_gcode_handlers
self._respond_state("Ready")
# Parse input into commands
args_r = re.compile('([A-Z_]+|[A-Z*/])')
def _process_commands(self, commands, need_ack=True):
for line in commands:
# Ignore comments and leading/trailing spaces
line = origline = line.strip()
cpos = line.find(';')
if cpos >= 0:
line = line[:cpos]
# Break line into parts and determine command
parts = self.args_r.split(line.upper())
numparts = len(parts)
cmd = ""
if numparts >= 3 and parts[1] != 'N':
cmd = parts[1] + parts[2].strip()
elif numparts >= 5 and parts[1] == 'N':
# Skip line number at start of command
cmd = parts[3] + parts[4].strip()
# Build gcode "params" dictionary
params = { parts[i]: parts[i+1].strip()
for i in range(1, numparts, 2) }
gcmd = GCodeCommand(self, cmd, origline, params, need_ack)
# Invoke handler for command
handler = self.gcode_handlers.get(cmd, self.cmd_default)
try:
handler(gcmd)
except self.error as e:
self._respond_error(str(e))
self.printer.send_event("gcode:command_error")
if not need_ack:
raise
except:
msg = """{"code":"key60", "msg":"Internal error on command:%s", "values": ["%s"]}""" % (cmd, cmd)
logging.exception(msg)
self.printer.invoke_shutdown(msg)
self._respond_error(msg)
if not need_ack:
raise
gcmd.ack()
if line.startswith("G1") or line.startswith("G0"):
pass
elif line.startswith("M104"):
self.set_temperature("extruder", line)
elif line.startswith("M140"):
self.set_temperature("bed", line)
elif line.startswith("M109"):
self.set_temperature("extruder", line)
elif line.startswith("M190"):
self.set_temperature("bed", line)
elif line.startswith("EXCLUDE_OBJECT_DEFINE") or line.startswith("EXCLUDE_OBJECT NAME"):
self.record_exclude_object_info(line)
def set_temperature(self, key, value):
import json
try:
# configfile = self.printer.lookup_object('configfile')
# print_stats = self.printer.load_object(configfile, 'print_stats')
temp_value = float(value.strip("\n").split("S")[-1])
# if key == "extruder" and print_stats and print_stats.state == "printing":
# if temp_value >= 240:
# self.run_script_from_command("M107 P1")
# logging.info("Fan Off SET M107 P1")
# elif temp_value >= 170:
# self.run_script_from_command("M106 P1 S255")
# logging.info("Fan On SET M106 P1 S255")
if key == "extruder" and temp_value < 170:
return
if not os.path.exists(self.last_temperature_info):
from subprocess import call
call("touch %s" % self.last_temperature_info, shell=True)
with open(self.last_temperature_info, "r") as f:
ret = f.read()
if len(ret) > 0:
ret = json.loads(ret)
else:
ret = {}
ret[key] = temp_value
with open(self.last_temperature_info, "w") as f:
f.write(json.dumps(ret))
f.flush()
except Exception as err:
logging.error("set_temperature error: %s" % err)
def record_exclude_object_info(self, line):
import json
try:
if not os.path.exists(self.exclude_object_info):
with open(self.exclude_object_info, "w") as f:
data = {}
data["EXCLUDE_OBJECT_DEFINE"] = []
data["EXCLUDE_OBJECT"] = []
f.write(json.dumps(data))
f.flush()
with open(self.exclude_object_info, "r") as f:
ret = f.read()
if len(ret) > 0:
ret = eval(ret)
else:
ret = {}
if line.startswith("EXCLUDE_OBJECT_DEFINE"):
if line not in ret["EXCLUDE_OBJECT_DEFINE"]:
ret["EXCLUDE_OBJECT_DEFINE"].append(line)
elif line.startswith("EXCLUDE_OBJECT NAME"):
if line not in ret["EXCLUDE_OBJECT"]:
ret["EXCLUDE_OBJECT"].append(line)
with open(self.exclude_object_info, "w") as f:
f.write(json.dumps(ret))
f.flush()
except Exception as err:
logging.error("record_exclude_object_info error: %s" % err)
def run_script_from_command(self, script):
self._process_commands(script.split('\n'), need_ack=False)
def run_script(self, script):
with self.mutex:
self._process_commands(script.split('\n'), need_ack=False)
def get_mutex(self):
return self.mutex
def create_gcode_command(self, command, commandline, params):
return GCodeCommand(self, command, commandline, params, False)
# Response handling
def respond_raw(self, msg):
for cb in self.output_callbacks:
cb(msg)
def respond_info(self, msg, log=True):
if log:
logging.info(msg)
lines = [l.strip() for l in msg.strip().split('\n')]
self.respond_raw("// " + "\n// ".join(lines))
def _respond_error(self, msg):
from extras.tool import reportInformation
try:
v_sd = self.printer.lookup_object('virtual_sdcard')
if v_sd.print_id and "key" in msg and re.findall('key(\d+)', msg) and v_sd.cur_print_data:
v_sd.update_print_history_info(only_update_status=True, state="error", error_msg=eval(msg))
v_sd.print_id = ""
reportInformation("key701", data=v_sd.cur_print_data)
v_sd.cur_print_data = {}
except Exception as err:
logging.error(err)
try:
if "key" in msg and re.findall('key(\d+)', msg):
reportInformation(msg)
except Exception as err:
logging.error(err)
logging.warning(msg)
lines = msg.strip().split('\n')
if len(lines) > 1:
self.respond_info("\n".join(lines), log=False)
self.respond_raw('!! %s' % (lines[0].strip(),))
if self.is_fileinput:
self.printer.request_exit('error_exit')
def _respond_state(self, state):
self.respond_info("Klipper state: %s" % (state,), log=False)
# Parameter parsing helpers
extended_r = re.compile(
r'^\s*(?:N[0-9]+\s*)?'
r'(?P<cmd>[a-zA-Z_][a-zA-Z0-9_]+)(?:\s+|$)'
r'(?P<args>[^*;]*?)'
r'\s*(?:[#*;].*)?$')
def _get_extended_params(self, gcmd):
m = self.extended_r.match(gcmd.get_commandline())
if m is None:
raise self.error("""{"code":"key513", "msg": "Malformed command '%s'", "values": ["%s"]}""" % (gcmd.get_commandline(), gcmd.get_commandline()))
eargs = m.group('args')
try:
eparams = [earg.split('=', 1) for earg in shlex.split(eargs)]
eparams = { k.upper(): v for k, v in eparams }
gcmd._params.clear()
gcmd._params.update(eparams)
return gcmd
except ValueError as e:
raise self.error("""{"code":"key514", "msg": "Malformed command args '%s'", "values": ["%s"]}""" % (gcmd.get_commandline(), str(e)))
# G-Code special command handlers
def cmd_default(self, gcmd):
cmd = gcmd.get_command()
if cmd == 'M105':
# Don't warn about temperature requests when not ready
gcmd.ack("T:0")
return
if cmd == 'M21':
# Don't warn about sd card init when not ready
return
if not self.is_printer_ready:
raise gcmd.error(self.printer.get_state_message()[0])
return
if not cmd:
cmdline = gcmd.get_commandline()
if cmdline:
logging.debug(cmdline)
return
if cmd.startswith("M117 ") or cmd.startswith("M118 "):
# Handle M117/M118 gcode with numeric and special characters
handler = self.gcode_handlers.get(cmd[:4], None)
if handler is not None:
handler(gcmd)
return
elif cmd in ['M140', 'M104'] and not gcmd.get_float('S', 0.):
# Don't warn about requests to turn off heaters when not present
return
elif cmd == 'M107' or (cmd == 'M106' and (
not gcmd.get_float('S', 1.) or self.is_fileinput)):
# Don't warn about requests to turn off fan when fan not present
return
gcmd.respond_info("""{"code":"key61, "msg":"Unknown command:%s", "values": ["%s"]}""" % (cmd, cmd))
def get_muxcmd(self, cmdkey):
if cmdkey in self.mux_commands:
key, values = self.mux_commands[cmdkey]
return values
return None
def _cmd_mux(self, command, gcmd):
key, values = self.mux_commands[command]
if None in values:
key_param = gcmd.get(key, None)
else:
key_param = gcmd.get(key)
if key_param not in values:
raise gcmd.error("""{"code":"key69", "msg": "The value '%s' is not valid for %s", "values": ["%s", "%s"]}"""
% (key_param, key, key_param, key))
values[key_param](gcmd)
# Low-level G-Code commands that are needed before the config file is loaded
def cmd_M110(self, gcmd):
# Set Current Line Number
pass
def cmd_M112(self, gcmd):
# Emergency Stop
self.printer.invoke_shutdown("""{"code":"key70", "msg": "Shutdown due to M112 command", "values": []}""")
def cmd_M115(self, gcmd):
# Get Firmware Version and Capabilities
software_version = self.printer.get_start_args().get('software_version')
kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version}
msg = " ".join(["%s:%s" % (k, v) for k, v in kw.items()])
did_ack = gcmd.ack(msg)
if not did_ack:
gcmd.respond_info(msg)
def request_restart(self, result):
if self.is_printer_ready:
toolhead = self.printer.lookup_object('toolhead')
print_time = toolhead.get_last_move_time()
if result == 'exit':
logging.info("Exiting (print time %.3fs)" % (print_time,))
self.printer.send_event("gcode:request_restart", print_time)
toolhead.dwell(0.500)
toolhead.wait_moves()
self.printer.request_exit(result)
cmd_RESTART_help = "Reload config file and restart host software"
def cmd_RESTART(self, gcmd):
self.request_restart('restart')
cmd_FIRMWARE_RESTART_help = "Restart firmware, host, and reload config"
def cmd_FIRMWARE_RESTART(self, gcmd):
self.request_restart('firmware_restart')
def cmd_ECHO(self, gcmd):
gcmd.respond_info(gcmd.get_commandline(), log=False)
cmd_STATUS_help = "Report the printer status"
def cmd_STATUS(self, gcmd):
if self.is_printer_ready:
self._respond_state("Ready")
return
msg = self.printer.get_state_message()[0]
msg = msg.rstrip() + "\nKlipper state: Not ready"
raise gcmd.error(msg)
cmd_HELP_help = "Report the list of available extended G-Code commands"
def cmd_HELP(self, gcmd):
cmdhelp = []
if not self.is_printer_ready:
cmdhelp.append("""{"code":"key72", "msg": "Printer is not ready - not all commands available.\n""")
cmdhelp.append("Available extended commands:")
for cmd in sorted(self.gcode_handlers):
if cmd in self.gcode_help:
cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd]))
gcmd.respond_info("\n".join(cmdhelp), log=False)
# Support reading gcode from a pseudo-tty interface
class GCodeIO:
def __init__(self, printer):
self.printer = printer
printer.register_event_handler("klippy:ready", self._handle_ready)
printer.register_event_handler("klippy:shutdown", self._handle_shutdown)
self.gcode = printer.lookup_object('gcode')
self.gcode_mutex = self.gcode.get_mutex()
self.fd = printer.get_start_args().get("gcode_fd")
self.reactor = printer.get_reactor()
self.is_printer_ready = False
self.is_processing_data = False
self.is_fileinput = not not printer.get_start_args().get("debuginput")
self.pipe_is_active = True
self.fd_handle = None
if not self.is_fileinput:
self.gcode.register_output_handler(self._respond_raw)
self.fd_handle = self.reactor.register_fd(self.fd,
self._process_data)
self.partial_input = ""
self.pending_commands = []
self.bytes_read = 0
self.input_log = collections.deque([], 50)
def _handle_ready(self):
self.is_printer_ready = True
if self.is_fileinput and self.fd_handle is None:
self.fd_handle = self.reactor.register_fd(self.fd,
self._process_data)
def _dump_debug(self):
out = []
out.append("Dumping gcode input %d blocks" % (len(self.input_log),))
for eventtime, data in self.input_log:
out.append("Read %f: %s" % (eventtime, repr(data)))
logging.info("\n".join(out))
def _handle_shutdown(self):
if not self.is_printer_ready:
return
self.is_printer_ready = False
self._dump_debug()
if self.is_fileinput:
self.printer.request_exit('error_exit')
m112_r = re.compile('^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)')
def _process_data(self, eventtime):
# Read input, separate by newline, and add to pending_commands
try:
data = str(os.read(self.fd, 4096).decode())
except (os.error, UnicodeDecodeError):
logging.exception("Read g-code")
return
self.input_log.append((eventtime, data))
self.bytes_read += len(data)
lines = data.split('\n')
lines[0] = self.partial_input + lines[0]
self.partial_input = lines.pop()
pending_commands = self.pending_commands
pending_commands.extend(lines)
self.pipe_is_active = True
# Special handling for debug file input EOF
if not data and self.is_fileinput:
if not self.is_processing_data:
self.reactor.unregister_fd(self.fd_handle)
self.fd_handle = None
self.gcode.request_restart('exit')
pending_commands.append("")
# Handle case where multiple commands pending
if self.is_processing_data or len(pending_commands) > 1:
if len(pending_commands) < 20:
# Check for M112 out-of-order
for line in lines:
if self.m112_r.match(line) is not None:
self.gcode.cmd_M112(None)
if self.is_processing_data:
if len(pending_commands) >= 20:
# Stop reading input
self.reactor.unregister_fd(self.fd_handle)
self.fd_handle = None
return
# Process commands
self.is_processing_data = True
while pending_commands:
self.pending_commands = []
with self.gcode_mutex:
self.gcode._process_commands(pending_commands)
pending_commands = self.pending_commands
self.is_processing_data = False
if self.fd_handle is None:
self.fd_handle = self.reactor.register_fd(self.fd,
self._process_data)
def _respond_raw(self, msg):
if self.pipe_is_active:
try:
os.write(self.fd, (msg+"\n").encode())
# if 'key506' not in msg and 'key507' not in msg and 'key3"' not in msg and "key" in msg:
# reportInformation(msg)
except os.error:
logging.exception("Write g-code response")
self.pipe_is_active = False
def stats(self, eventtime):
return False, "gcodein=%d" % (self.bytes_read,)
def add_early_printer_objects(printer):
printer.add_object('gcode', GCodeDispatch(printer))
printer.add_object('gcode_io', GCodeIO(printer))

2
files/fixes/sudo Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
exec $*

131
files/fixes/supervisorctl Executable file
View file

@ -0,0 +1,131 @@
#!/bin/sh
# supervisorctl shim - by destinal
# this is a fake supervisorctl that provides just enough information for moonraker to think it's the real thing.
# good enough to list the names of services in moonraker.conf, to say whether they're running or not (with false pids and times)
# and to start and stop them by name, finding and calling the matching init scripts.
# installing: put this in in /usr/bin/supervisorctl and then in moonraker.conf in [machine] section, set "provider: supervisord_cli"
if [ -t 1 ]; then # colorize only if we're on a terminal
GREEN='\033[32m'
RED='\033[31m'
ENDCOLOR='\033[0m'
fi
get_services() {
moonraker_pid="$(cat /var/run/moonraker.pid)"
# if moonraker is running, get its config directory from its command line
if [ -f /var/run/moonraker.pid ] && [ -d /proc/"$moonraker_pid" ] ; then
cmdline="$(tr '\0' '\n' < /proc/"$moonraker_pid"/cmdline)"
moonraker_dir="$(echo $cmdline | awk -F'-d ' '{print $2}' | awk '{print $1}')"
moonraker_conf="$moonraker_dir/config/moonraker.conf"
# services="klipper moonraker $(awk '/managed_services:/ {print $2}' $moonraker_conf | sed 's/://')"
# services=`(printf 'klipper\nmoonraker\n'; awk '/managed_services:/ {print $2}' $moonraker_conf | sed 's/://') | sort|uniq`
services=$(ls -1 /etc/init.d/S*|sed 's/.*\/S..//;s/_service$//')
echo $services
else
echo "Error: Invalid or missing PID file /var/run/moonraker.pid" >&2
exit 1
fi
}
get_pid_file() {
service="$1"
[ $service == "klipper" ] && service="klippy"
pid_file="/var/run/$service.pid"
echo $pid_file
}
is_running() {
service="$1"
pid_file="$(get_pid_file "$service")"
# Check for PID file
if [ -f "$pid_file" ] && [ -d "/proc/$(cat $pid_file)" ]; then
return 0 # Running
fi
# Fallback to using pidof in case the service doesn't use pid files
if pidof "$service" &>/dev/null; then
return 0 # Running
fi
return 1 # Not running
}
print_process_status() {
if is_running "$service"; then
printf "%-33s$GREEN""RUNNING$ENDCOLOR\n" "$service"
else
printf "%-33s$RED""STOPPED$ENDCOLOR\n" "$service"
fi
}
print_usage() {
echo "supervisorctl shim - provide minimal support for moonraker so CrealityOS moonraker can start/stop without systemd"
echo "Usage: $0 [command] <service>"
echo "commands include status stop start restart"
}
get_script_path() {
service="$1"
script_path="$(ls -1 /etc/init.d/S[0-9][0-9]${service}_service /etc/init.d/S[0-9][0-9]${service}* 2>/dev/null|head -1)"
echo "$script_path"
}
stop() {
service="$1"
script_path="$(get_script_path $service)"
# Check if the script exists and stop the service
if [[ -f "$script_path" ]]; then
"$script_path" stop
fi
}
start() {
service="$1"
script_path="$(get_script_path $service)"
# Check if the script exists and start the service
if [[ -f "$script_path" ]]; then
"$script_path" start
fi
}
restart() {
service="$1"
script_path="$(get_script_path $service)"
# Check if the script exists and restart the service
if [[ -f "$script_path" ]]; then
"$script_path" restart
fi
}
main() {
# echo "$0 $@" >> /tmp/supervisorctl.log
action="$1"; shift
case "$action" in
status)
if [ "$#" -lt 1 ]; then # just status, no arguments
for service in $(get_services); do
print_process_status $service
done
else
for service in "$@"; do # loop through the arguments provided
print_process_status $service
done
fi
;;
start)
start "$1"
;;
stop)
stop "$1"
;;
restart)
restart "$1"
;;
*)
print_usage
exit 1
esac
}
main "$@"

7
files/fixes/systemctl Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
if [ "$1" == "reboot" ]; then
/sbin/reboot
elif [ "$1" == "poweroff" ]; then
/sbin/poweroff
fi

View file

@ -0,0 +1,167 @@
{
"blacklist": [
"fluidd.xyz",
"fluidd.net"
],
"endpoints": [
],
"hosted": false,
"themePresets": [
{
"name": "Fluidd",
"color": "#2196F3",
"isDark": true,
"logo": {
"src": "logo_fluidd.svg"
}
},
{
"name": "Annex",
"color": "#96CC4A",
"isDark": true,
"logo": {
"src": "logo_annex.svg"
}
},
{
"name": "BTT",
"color": "#475A91",
"isDark": true,
"logo": {
"src": "logo_btt.svg"
}
},
{
"name": "Creality V1",
"color": "#2196F3",
"isDark": true,
"logo": {
"src": "logo_creality_v1.svg"
}
},
{
"name": "Creality V2",
"color": "#2196F3",
"isDark": true,
"logo": {
"src": "logo_creality_v2.svg"
}
},
{
"name": "EVA",
"color": "#76FB00",
"isDark": true,
"logo": {
"src": "logo_eva.svg",
"dark": "#232323",
"light": "#ffffff"
}
},
{
"name": "HevORT",
"color": "#dfff3e",
"isDark": true,
"logo": {
"src": "logo_hevort.svg"
}
},
{
"name": "Kingroon",
"color": "#DA7A2C",
"isDark": true,
"logo": {
"src": "logo_kingroon.svg"
}
},
{
"name": "Klipper",
"color": "#B12F36",
"isDark": true,
"logo": {
"src": "logo_klipper.svg"
}
},
{
"name": "LDO",
"color": "#326799",
"isDark": true,
"logo": {
"src": "logo_ldo.svg"
}
},
{
"name": "Peopoly",
"color": "#007CC2",
"isDark": true,
"logo": {
"src": "logo_peopoly.svg"
}
},
{
"name": "Prusa",
"color": "#E05D2D",
"isDark": false,
"logo": {
"src": "logo_prusa.svg"
}
},
{
"name": "Qidi Tech",
"color": "#5B7AEA",
"isDark": true,
"logo": {
"src": "logo_qidi.svg"
}
},
{
"name": "RatRig",
"color": "#76FB00",
"isDark": true,
"logo": {
"src": "logo_ratrig.svg",
"dark": "#232323",
"light": "#ffffff"
}
},
{
"name": "Siboor",
"color": "#32E0DF",
"isDark": true,
"logo": {
"src": "logo_siboor.svg"
}
},
{
"name": "Voron",
"color": "#FF2300",
"isDark": true,
"logo": {
"src": "logo_voron.svg"
}
},
{
"name": "VzBot",
"color": "#FF2300",
"isDark": true,
"logo": {
"src": "logo_vzbot.svg"
}
},
{
"name": "ZeroG",
"color": "#e34234",
"isDark": true,
"logo": {
"src": "logo_zerog.svg"
}
},
{
"name": "SnakeOil",
"color": "#4bc3ca",
"isDark": true,
"logo": {
"src": "logo_snakeoil.svg"
}
}
]
}

View file

@ -0,0 +1,6 @@
<svg width="56" height="56" viewBox="0 0 56 56"
xmlns="http://www.w3.org/2000/svg">
<g>
<path fill="var(--v-primary-base, #2E75AE)" d="m 8.6551814,44.057554 c 0.3603498,-0.652878 1.2065683,-2.158274 1.8804856,-3.345324 0.673917,-1.18705 2.988293,-5.201439 5.143057,-8.920863 2.154764,-3.719425 5.310426,-9.1259 7.012583,-12.014389 1.702157,-2.888489 3.572196,-6.044964 4.155639,-7.014388 C 27.430391,11.793165 27.953897,11 28.010294,11 c 0.0564,0 1.099108,1.667266 2.317135,3.705036 1.218028,2.03777 3.477681,5.874101 5.021451,8.52518 1.543771,2.651079 4.050926,6.976716 5.571457,9.612526 1.520531,2.63581 3.698759,6.45218 4.840504,8.480822 1.141747,2.028642 2.075902,3.740774 2.075902,3.804739 v 0.116301 H 34.81516 21.793578 l 0.0037,-0.107914 c 0.002,-0.05935 0.803004,-1.5 1.779933,-3.201438 l 1.776235,-3.093526 5.618273,-0.03763 5.618275,-0.03763 -0.533989,-0.971457 C 35.76231,37.260709 33.947866,34.068448 32.023906,30.7011 30.099946,27.33375 28.41152,24.427148 28.27185,24.241982 l -0.253947,-0.336665 -1.364829,2.28835 c -0.750656,1.258591 -3.146114,5.363888 -5.323238,9.122881 -2.177125,3.758992 -4.369345,7.530575 -4.871603,8.381295 l -0.913195,1.546762 H 11.772517 8 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,122 @@
<svg
id="Layer_1"
data-name="Layer 1"
width="56"
height="56"
viewBox="0 0 56 56"
version="1.1"
sodipodi:docname="logo_creality.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview240"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false" />
<defs
id="defs227">
<style
id="style182">
.cls-1 {
fill: url(#linear-gradient-3);
}
.cls-1, .cls-2 {
fill-rule: evenodd;
}
.cls-1, .cls-2, .cls-3, .cls-4 {
stroke-width: 0px;
}
.cls-2 {
fill: url(#linear-gradient);
}
.cls-3 {
fill: url(#linear-gradient-2);
}
.cls-4 {
fill: #fff;
opacity: 0;
}
</style>
<linearGradient
id="linear-gradient-2"
x1="28"
y1="44.608002"
x2="28.002001"
y2="34.222"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
stop-color="#8bb034"
id="stop201"
style="stop-color:var(--v-primary-base, #2196F3);stop-opacity:1;" />
<stop
offset="0.20200001"
stop-color="#87ad33"
id="stop203"
style="stop-color:var(--v-primary-darken2, #2E75AE);stop-opacity:1;" />
<stop
offset="0.38100001"
stop-color="#7ca533"
id="stop205"
style="stop-color:var(--v-primary-darken2, #2E75AE);stop-opacity:1;" />
<stop
offset="0.55199999"
stop-color="#6a9832"
id="stop207"
style="stop-color:var(--v-primary-darken2, #2E75AE);stop-opacity:1;" />
<stop
offset="0.71700001"
stop-color="#508632"
id="stop209"
style="stop-color:var(--v-primary-darken2, #2E75AE);stop-opacity:1;" />
<stop
offset="0.87800002"
stop-color="#306e31"
id="stop211"
style="stop-color:var(--v-primary-darken2, #2E75AE);stop-opacity:1;" />
<stop
offset="1"
stop-color="#125930"
id="stop213"
style="stop-color:var(--v-primary-darken2, #2E75AE);stop-opacity:1;" />
</linearGradient>
</defs>
<rect
class="cls-4"
width="56"
height="56"
id="rect229"
x="0"
y="0" />
<g
id="g237">
<path
class="cls-2"
d="m 47.869,43.265 c -0.035,-0.08 -1.131,-2.107 -2.435,-4.504 -3.689,-6.778 -11.028,-20.27 -13.546,-24.902 -0.539,-0.993 -1.026,-1.882 -1.082,-1.975 -0.607,-1.028 -1.681,-1.647 -2.832,-1.631 -0.108,10e-4 -0.222,0.006 -0.252,0.009 -1.04,0.121 -1.875,0.63 -2.433,1.484 -0.064,0.098 -0.535,0.948 -1.046,1.889 -2.339,4.303 -7.668,14.1 -13.713,25.211 -1.307,2.402 -2.397,4.428 -2.424,4.501 -0.159,0.444 -0.136,0.945 0.061,1.381 0.121,0.266 0.368,0.564 0.596,0.718 0.284,0.191 0.559,0.281 0.911,0.295 0.407,0.017 0.119,0.124 4.694,-1.748 8.877,-3.633 10.629,-4.348 10.846,-4.426 0.481,-0.174 1.13,-0.33 1.667,-0.401 1.357,-0.18 2.766,-0.027 4.007,0.435 0.12,0.045 3.509,1.428 7.531,3.074 4.022,1.646 7.372,3.008 7.444,3.027 0.102,0.027 0.194,0.035 0.421,0.034 0.26,0 0.307,-0.006 0.457,-0.052 0.212,-0.065 0.453,-0.19 0.608,-0.316 0.258,-0.21 0.491,-0.566 0.592,-0.903 0.04,-0.135 0.052,-0.217 0.059,-0.429 0.011,-0.311 -0.027,-0.536 -0.13,-0.771 z m -11.721,-8.81 c -0.054,0.261 -0.202,0.455 -0.427,0.562 -0.163,0.077 -0.37,0.092 -0.529,0.038 -0.057,-0.019 -1.097,-0.601 -2.312,-1.292 -1.215,-0.692 -2.296,-1.303 -2.403,-1.358 -0.542,-0.281 -1.119,-0.473 -1.699,-0.565 -0.261,-0.041 -1.011,-0.057 -1.304,-0.028 -0.455,0.045 -1.002,0.18 -1.407,0.346 -0.38,0.156 -0.718,0.34 -2.906,1.585 -1.214,0.691 -2.258,1.277 -2.319,1.301 -0.153,0.062 -0.38,0.054 -0.54,-0.02 -0.354,-0.162 -0.542,-0.592 -0.416,-0.949 0.013,-0.036 1.467,-3.028 3.231,-6.649 1.764,-3.621 3.332,-6.84 3.483,-7.154 0.312,-0.644 0.39,-0.774 0.563,-0.933 0.291,-0.266 0.676,-0.381 1.055,-0.313 0.277,0.05 0.503,0.175 0.705,0.392 0.145,0.155 0.167,0.196 0.614,1.116 0.202,0.417 1.768,3.632 3.479,7.144 3.433,7.046 3.187,6.515 3.134,6.775 z"
id="path231"
style="fill:var(--v-primary-base, #2196F3);fill-opacity:1" />
<path
class="cls-3"
d="m 26.851,34.093 -5.479,3.228 c -2.105,1.263 -4.038,2.423 -4.296,2.578 -0.257,0.155 -0.809,0.487 -1.225,0.738 -1.668,1.002 -4.248,2.555 -4.442,2.673 -3.0029708,1.704833 -2.7606933,1.506138 -3.0176933,1.660138 l 0.051014,0.08915 0.6450702,0.412958 L 10.114155,45.63983 15.85,43.381 c 0.891,-0.343 2.755,-1.06 4.144,-1.594 1.389,-0.534 3.08,-1.185 3.759,-1.446 1.356,-0.522 1.514,-0.579 1.927,-0.69 0.468,-0.127 0.909,-0.208 1.439,-0.267 0.395,-0.044 1.362,-0.044 1.758,0 0.646,0.071 1.205,0.186 1.767,0.363 0.18,0.057 0.912,0.328 1.627,0.604 0.715,0.275 2.407,0.926 3.759,1.446 1.353,0.52 3.197,1.229 4.099,1.576 l 5.565998,2.185041 1.108556,-0.004 0.78139,-0.49475 L 45.121,43.629 c -0.336,-0.202 -1.023,-0.616 -1.527,-0.92 -0.966,-0.583 -1.581,-0.953 -2.852,-1.716 -0.427,-0.256 -1.05,-0.631 -1.384,-0.832 -0.656,-0.395 -8.261,-5.054 -8.4,-5.137 0,0 -1.24,-0.737 -1.966,-1.154 -0.239,-0.137 -1.055,-0.526 -2.142,0.222 z"
id="path233"
sodipodi:nodetypes="ccccccccccsccccccccccccccccccc"
style="fill:url(#linear-gradient-2)" />
<path
class="cls-1"
d="m 33.207,25.075 c -1.779,-4.886 -3.406,-9.357 -3.616,-9.937 -0.464,-1.279 -0.487,-1.337 -0.638,-1.552 -0.21,-0.301 -0.445,-0.475 -0.733,-0.545 -0.394,-0.095 -0.794,0.064 -1.096,0.435 -0.18,0.22 -0.261,0.401 -0.585,1.297 -0.158,0.436 -1.787,4.914 -3.621,9.95 -1.834,5.036 -3.345,9.197 -3.358,9.248 -0.131,0.496 0.065,1.094 0.433,1.32 0.166,0.102 0.71,0.171 0.87,0.085 0.052,-0.028 3.669,-2.34 5.301,-3.257 -0.034,0.013 -0.069,0.026 -0.102,0.039 -0.38,0.156 -0.718,0.34 -2.906,1.585 -1.214,0.691 -2.258,1.277 -2.319,1.301 -0.153,0.062 -0.38,0.054 -0.54,-0.02 -0.354,-0.162 -0.542,-0.592 -0.416,-0.949 0.013,-0.036 1.467,-3.028 3.231,-6.649 1.764,-3.621 3.332,-6.84 3.483,-7.154 0.312,-0.644 0.39,-0.774 0.563,-0.933 0.291,-0.266 0.676,-0.381 1.055,-0.313 0.277,0.05 0.503,0.175 0.705,0.392 0.145,0.155 0.167,0.196 0.614,1.116 0.202,0.417 1.768,3.632 3.479,7.144 3.433,7.046 3.187,6.515 3.134,6.775 -0.054,0.261 -0.202,0.455 -0.427,0.562 -0.163,0.077 -0.37,0.092 -0.529,0.038 -0.057,-0.019 -1.097,-0.601 -2.312,-1.292 -1.215,-0.692 -2.296,-1.303 -2.403,-1.358 -0.158,-0.082 -0.319,-0.155 -0.481,-0.221 1.638,0.944 5.056,3.211 5.103,3.232 0.165,0.075 0.758,-0.029 0.927,-0.137 0.234,-0.148 0.388,-0.419 0.444,-0.781 0.056,-0.361 0.311,0.377 -3.257,-9.423 z"
id="path235"
style="fill:var(--v-primary-darken2, #2E75AE);fill-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -0,0 +1,87 @@
# Run a shell command via gcode
#
# Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os
import shlex
import subprocess
import logging
class ShellCommand:
def __init__(self, config):
self.name = config.get_name().split()[-1]
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
cmd = config.get('command')
cmd = os.path.expanduser(cmd)
self.command = shlex.split(cmd)
self.timeout = config.getfloat('timeout', 2., above=0.)
self.verbose = config.getboolean('verbose', True)
self.proc_fd = None
self.partial_output = ""
self.gcode.register_mux_command(
"RUN_SHELL_COMMAND", "CMD", self.name,
self.cmd_RUN_SHELL_COMMAND,
desc=self.cmd_RUN_SHELL_COMMAND_help)
def _process_output(self, eventime):
if self.proc_fd is None:
return
try:
data = os.read(self.proc_fd, 4096)
except Exception:
pass
data = self.partial_output + data.decode()
if '\n' not in data:
self.partial_output = data
return
elif data[-1] != '\n':
split = data.rfind('\n') + 1
self.partial_output = data[split:]
data = data[:split]
else:
self.partial_output = ""
self.gcode.respond_info(data)
cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command"
def cmd_RUN_SHELL_COMMAND(self, params):
gcode_params = params.get('PARAMS','')
gcode_params = shlex.split(gcode_params)
reactor = self.printer.get_reactor()
try:
proc = subprocess.Popen(
self.command + gcode_params, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception:
logging.exception(
"shell_command: Command {%s} failed" % (self.name))
raise self.gcode.error("Error running command {%s}" % (self.name))
if self.verbose:
self.proc_fd = proc.stdout.fileno()
self.gcode.respond_info("Running Command {%s}...:" % (self.name))
hdl = reactor.register_fd(self.proc_fd, self._process_output)
eventtime = reactor.monotonic()
endtime = eventtime + self.timeout
complete = False
while eventtime < endtime:
eventtime = reactor.pause(eventtime + .05)
if proc.poll() is not None:
complete = True
break
if not complete:
proc.terminate()
if self.verbose:
if self.partial_output:
self.gcode.respond_info(self.partial_output)
self.partial_output = ""
if complete:
msg = "Command {%s} finished\n" % (self.name)
else:
msg = "Command {%s} timed out" % (self.name)
self.gcode.respond_info(msg)
reactor.unregister_fd(hdl)
self.proc_fd = None
def load_config_prefix(config):
return ShellCommand(config)

25
files/git-backup/S52Git-Backup Executable file
View file

@ -0,0 +1,25 @@
#!/bin/sh
case "$1" in
start)
echo "Starting Git Backup..."
/usr/data/helper-script/files/git-backup/git-backup.sh -b "$BRANCH" -t "$IFS" -g origin & > /dev/null
;;
stop)
echo "Stopping Git Backup..."
pkill Git-Backup
pkill inotifywait
;;
restart)
echo "Restarting Git Backup..."
pkill Git-Backup
pkill inotifywait
sleep 1
/usr/data/helper-script/files/git-backup/git-backup.sh -b "$BRANCH" -t "$IFS" -g origin & > /dev/null
;;
*)
Usage: $0 {start|stop|restart}
exit 1
;;
esac
exit 0

View file

@ -0,0 +1,35 @@
########################################
# Git Backup
########################################
[gcode_shell_command Backup_Stop]
command: sh /usr/data/helper-script/files/git-backup/git-backup.sh -s
timeout: 600.0
verbose: true
[gcode_shell_command Backup_Pause]
command: sh /usr/data/helper-script/files/git-backup/git-backup.sh -p
timeout: 600.0
verbose: true
[gcode_shell_command Backup_Resume]
command: sh /usr/data/helper-script/files/git-backup/git-backup.sh -s
timeout: 600.0
verbose: true
[gcode_macro GIT_BACKUP_STOP]
gcode:
RUN_SHELL_COMMAND CMD=Backup_Stop
[gcode_macro GIT_BACKUP_PAUSE]
gcode:
RUN_SHELL_COMMAND CMD=Backup_Pause
[gcode_macro GIT_BACKUP_RESUME]
gcode:
RUN_SHELL_COMMAND CMD=Backup_Resume

310
files/git-backup/git-backup.sh Executable file
View file

@ -0,0 +1,310 @@
#!/bin/sh
#
# This program is based off of gitwatch @ https://github.com/gitwatch/gitwatch.git
# Copyright (C) 2013-2018 Patrick Lehner
# with modifications and contributions by:
# - Matthew McGowan
# - Dominik D. Geyer
# - Phil Thompson
# - Dave Musicant
#
# Edited to work on busybox ash shell, specifically the Creality K1 & K1Max
#############################################################################
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#############################################################################
#
# Idea and original code taken from http://stackoverflow.com/a/965274
# original work by Lester Buck
# (but heavily modified by now)
#
# Requires the command 'inotifywait' to be available, which is part of
# the inotify-tools (See https://github.com/rvoicilas/inotify-tools ),
# and (obviously) git.
# Will check the availability of both commands using the `which` command
# and will abort if either command (or `which`) is not found.
#
white=`echo -en "\033[m"`
yellow=`echo -en "\033[1;33m"`
green=`echo -en "\033[01;32m"`
INSTALL=0
PAUSE=0
RESUME=0
STOP=0
REMOTE=""
BRANCH=""
TARGET=""
EVENTS="${EVENTS:-close_write,move,move_self,delete,create,modify}"
SLEEP_TIME=5
DATE_FMT="+%d-%m-%Y (%H:%M:%S)"
COMMITMSG="Auto-commit on %d by Git Backup"
SKIP_IF_MERGING=0
# Function to print script help
shelp() {
echo "Usage: $(basename "$0") [-i] [-p] [-r] [-s] -b branch -t target -g remote"
echo "Options:"
echo " -i Install"
echo " -p Pause"
echo " -r Resume"
echo " -s Stop"
echo " -b branch Specify branch for git push"
echo " -t target Specify target directory or file to watch"
echo " -g remote Specify remote for git push"
}
# Parse command-line arguments
while getopts "iprsb:t:g:hn" option; do
case "${option}" in
i) INSTALL=1 ;;
p) PAUSE=1 ;;
r) RESUME=1 ;;
s) STOP=1 ;;
b) BRANCH="${OPTARG}" ;;
t) TARGET="${OPTARG}" ;;
g) REMOTE="${OPTARG}" ;;
h)
shelp
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
shelp
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
shelp
exit 1
;;
*)
shelp
exit 0
;;
esac
done
# Check if more than one flag is used
if [ "$((INSTALL + PAUSE + RESUME + STOP))" -gt 1 ]; then
echo "Error: Only one flag is allowed at a time."
shelp
exit 1
fi
# Pause, Resume, Stop flags
if [ "$PAUSE" = 1 ]; then
echo "Info: Pausing automatic backups until the next reboot or manually restarted..."
/etc/init.d/S52Git-Backup stop
exit 0
elif [ "$STOP" = 1 ]; then
echo "Info: Stopping automatic backups until manually restarted..."
mv /etc/init.d/S52Git-Backup /etc/init.d/disabled.S52Git-Backup
exit 0
elif [ "$RESUME" = 1 ]; then
echo "Info: Resuming automatic backups..."
mv /etc/init.d/disabled.S52Git-Backup /etc/init.d/S52Git-Backup
exit 0
elif [ "$INSTALL" = 1 ]; then
# Install required packages using opkg
if [ -f /opt/bin/opkg ]; then
/opt/bin/opkg update
/opt/bin/opkg install inotifywait procps-ng-pkill
else
echo "Error: opkg package manager not found. Please install Entware."
exit 1
fi
# Prompt user for configuration
echo "${white}"
read -p " Please enter your ${green}GitHub username${white} and press Enter: ${yellow}" USER_NAME
echo "${white}"
read -p " Please enter your ${green}GitHub repository name${white} and press Enter: ${yellow}" REPO_NAME
echo "${white}"
read -p " Please enter your ${green}GitHub personal access token${white} and press Enter: ${yellow}" GITHUB_TOKEN
echo "${white}"
# Prompt user to select folders to be watched
IFS=/usr/data/printer_data/config
# Connect config directory to github
cd "$IFS" || exit
git init
git remote add origin "https://$USER_NAME:$GITHUB_TOKEN@github.com/$USER_NAME/$REPO_NAME.git"
git checkout -b "$BRANCH"
git add .
git commit -m "Initial Backup"
git push -u origin "$BRANCH"
# Write configuration to .env file
echo "IFS=$IFS" > "$IFS/.env"
echo "GITHUB_TOKEN=$GITHUB_TOKEN" >> "$IFS/.env"
echo "REMOTE=$REPO_NAME" >> "$IFS/.env"
echo "BRANCH=$BRANCH" >> "$IFS/.env"
echo "USER=$USER_NAME" >> "$IFS/.env"
# Create .gitignore file to protect .env variables
echo ".env" > "$IFS/.gitignore"
# Insert .env to S52gitwatch.sh and move to init.d
cp -f /usr/data/helper-script/files/git-backup/S52Git-Backup /etc/init.d/S52Git-Backup
sed -i "2i source $IFS/.env" /etc/init.d/S52Git-Backup
chmod +x /etc/init.d/S52Git-Backup
/etc/init.d/S52Git-Backup start
exit 0
fi
# print all arguments to stderr
stderr() {
echo "$@" >&2
}
# clean up at end of program, killing the remaining sleep process if it still exists
cleanup() {
if [ -n "$SLEEP_PID" ] && kill -0 "$SLEEP_PID" 2>/dev/null; then
kill "$SLEEP_PID" 2>/dev/null
fi
exit 0
}
# Tests for the availability of a command
is_command() {
command -v "$1" >/dev/null 2>&1
}
# Test whether or not current git directory has ongoing merge
is_merging () {
[ -f "$(git rev-parse --git-dir)"/MERGE_HEAD ]
}
shift $((OPTIND - 1)) # Shift the input arguments, so that the input file (last arg) is $1 in the code below
GIT="git"
RL="readlink"
INW="inotifywait"
# Check availability of selected binaries and die if not met
for cmd in "$GIT" "$INW"; do
is_command "$cmd" || {
stderr "Error: Required command '$cmd' not found."
exit 2
}
done
SLEEP_PID="" # pid of timeout subprocess
trap "cleanup" EXIT # make sure the timeout is killed when exiting script
# Expand the path to the target to absolute path
if [ "$(uname)" != "Darwin" ]; then
IN=$($RL -f "$TARGET")
else
if is_command "greadlink"; then
IN=$(greadlink -f "$TARGET")
else
IN=$($RL -f "$TARGET")
if [ $? -eq 1 ]; then
echo "Info: Seems like your readlink doesn't support '-f'. Running without. Please 'brew install coreutils'."
IN=$($RL "$TARGET")
fi
fi
fi
if [ -d "$TARGET" ]; then # if the target is a directory
TARGETDIR=$(echo "$IN" | sed -e "s/\/*$//") # dir to CD into before using git commands: trim trailing slash, if any
# construct inotifywait-commandline
if [ "$(uname)" != "Darwin" ]; then
INW_ARGS="-qmr -e $EVENTS $TARGETDIR"
fi
GIT_ADD="git add -A ." # add "." (CWD) recursively to index
GIT_COMMIT_ARGS="-a" # add -a switch to "commit" call just to be sure
else
stderr "Error: The target is neither a regular file nor a directory."
exit 3
fi
# CD into the right dir
cd "$TARGETDIR" || {
stderr "Error: Can't change directory to '${TARGETDIR}'."
exit 5
}
if [ -n "$REMOTE" ]; then # are we pushing to a remote?
if [ -z "$BRANCH" ]; then # Do we have a branch set to push to ?
PUSH_CMD="$GIT push $REMOTE" # Branch not set, push to remote without a branch
else
# check if we are on a detached HEAD
if HEADREF=$($GIT symbolic-ref HEAD 2> /dev/null); then # HEAD is not detached
PUSH_CMD="$GIT push $REMOTE ${HEADREF#refs/heads/}:$BRANCH"
else # HEAD is detached
PUSH_CMD="$GIT push $REMOTE $BRANCH"
fi
fi
else
PUSH_CMD="" # if no remote is selected, make sure the push command is empty
fi
# main program loop: wait for changes and commit them
# whenever inotifywait reports a change, we spawn a timer (sleep process) that gives the writing
# process some time (in case there are a lot of changes or w/e); if there is already a timer
# running when we receive an event, we kill it and start a new one; thus we only commit if there
# have been no changes reported during a whole timeout period
# Custom timeout function
# main program loop: wait for changes and commit them
# Custom timeout function
timeout() {
sleep "5" &
timeout_pid=$!
trap "kill $timeout_pid 2>/dev/null" EXIT
wait $timeout_pid 2>/dev/null
}
while true; do
# Start inotifywait to monitor changes
eval "$INW $INW_ARGS" | while read -r line; do
# Check if there were any changes reported during the timeout period
if [ -n "$line" ]; then
# Process changes
if [ -n "$DATE_FMT" ]; then
COMMITMSG=$(echo "$COMMITMSG" | awk -v date="$(date "$DATE_FMT")" '{gsub(/%d/, date)}1') # splice the formatted date-time into the commit message
fi
cd "$TARGETDIR" || {
stderr "Error: Can't change directory to '${TARGETDIR}'."
exit 6
}
STATUS=$($GIT status -s)
if [ -n "$STATUS" ]; then # only commit if status shows tracked changes.
if [ "$SKIP_IF_MERGING" -eq 1 ] && is_merging; then
echo "Skipping commit - repo is merging"
continue
fi
$GIT_ADD # add file(s) to index
$GIT commit $GIT_COMMIT_ARGS -m "$COMMITMSG" # construct commit message and commit
if [ -n "$PUSH_CMD" ]; then
echo "Push command is $PUSH_CMD"
eval "$PUSH_CMD"
pkill 'inotifywait'
timeout
fi
fi
fi
done
done

View file

@ -0,0 +1,44 @@
#!/bin/sh
GUPPY_DIR="/usr/data/guppyscreen"
CURL="/usr/data/helper-script/files/fixes/curl"
VERSION_FILE="$GUPPY_DIR/.version"
CUSTOM_UPGRADE_SCRIPT="$GUPPY_DIR/custom_upgrade.sh"
if [ -f "$VERSION_FILE" ]; then
CURRENT_VERSION=$(jq -r '.version' "$VERSION_FILE")
THEME=$(jq -r '.theme' "$VERSION_FILE")
ASSET_NAME=$(jq '.asset_name' "$VERSION_FILE")
fi
"$CURL" -s https://api.github.com/repos/ballaswag/guppyscreen/releases -o /tmp/guppy-releases.json
latest_version=$(jq -r '.[0].tag_name' /tmp/guppy-releases.json)
if [ "$(printf '%s\n' "$CURRENT_VERSION" "$latest_version" | sort -V | head -n1)" = "$latest_version" ]; then
echo "Guppy Screen $CURRENT_VERSION is already up to date!"
rm -f /tmp/guppy-releases.json
exit 0
else
asset_url=$(jq -r ".[0].assets[] | select(.name == $ASSET_NAME).browser_download_url" /tmp/guppy-releases.json)
echo "Downloading latest version $latest_version from $asset_url"
"$CURL" -L "$asset_url" -o /usr/data/guppyscreen.tar.gz
fi
tar -xvf /usr/data/guppyscreen.tar.gz -C "$GUPPY_DIR/.."
if [ -f "$CUSTOM_UPGRADE_SCRIPT" ]; then
echo "Running custom_upgrade.sh for release $latest_version..."
"$CUSTOM_UPGRADE_SCRIPT"
fi
echo "Guppy Screen have been updated to version $latest_version!"
if grep -Fqs "ID=buildroot" /etc/os-release
then
[ -f /etc/init.d/S99guppyscreen ] && /etc/init.d/S99guppyscreen stop &> /dev/null
killall -q guppyscreen
/etc/init.d/S99guppyscreen restart &> /dev/null
rm -f /usr/data/guppyscreen.tar.gz
rm -f /tmp/guppy-releases.json
fi
exit 0

View file

@ -0,0 +1,37 @@
########################################
# Guppy Screen Update
########################################
[gcode_shell_command guppy_update]
command: sh /usr/data/helper-script/files/guppy-screen/guppy-update.sh
timeout: 600.0
verbose: True
[gcode_macro GUPPY_UPDATE]
description: Check for Guppy Screen Updates
gcode:
{% if printer.idle_timeout.state == "Printing" %}
RESPOND TYPE=error MSG="It's not possible to update Guppy Screen while printing!"
{% else %}
RUN_SHELL_COMMAND CMD=guppy_update
{% endif %}
[gcode_macro INPUT_SHAPER_CALIBRATION]
description: Measure X and Y Axis Resonances and Save values
gcode:
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=30
{% endif %}
{% if printer.toolhead.homed_axes != "xyz" %}
RESPOND TYPE=command MSG="Homing..."
G28
{% endif %}
RESPOND TYPE=command MSG="Measuring X and Y Resonances..."
SHAPER_CALIBRATE
M400
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=50
{% endif %}
CXSAVE_CONFIG

View file

@ -0,0 +1,39 @@
class CalibrateShaperConfig:
def __init__(self, config):
self.printer = config.get_printer();
shaper_type = config.get('shaper_type', 'mzv')
self.shaper_type_x = config.get('shaper_type_x' , shaper_type)
self.shaper_freq_x = config.getfloat('shaper_freq_x', 0., minval=0.)
self.shaper_type_y = config.get('shaper_type_y' , shaper_type)
self.shaper_freq_y = config.getfloat('shaper_freq_y', 0., minval=0.)
# Register commands
gcode = config.get_printer().lookup_object('gcode')
gcode.register_command("SAVE_INPUT_SHAPER", self.cmd_save_input_shaper)
def get_status(self, eventtime):
return {}
def cmd_save_input_shaper(self, gcmd):
self.shaper_freq_x = gcmd.get_float('SHAPER_FREQ_X',
self.shaper_freq_x, minval=0.)
self.shaper_type_x = gcmd.get('SHAPER_TYPE_X', self.shaper_type_x)
self.shaper_freq_y = gcmd.get_float('SHAPER_FREQ_Y',
self.shaper_freq_y, minval=0.)
self.shaper_type_y = gcmd.get('SHAPER_TYPE_Y', self.shaper_type_y)
configfile = self.printer.lookup_object('configfile')
configfile.set('input_shaper', 'shaper_type_x', self.shaper_type_x)
configfile.set('input_shaper', 'shaper_freq_x',
'%.1f' % (self.shaper_freq_x,))
configfile.set('input_shaper', 'shaper_type_y', self.shaper_type_y)
configfile.set('input_shaper', 'shaper_freq_y',
'%.1f' % (self.shaper_freq_y,))
def load_config(config):
return CalibrateShaperConfig(config)

View file

@ -0,0 +1,118 @@
########################################
# Improved Shapers Configurations
########################################
[respond]
[calibrate_shaper_config]
[gcode_shell_command resonance_graph]
command: /usr/data/printer_data/config/Helper-Script/improved-shapers/scripts/calibrate_shaper.py
timeout: 600.0
verbose: True
[gcode_shell_command belts_graph]
command: /usr/data/printer_data/config/Helper-Script/improved-shapers/scripts/graph_belts.py
timeout: 600.0
verbose: True
[gcode_macro INPUT_SHAPER_CALIBRATION]
description: Measure X and Y Axis Resonances and Save values
gcode:
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=30
{% endif %}
{% if printer.toolhead.homed_axes != "xyz" %}
RESPOND TYPE=command MSG="Homing..."
G28
{% endif %}
RESPOND TYPE=command MSG="Measuring X and Y Resonances..."
SHAPER_CALIBRATE
M400
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=50
{% endif %}
CXSAVE_CONFIG
[gcode_macro TEST_RESONANCES_GRAPHS]
description: Test X and Y Axis Resonances and Generate Graphs
gcode:
{% set x_png = params.X_PNG|default("/usr/data/printer_data/config/Helper-Script/improved-shapers/resonances_x.png") %}
{% set y_png = params.Y_PNG|default("/usr/data/printer_data/config/Helper-Script/improved-shapers/resonances_y.png") %}
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=30
{% endif %}
{% if printer.toolhead.homed_axes != "xyz" %}
RESPOND TYPE=command MSG="Homing..."
G28
{% endif %}
RESPOND TYPE=command MSG="Testing X Resonances..."
TEST_RESONANCES AXIS=X NAME=x
M400
RESPOND TYPE=command MSG="Generating X Graphs... This may take some time."
RUN_SHELL_COMMAND CMD=resonance_graph PARAMS="/tmp/resonances_x_x.csv -o {x_png}"
RESPOND TYPE=command MSG="X Graph (resonances_x.png) is available in /Helper-Script/improved-shapers folder."
RESPOND TYPE=command MSG="Testing Y Resonances..."
TEST_RESONANCES AXIS=Y NAME=y
M400
RESPOND TYPE=command MSG="Generating Y Graphs... This may take some time."
RUN_SHELL_COMMAND CMD=resonance_graph PARAMS="/tmp/resonances_y_y.csv -o {y_png}"
RESPOND TYPE=command MSG="Y Graph (resonances_y.png) is available in /Helper-Script/improved-shapers folder."
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=50
{% endif %}
[gcode_macro BELTS_SHAPER_CALIBRATION]
description: Perform a custom half-axis test to analyze and compare the frequency profiles of individual belts on CoreXY printers
gcode:
{% set min_freq = params.FREQ_START|default(5)|float %}
{% set max_freq = params.FREQ_END|default(133.33)|float %}
{% set hz_per_sec = params.HZ_PER_SEC|default(1)|float %}
{% set png_width = params.PNG_WIDTH|default(8)|float %}
{% set png_height = params.PNG_HEIGHT|default(4.8)|float %}
{% set png_out_path = params.PNG_OUT_PATH|default("/usr/data/printer_data/config/Helper-Script/improved-shapers/belts_calibration.png") %}
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=30
{% endif %}
{% if printer.toolhead.homed_axes != "xyz" %}
RESPOND TYPE=command MSG="Homing..."
G28
{% endif %}
TEST_RESONANCES AXIS=1,1 OUTPUT=raw_data NAME=b FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
M400
TEST_RESONANCES AXIS=1,-1 OUTPUT=raw_data NAME=a FREQ_START={min_freq} FREQ_END={max_freq} HZ_PER_SEC={hz_per_sec}
M400
RESPOND TYPE=command MSG="Generating belts comparative frequency profile..."
RESPOND TYPE=command MSG="This may take some time (3-5min)."
RUN_SHELL_COMMAND CMD=belts_graph PARAMS="-w {png_width} -l {png_height} -n -o {png_out_path} -k /usr/share/klipper /tmp/raw_data_axis=1.000,-1.000_a.csv /tmp/raw_data_axis=1.000,1.000_b.csv"
RESPOND TYPE=command MSG="Graph (belts_calibration.png) is available in /Helper-Script/improved-shapers folder."
{% if printer["configfile"].config["temperature_fan mcu_fan"] %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=mcu_fan TARGET=50
{% endif %}
[gcode_macro EXCITATE_AXIS_AT_FREQ]
description: Maintain a specified excitation frequency for a period of time to diagnose and locate a vibration source
gcode:
{% set frequency = params.FREQUENCY|default(25)|int %}
{% set time = params.TIME|default(10)|int %}
{% set axis = params.AXIS|default("x")|string|lower %}
{% if axis not in ["x", "y", "a", "b"] %}
{ action_raise_error("AXIS selection is invalid. Should be either x, y, a or b!") }
{% endif %}
{% if axis == "a" %}
{% set axis = "1,-1" %}
{% elif axis == "b" %}
{% set axis = "1,1" %}
{% endif %}
{% if printer.toolhead.homed_axes != "xyz" %}
RESPOND TYPE=command MSG="Homing..."
G28
{% endif %}
TEST_RESONANCES OUTPUT=raw_data AXIS={axis} FREQ_START={frequency-1} FREQ_END={frequency+1} HZ_PER_SEC={1/(time/3)}
M400

View file

@ -0,0 +1,186 @@
#!/usr/bin/env python3
###!/usr/data/rootfs/usr/bin/python3
# Shaper auto-calibration script
#
# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from __future__ import print_function
import importlib, optparse, os, sys, pathlib
from textwrap import wrap
import numpy as np, matplotlib
import shaper_calibrate
import json
MAX_TITLE_LENGTH=65
def parse_log(logname):
with open(logname) as f:
for header in f:
if not header.startswith('#'):
break
if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
# Raw accelerometer data
return np.loadtxt(logname, comments='#', delimiter=',')
# Parse power spectral density data
data = np.loadtxt(logname, skiprows=1, comments='#', delimiter=',')
calibration_data = shaper_calibrate.CalibrationData(
freq_bins=data[:,0], psd_sum=data[:,4],
psd_x=data[:,1], psd_y=data[:,2], psd_z=data[:,3])
calibration_data.set_numpy(np)
# If input shapers are present in the CSV file, the frequency
# response is already normalized to input frequencies
if 'mzv' not in header:
calibration_data.normalize_to_frequencies()
return calibration_data
######################################################################
# Shaper calibration
######################################################################
# Find the best shaper parameters
def calibrate_shaper(datas, csv_output, max_smoothing):
helper = shaper_calibrate.ShaperCalibrate(printer=None)
if isinstance(datas[0], shaper_calibrate.CalibrationData):
calibration_data = datas[0]
for data in datas[1:]:
calibration_data.add_data(data)
else:
# Process accelerometer data
calibration_data = helper.process_accelerometer_data(datas[0])
for data in datas[1:]:
calibration_data.add_data(helper.process_accelerometer_data(data))
calibration_data.normalize_to_frequencies()
shaper, all_shapers, resp = helper.find_best_shaper(
calibration_data, max_smoothing, print)
if csv_output is not None:
helper.save_calibration_data(
csv_output, calibration_data, all_shapers)
return shaper.name, all_shapers, calibration_data, resp
######################################################################
# Plot frequency response and suggested input shapers
######################################################################
def plot_freq_response(lognames, calibration_data, shapers,
selected_shaper, max_freq):
freqs = calibration_data.freq_bins
psd = calibration_data.psd_sum[freqs <= max_freq]
px = calibration_data.psd_x[freqs <= max_freq]
py = calibration_data.psd_y[freqs <= max_freq]
pz = calibration_data.psd_z[freqs <= max_freq]
freqs = freqs[freqs <= max_freq]
fontP = matplotlib.font_manager.FontProperties()
fontP.set_size('small')
fig, ax = matplotlib.pyplot.subplots()
ax.set_xlabel('Frequency, Hz')
ax.set_xlim([0, max_freq])
ax.set_ylabel('Power spectral density')
ax.plot(freqs, psd, label='X+Y+Z', color='purple')
ax.plot(freqs, px, label='X', color='red')
ax.plot(freqs, py, label='Y', color='green')
ax.plot(freqs, pz, label='Z', color='blue')
title = "Frequency response and shapers (%s)" % (', '.join(lognames))
ax.set_title("\n".join(wrap(title, MAX_TITLE_LENGTH)))
ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(5))
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
ax.grid(which='major', color='grey')
ax.grid(which='minor', color='lightgrey')
ax2 = ax.twinx()
ax2.set_ylabel('Shaper vibration reduction (ratio)')
best_shaper_vals = None
for shaper in shapers:
label = "%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)" % (
shaper.name.upper(), shaper.freq,
shaper.vibrs * 100., shaper.smoothing,
round(shaper.max_accel / 100.) * 100.)
linestyle = 'dotted'
if shaper.name == selected_shaper:
linestyle = 'dashdot'
best_shaper_vals = shaper.vals
ax2.plot(freqs, shaper.vals, label=label, linestyle=linestyle)
ax.plot(freqs, psd * best_shaper_vals,
label='After\nshaper', color='cyan')
# A hack to add a human-readable shaper recommendation to legend
ax2.plot([], [], ' ',
label="Recommended shaper: %s" % (selected_shaper.upper()))
ax.legend(loc='upper left', prop=fontP)
ax2.legend(loc='upper right', prop=fontP)
fig.tight_layout()
return fig
######################################################################
# Startup
######################################################################
def setup_matplotlib(output_to_file):
global matplotlib
if output_to_file:
matplotlib.rcParams.update({'figure.autolayout': True})
matplotlib.use('Agg')
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
def main():
# Parse command-line arguments
usage = "%prog [options] <logs>"
opts = optparse.OptionParser(usage)
opts.add_option("-o", "--output", type="string", dest="output",
default=None, help="filename of output graph")
opts.add_option("-c", "--csv", type="string", dest="csv",
default=None, help="filename of output csv file")
opts.add_option("-f", "--max_freq", type="float", default=200.,
help="maximum frequency to graph")
opts.add_option("-s", "--max_smoothing", type="float", default=None,
help="maximum shaper smoothing to allow")
opts.add_option("-w", "--width", type="float", dest="width",
default=8.3, help="width (inches) of the graph(s)")
opts.add_option("-l", "--height", type="float", dest="height",
default=11.6, help="height (inches) of the graph(s)")
options, args = opts.parse_args()
if len(args) < 1:
opts.error("Incorrect number of arguments")
if options.max_smoothing is not None and options.max_smoothing < 0.05:
opts.error("Too small max_smoothing specified (must be at least 0.05)")
# Parse data
datas = [parse_log(fn) for fn in args]
# Calibrate shaper and generate outputs
selected_shaper, shapers, calibration_data, resp = calibrate_shaper(
datas, options.csv, options.max_smoothing)
resp['logfile'] = args[0]
if not options.csv or options.output:
# Draw graph
setup_matplotlib(options.output is not None)
fig = plot_freq_response(args, calibration_data, shapers,
selected_shaper, options.max_freq)
# Show graph
if options.output is None:
matplotlib.pyplot.show()
else:
pathlib.Path(options.output).unlink(missing_ok=True)
fig.set_size_inches(options.width, options.height)
fig.savefig(options.output)
resp['png'] = options.output
print(json.dumps(resp))
print
if __name__ == '__main__':
main()

View file

@ -0,0 +1,573 @@
#!/usr/bin/env python3
#################################################
######## CoreXY BELTS CALIBRATION SCRIPT ########
#################################################
# Written by Frix_x#0161 #
# Be sure to make this script executable using SSH: type 'chmod +x ./graph_belts.py' when in the folder!
#####################################################################
################ !!! DO NOT EDIT BELOW THIS LINE !!! ################
#####################################################################
import optparse, matplotlib, sys, importlib, os, pathlib
from collections import namedtuple
import numpy as np
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker, matplotlib.gridspec, matplotlib.colors
import matplotlib.patches
import locale
import time
import glob
import shaper_calibrate
from datetime import datetime
matplotlib.use('Agg')
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # For paired peaks names
PEAKS_DETECTION_THRESHOLD = 0.20
CURVE_SIMILARITY_SIGMOID_K = 0.6
DC_GRAIN_OF_SALT_FACTOR = 0.75
DC_THRESHOLD_METRIC = 1.5e9
DC_MAX_UNPAIRED_PEAKS_ALLOWED = 4
# Define the SignalData namedtuple
SignalData = namedtuple('CalibrationData', ['freqs', 'psd', 'peaks', 'paired_peaks', 'unpaired_peaks'])
KLIPPAIN_COLORS = {
"purple": "#70088C",
"orange": "#FF8D32",
"dark_purple": "#150140",
"dark_orange": "#F24130",
"red_pink": "#F2055C"
}
# Set the best locale for time and date formating (generation of the titles)
try:
locale.setlocale(locale.LC_TIME, locale.getdefaultlocale())
except locale.Error:
locale.setlocale(locale.LC_TIME, 'C')
# Override the built-in print function to avoid problem in Klipper due to locale settings
original_print = print
def print_with_c_locale(*args, **kwargs):
original_locale = locale.setlocale(locale.LC_ALL, None)
locale.setlocale(locale.LC_ALL, 'C')
original_print(*args, **kwargs)
locale.setlocale(locale.LC_ALL, original_locale)
print = print_with_c_locale
def is_file_open(filepath):
for proc in os.listdir('/proc'):
if proc.isdigit():
for fd in glob.glob(f'/proc/{proc}/fd/*'):
try:
if os.path.samefile(fd, filepath):
return True
except FileNotFoundError:
# Klipper has already released the CSV file
pass
except PermissionError:
# Unable to check for this particular process due to permissions
pass
return False
######################################################################
# Computation of the PSD graph
######################################################################
# Calculate estimated "power spectral density" using existing Klipper tools
def calc_freq_response(data):
helper = shaper_calibrate.ShaperCalibrate(printer=None)
return helper.process_accelerometer_data(data)
# Calculate or estimate a "similarity" factor between two PSD curves and scale it to a percentage. This is
# used here to quantify how close the two belts path behavior and responses are close together.
def compute_curve_similarity_factor(signal1, signal2):
freqs1 = signal1.freqs
psd1 = signal1.psd
freqs2 = signal2.freqs
psd2 = signal2.psd
# Interpolate PSDs to match the same frequency bins and do a cross-correlation
psd2_interp = np.interp(freqs1, freqs2, psd2)
cross_corr = np.correlate(psd1, psd2_interp, mode='full')
# Find the peak of the cross-correlation and compute a similarity normalized by the energy of the signals
peak_value = np.max(cross_corr)
similarity = peak_value / (np.sqrt(np.sum(psd1**2) * np.sum(psd2_interp**2)))
# Apply sigmoid scaling to get better numbers and get a final percentage value
scaled_similarity = sigmoid_scale(-np.log(1 - similarity), CURVE_SIMILARITY_SIGMOID_K)
return scaled_similarity
# This find all the peaks in a curve by looking at when the derivative term goes from positive to negative
# Then only the peaks found above a threshold are kept to avoid capturing peaks in the low amplitude noise of a signal
def detect_peaks(psd, freqs, window_size=5, vicinity=3):
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals
kernel = np.ones(window_size) / window_size
smoothed_psd = np.convolve(psd, kernel, mode='valid')
mean_pad = [np.mean(psd[:window_size])] * (window_size // 2)
smoothed_psd = np.concatenate((mean_pad, smoothed_psd))
# Find peaks on the smoothed curve
smoothed_peaks = np.where((smoothed_psd[:-2] < smoothed_psd[1:-1]) & (smoothed_psd[1:-1] > smoothed_psd[2:]))[0] + 1
detection_threshold = PEAKS_DETECTION_THRESHOLD * psd.max()
smoothed_peaks = smoothed_peaks[smoothed_psd[smoothed_peaks] > detection_threshold]
# Refine peak positions on the original curve
refined_peaks = []
for peak in smoothed_peaks:
local_max = peak + np.argmax(psd[max(0, peak-vicinity):min(len(psd), peak+vicinity+1)]) - vicinity
refined_peaks.append(local_max)
return np.array(refined_peaks), freqs[refined_peaks]
# This function create pairs of peaks that are close in frequency on two curves (that are known
# to be resonances points and must be similar on both belts on a CoreXY kinematic)
def pair_peaks(peaks1, freqs1, psd1, peaks2, freqs2, psd2):
# Compute a dynamic detection threshold to filter and pair peaks efficiently
# even if the signal is very noisy (this get clipped to a maximum of 10Hz diff)
distances = []
for p1 in peaks1:
for p2 in peaks2:
distances.append(abs(freqs1[p1] - freqs2[p2]))
distances = np.array(distances)
median_distance = np.median(distances)
iqr = np.percentile(distances, 75) - np.percentile(distances, 25)
threshold = median_distance + 1.5 * iqr
threshold = min(threshold, 10)
# Pair the peaks using the dynamic thresold
paired_peaks = []
unpaired_peaks1 = list(peaks1)
unpaired_peaks2 = list(peaks2)
while unpaired_peaks1 and unpaired_peaks2:
min_distance = threshold + 1
pair = None
for p1 in unpaired_peaks1:
for p2 in unpaired_peaks2:
distance = abs(freqs1[p1] - freqs2[p2])
if distance < min_distance:
min_distance = distance
pair = (p1, p2)
if pair is None: # No more pairs below the threshold
break
p1, p2 = pair
paired_peaks.append(((p1, freqs1[p1], psd1[p1]), (p2, freqs2[p2], psd2[p2])))
unpaired_peaks1.remove(p1)
unpaired_peaks2.remove(p2)
return paired_peaks, unpaired_peaks1, unpaired_peaks2
######################################################################
# Computation of a basic signal spectrogram
######################################################################
def compute_spectrogram(data):
import scipy
N = data.shape[0]
Fs = N / (data[-1, 0] - data[0, 0])
# Round up to a power of 2 for faster FFT
M = 1 << int(.5 * Fs - 1).bit_length()
window = np.kaiser(M, 6.)
def _specgram(x):
x_detrended = x - np.mean(x) # Detrending by subtracting the mean value
return scipy.signal.spectrogram(
x_detrended, fs=Fs, window=window, nperseg=M, noverlap=M//2,
detrend='constant', scaling='density', mode='psd')
d = {'x': data[:, 1], 'y': data[:, 2], 'z': data[:, 3]}
f, t, pdata = _specgram(d['x'])
for axis in 'yz':
pdata += _specgram(d[axis])[2]
return pdata, t, f
######################################################################
# Computation of the differential spectrogram
######################################################################
# Interpolate source_data (2D) to match target_x and target_y in order to
# get similar time and frequency dimensions for the differential spectrogram
def interpolate_2d(target_x, target_y, source_x, source_y, source_data):
import scipy
# Create a grid of points in the source and target space
source_points = np.array([(x, y) for y in source_y for x in source_x])
target_points = np.array([(x, y) for y in target_y for x in target_x])
# Flatten the source data to match the flattened source points
source_values = source_data.flatten()
# Interpolate and reshape the interpolated data to match the target grid shape and replace NaN with zeros
interpolated_data = scipy.interpolate.griddata(source_points, source_values, target_points, method='nearest')
interpolated_data = interpolated_data.reshape((len(target_y), len(target_x)))
interpolated_data = np.nan_to_num(interpolated_data)
return interpolated_data
# Main logic function to combine two similar spectrogram - ie. from both belts paths - by substracting signals in order to create
# a new composite spectrogram. This result of a divergent but mostly centered new spectrogram (center will be white) with some colored zones
# highlighting differences in the belts paths. The summative spectrogram is used for the MHI calculation.
def combined_spectrogram(data1, data2):
pdata1, bins1, t1 = compute_spectrogram(data1)
pdata2, bins2, t2 = compute_spectrogram(data2)
# Interpolate the spectrograms
pdata2_interpolated = interpolate_2d(bins1, t1, bins2, t2, pdata2)
# Cobine them in two form: a summed diff for the MHI computation and a diverging diff for the spectrogram colors
combined_sum = np.abs(pdata1 - pdata2_interpolated)
combined_divergent = pdata1 - pdata2_interpolated
return combined_sum, combined_divergent, bins1, t1
# Compute a composite and highly subjective value indicating the "mechanical health of the printer (0 to 100%)" that represent the
# likelihood of mechanical issues on the printer. It is based on the differential spectrogram sum of gradient, salted with a bit
# of the estimated similarity cross-correlation from compute_curve_similarity_factor() and with a bit of the number of unpaired peaks.
# This result in a percentage value quantifying the machine behavior around the main resonances that give an hint if only touching belt tension
# will give good graphs or if there is a chance of mechanical issues in the background (above 50% should be considered as probably problematic)
def compute_mhi(combined_data, similarity_coefficient, num_unpaired_peaks):
# filtered_data = combined_data[combined_data > 100]
filtered_data = np.abs(combined_data)
# First compute a "total variability metric" based on the sum of the gradient that sum the magnitude of will emphasize regions of the
# spectrogram where there are rapid changes in magnitude (like the edges of resonance peaks).
total_variability_metric = np.sum(np.abs(np.gradient(filtered_data)))
# Scale the metric to a percentage using the threshold (found empirically on a large number of user data shared to me)
base_percentage = (np.log1p(total_variability_metric) / np.log1p(DC_THRESHOLD_METRIC)) * 100
# Adjust the percentage based on the similarity_coefficient to add a grain of salt
adjusted_percentage = base_percentage * (1 - DC_GRAIN_OF_SALT_FACTOR * (similarity_coefficient / 100))
# Adjust the percentage again based on the number of unpaired peaks to add a second grain of salt
peak_confidence = num_unpaired_peaks / DC_MAX_UNPAIRED_PEAKS_ALLOWED
final_percentage = (1 - peak_confidence) * adjusted_percentage + peak_confidence * 100
# Ensure the result lies between 0 and 100 by clipping the computed value
final_percentage = np.clip(final_percentage, 0, 100)
return final_percentage, mhi_lut(final_percentage)
# LUT to transform the MHI into a textual value easy to understand for the users of the script
def mhi_lut(mhi):
if 0 <= mhi <= 30:
return "Excellent mechanical health"
elif 30 < mhi <= 45:
return "Good mechanical health"
elif 45 < mhi <= 55:
return "Acceptable mechanical health"
elif 55 < mhi <= 70:
return "Potential signs of a mechanical issue"
elif 70 < mhi <= 85:
return "Likely a mechanical issue"
elif 85 < mhi <= 100:
return "Mechanical issue detected"
######################################################################
# Graphing
######################################################################
def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
# Get the belt name for the legend to avoid putting the full file name
signal1_belt = (lognames[0].split('/')[-1]).split('_')[-1][0]
signal2_belt = (lognames[1].split('/')[-1]).split('_')[-1][0]
if signal1_belt == 'A' and signal2_belt == 'B':
signal1_belt += " (axis 1,-1)"
signal2_belt += " (axis 1, 1)"
elif signal1_belt == 'B' and signal2_belt == 'A':
signal1_belt += " (axis 1, 1)"
signal2_belt += " (axis 1,-1)"
else:
print("Warning: belts doesn't seem to have the correct name A and B (extracted from the filename.csv)")
# Plot the two belts PSD signals
ax.plot(signal1.freqs, signal1.psd, label="Belt " + signal1_belt, color=KLIPPAIN_COLORS['purple'])
ax.plot(signal2.freqs, signal2.psd, label="Belt " + signal2_belt, color=KLIPPAIN_COLORS['orange'])
# Trace the "relax region" (also used as a threshold to filter and detect the peaks)
psd_lowest_max = min(signal1.psd.max(), signal2.psd.max())
peaks_warning_threshold = PEAKS_DETECTION_THRESHOLD * psd_lowest_max
ax.axhline(y=peaks_warning_threshold, color='black', linestyle='--', linewidth=0.5)
ax.fill_between(signal1.freqs, 0, peaks_warning_threshold, color='green', alpha=0.15, label='Relax Region')
# Trace and annotate the peaks on the graph
paired_peak_count = 0
unpaired_peak_count = 0
offsets_table_data = []
for _, (peak1, peak2) in enumerate(signal1.paired_peaks):
label = ALPHABET[paired_peak_count]
amplitude_offset = abs(((signal2.psd[peak2[0]] - signal1.psd[peak1[0]]) / max(signal1.psd[peak1[0]], signal2.psd[peak2[0]])) * 100)
frequency_offset = abs(signal2.freqs[peak2[0]] - signal1.freqs[peak1[0]])
offsets_table_data.append([f"Peaks {label}", f"{frequency_offset:.1f} Hz", f"{amplitude_offset:.1f} %"])
ax.plot(signal1.freqs[peak1[0]], signal1.psd[peak1[0]], "x", color='black')
ax.plot(signal2.freqs[peak2[0]], signal2.psd[peak2[0]], "x", color='black')
ax.plot([signal1.freqs[peak1[0]], signal2.freqs[peak2[0]]], [signal1.psd[peak1[0]], signal2.psd[peak2[0]]], ":", color='gray')
ax.annotate(label + "1", (signal1.freqs[peak1[0]], signal1.psd[peak1[0]]),
textcoords="offset points", xytext=(8, 5),
ha='left', fontsize=13, color='black')
ax.annotate(label + "2", (signal2.freqs[peak2[0]], signal2.psd[peak2[0]]),
textcoords="offset points", xytext=(8, 5),
ha='left', fontsize=13, color='black')
paired_peak_count += 1
for peak in signal1.unpaired_peaks:
ax.plot(signal1.freqs[peak], signal1.psd[peak], "x", color='black')
ax.annotate(str(unpaired_peak_count + 1), (signal1.freqs[peak], signal1.psd[peak]),
textcoords="offset points", xytext=(8, 5),
ha='left', fontsize=13, color='red', weight='bold')
unpaired_peak_count += 1
for peak in signal2.unpaired_peaks:
ax.plot(signal2.freqs[peak], signal2.psd[peak], "x", color='black')
ax.annotate(str(unpaired_peak_count + 1), (signal2.freqs[peak], signal2.psd[peak]),
textcoords="offset points", xytext=(8, 5),
ha='left', fontsize=13, color='red', weight='bold')
unpaired_peak_count += 1
# Compute the similarity (using cross-correlation of the PSD signals)
ax2 = ax.twinx() # To split the legends in two box
ax2.yaxis.set_visible(False)
similarity_factor = compute_curve_similarity_factor(signal1, signal2)
ax2.plot([], [], ' ', label=f'Estimated similarity: {similarity_factor:.1f}%')
ax2.plot([], [], ' ', label=f'Number of unpaired peaks: {unpaired_peak_count}')
print(f"Belts estimated similarity: {similarity_factor:.1f}%")
# Setting axis parameters, grid and graph title
ax.set_xlabel('Frequency (Hz)')
ax.set_xlim([0, max_freq])
ax.set_ylabel('Power spectral density')
psd_highest_max = max(signal1.psd.max(), signal2.psd.max())
ax.set_ylim([0, psd_highest_max + psd_highest_max * 0.05])
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
ax.grid(which='major', color='grey')
ax.grid(which='minor', color='lightgrey')
fontP = matplotlib.font_manager.FontProperties()
fontP.set_size('small')
ax.set_title('Belts Frequency Profiles (estimated similarity: {:.1f}%)'.format(similarity_factor), fontsize=10, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
# Print the table of offsets ontop of the graph below the original legend (upper right)
if len(offsets_table_data) > 0:
columns = ["", "Frequency delta", "Amplitude delta", ]
offset_table = ax.table(cellText=offsets_table_data, colLabels=columns, bbox=[0.66, 0.75, 0.33, 0.15], loc='upper right', cellLoc='center')
offset_table.auto_set_font_size(False)
offset_table.set_fontsize(8)
offset_table.auto_set_column_width([0, 1, 2])
offset_table.set_zorder(100)
cells = [key for key in offset_table.get_celld().keys()]
for cell in cells:
offset_table[cell].set_facecolor('white')
offset_table[cell].set_alpha(0.6)
ax.legend(loc='upper left', prop=fontP)
ax2.legend(loc='center right', prop=fontP)
return similarity_factor, unpaired_peak_count
def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_factor, max_freq):
combined_sum, combined_divergent, bins, t = combined_spectrogram(data1, data2)
# Compute the MHI value from the differential spectrogram sum of gradient, salted with
# the similarity factor and the number or unpaired peaks from the belts frequency profile
# Be careful, this value is highly opinionated and is pretty experimental!
mhi, textual_mhi = compute_mhi(combined_sum, similarity_factor, len(signal1.unpaired_peaks) + len(signal2.unpaired_peaks))
print(f"[experimental] Mechanical Health Indicator: {textual_mhi.lower()} ({mhi:.1f}%)")
ax.set_title(f"Differential Spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
ax.plot([], [], ' ', label=f'{textual_mhi} (experimental)')
# Draw the differential spectrogram with a specific custom norm to get orange or purple values where there is signal or white near zeros
colors = [KLIPPAIN_COLORS['dark_orange'], KLIPPAIN_COLORS['orange'], 'white', KLIPPAIN_COLORS['purple'], KLIPPAIN_COLORS['dark_purple']]
cm = matplotlib.colors.LinearSegmentedColormap.from_list('klippain_divergent', list(zip([0, 0.25, 0.5, 0.75, 1], colors)))
norm = matplotlib.colors.TwoSlopeNorm(vmin=np.min(combined_divergent), vcenter=0, vmax=np.max(combined_divergent))
ax.pcolormesh(t, bins, combined_divergent.T, cmap=cm, norm=norm, shading='gouraud')
ax.set_xlabel('Frequency (hz)')
ax.set_xlim([0., max_freq])
ax.set_ylabel('Time (s)')
ax.set_ylim([0, bins[-1]])
fontP = matplotlib.font_manager.FontProperties()
fontP.set_size('medium')
ax.legend(loc='best', prop=fontP)
# Plot vertical lines for unpaired peaks
unpaired_peak_count = 0
for _, peak in enumerate(signal1.unpaired_peaks):
ax.axvline(signal1.freqs[peak], color=KLIPPAIN_COLORS['red_pink'], linestyle='dotted', linewidth=1.5)
ax.annotate(f"Peak {unpaired_peak_count + 1}", (signal1.freqs[peak], t[-1]*0.05),
textcoords="data", color=KLIPPAIN_COLORS['red_pink'], rotation=90, fontsize=10,
verticalalignment='bottom', horizontalalignment='right')
unpaired_peak_count +=1
for _, peak in enumerate(signal2.unpaired_peaks):
ax.axvline(signal2.freqs[peak], color=KLIPPAIN_COLORS['red_pink'], linestyle='dotted', linewidth=1.5)
ax.annotate(f"Peak {unpaired_peak_count + 1}", (signal2.freqs[peak], t[-1]*0.05),
textcoords="data", color=KLIPPAIN_COLORS['red_pink'], rotation=90, fontsize=10,
verticalalignment='bottom', horizontalalignment='right')
unpaired_peak_count +=1
# Plot vertical lines and zones for paired peaks
for idx, (peak1, peak2) in enumerate(signal1.paired_peaks):
label = ALPHABET[idx]
x_min = min(peak1[1], peak2[1])
x_max = max(peak1[1], peak2[1])
ax.axvline(x_min, color=KLIPPAIN_COLORS['dark_purple'], linestyle='dotted', linewidth=1.5)
ax.axvline(x_max, color=KLIPPAIN_COLORS['dark_purple'], linestyle='dotted', linewidth=1.5)
ax.fill_between([x_min, x_max], 0, np.max(combined_divergent), color=KLIPPAIN_COLORS['dark_purple'], alpha=0.3)
ax.annotate(f"Peaks {label}", (x_min, t[-1]*0.05),
textcoords="data", color=KLIPPAIN_COLORS['dark_purple'], rotation=90, fontsize=10,
verticalalignment='bottom', horizontalalignment='right')
return
######################################################################
# Custom tools
######################################################################
# Simple helper to compute a sigmoid scalling (from 0 to 100%)
def sigmoid_scale(x, k=1):
return 1 / (1 + np.exp(-k * x)) * 100
# Original Klipper function to get the PSD data of a raw accelerometer signal
def compute_signal_data(data, max_freq):
calibration_data = calc_freq_response(data)
freqs = calibration_data.freq_bins[calibration_data.freq_bins <= max_freq]
psd = calibration_data.get_psd('all')[calibration_data.freq_bins <= max_freq]
peaks, _ = detect_peaks(psd, freqs)
return SignalData(freqs=freqs, psd=psd, peaks=peaks, paired_peaks=None, unpaired_peaks=None)
######################################################################
# Startup and main routines
######################################################################
def parse_log(logname):
with open(logname) as f:
for header in f:
if not header.startswith('#'):
break
if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
# Raw accelerometer data
return np.loadtxt(logname, comments='#', delimiter=',')
# Power spectral density data or shaper calibration data
raise ValueError("File %s does not contain raw accelerometer data and therefore "
"is not supported by this script. Please use the official Klipper "
"graph_accelerometer.py script to process it instead." % (logname,))
def belts_calibration(lognames, klipperdir="~/klipper", max_freq=200., graph_spectogram=True, width=8.3, height=11.6):
for filename in lognames[:2]:
# Wait for the file handler to be released by Klipper
while is_file_open(filename):
time.sleep(2)
# Parse data
datas = [parse_log(fn) for fn in lognames]
if len(datas) > 2:
raise ValueError("Incorrect number of .csv files used (this function needs two files to compare them)")
# Compute calibration data for the two datasets with automatic peaks detection
signal1 = compute_signal_data(datas[0], max_freq)
signal2 = compute_signal_data(datas[1], max_freq)
# Pair the peaks across the two datasets
paired_peaks, unpaired_peaks1, unpaired_peaks2 = pair_peaks(signal1.peaks, signal1.freqs, signal1.psd,
signal2.peaks, signal2.freqs, signal2.psd)
signal1 = signal1._replace(paired_peaks=paired_peaks, unpaired_peaks=unpaired_peaks1)
signal2 = signal2._replace(paired_peaks=paired_peaks, unpaired_peaks=unpaired_peaks2)
if graph_spectogram:
fig = matplotlib.pyplot.figure()
gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
else:
fig, ax1 = matplotlib.pyplot.subplots()
# Add title
try:
filename = lognames[0].split('/')[-1]
dt = datetime.strptime(f"{filename.split('_')[1]} {filename.split('_')[2]}", "%Y%m%d %H%M%S")
title_line2 = dt.strftime('%x %X')
except:
print("Warning: CSV filenames look to be different than expected (%s , %s)" % (lognames[0], lognames[1]))
title_line2 = lognames[0].split('/')[-1] + " / " + lognames[1].split('/')[-1]
fig.suptitle(title_line2)
# Plot the graphs
similarity_factor, _ = plot_compare_frequency(ax1, lognames, signal1, signal2, max_freq)
if graph_spectogram:
plot_difference_spectrogram(ax2, datas[0], datas[1], signal1, signal2, similarity_factor, max_freq)
fig.set_size_inches(width, height)
fig.tight_layout()
fig.subplots_adjust(top=0.89)
return fig
def main():
# Parse command-line arguments
usage = "%prog [options] <raw logs>"
opts = optparse.OptionParser(usage)
opts.add_option("-o", "--output", type="string", dest="output",
default=None, help="filename of output graph")
opts.add_option("-f", "--max_freq", type="float", default=200.,
help="maximum frequency to graph")
opts.add_option("-k", "--klipper_dir", type="string", dest="klipperdir",
default="~/klipper", help="main klipper directory")
opts.add_option("-n", "--no_spectogram", action="store_false", dest="no_spectogram",
default=True, help="disable plotting of spectogram")
opts.add_option("-w", "--width", type="float", dest="width",
default=8.3, help="width (inches) of the graph(s)")
opts.add_option("-l", "--height", type="float", dest="height",
default=11.6, help="height (inches) of the graph(s)")
options, args = opts.parse_args()
if len(args) < 1:
opts.error("Incorrect number of arguments")
if options.output is None:
opts.error("You must specify an output file.png to use the script (option -o)")
fig = belts_calibration(args, options.klipperdir, options.max_freq, options.no_spectogram,
options.width, options.height)
pathlib.Path(options.output).unlink(missing_ok=True)
fig.savefig(options.output)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,372 @@
# Automatic calibration of input shapers
#
# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import collections, importlib, logging, math, multiprocessing, traceback
import shaper_defs
MIN_FREQ = 5.
MAX_FREQ = 200.
WINDOW_T_SEC = 0.5
MAX_SHAPER_FREQ = 150.
TEST_DAMPING_RATIOS=[0.075, 0.1, 0.15]
AUTOTUNE_SHAPERS = ['zv', 'mzv', 'ei', '2hump_ei', '3hump_ei']
######################################################################
# Frequency response calculation and shaper auto-tuning
######################################################################
class CalibrationData:
def __init__(self, freq_bins, psd_sum, psd_x, psd_y, psd_z):
self.freq_bins = freq_bins
self.psd_sum = psd_sum
self.psd_x = psd_x
self.psd_y = psd_y
self.psd_z = psd_z
self._psd_list = [self.psd_sum, self.psd_x, self.psd_y, self.psd_z]
self._psd_map = {'x': self.psd_x, 'y': self.psd_y, 'z': self.psd_z,
'all': self.psd_sum}
self.data_sets = 1
def add_data(self, other):
np = self.numpy
joined_data_sets = self.data_sets + other.data_sets
for psd, other_psd in zip(self._psd_list, other._psd_list):
# `other` data may be defined at different frequency bins,
# interpolating to fix that.
other_normalized = other.data_sets * np.interp(
self.freq_bins, other.freq_bins, other_psd)
psd *= self.data_sets
psd[:] = (psd + other_normalized) * (1. / joined_data_sets)
self.data_sets = joined_data_sets
def set_numpy(self, numpy):
self.numpy = numpy
def normalize_to_frequencies(self):
for psd in self._psd_list:
# Avoid division by zero errors
psd /= self.freq_bins + .1
# Remove low-frequency noise
psd[self.freq_bins < MIN_FREQ] = 0.
def get_psd(self, axis='all'):
return self._psd_map[axis]
CalibrationResult = collections.namedtuple(
'CalibrationResult',
('name', 'freq', 'vals', 'vibrs', 'smoothing', 'score', 'max_accel'))
class ShaperCalibrate:
def __init__(self, printer):
self.printer = printer
self.error = printer.command_error if printer else Exception
try:
self.numpy = importlib.import_module('numpy')
except ImportError:
raise self.error(
"Failed to import `numpy` module, make sure it was "
"installed via `~/klippy-env/bin/pip install` (refer to "
"docs/Measuring_Resonances.md for more details).")
def background_process_exec(self, method, args):
if self.printer is None:
return method(*args)
import queuelogger
parent_conn, child_conn = multiprocessing.Pipe()
def wrapper():
queuelogger.clear_bg_logging()
try:
res = method(*args)
except:
child_conn.send((True, traceback.format_exc()))
child_conn.close()
return
child_conn.send((False, res))
child_conn.close()
# Start a process to perform the calculation
calc_proc = multiprocessing.Process(target=wrapper)
calc_proc.daemon = True
calc_proc.start()
# Wait for the process to finish
reactor = self.printer.get_reactor()
gcode = self.printer.lookup_object("gcode")
eventtime = last_report_time = reactor.monotonic()
while calc_proc.is_alive():
if eventtime > last_report_time + 5.:
last_report_time = eventtime
gcode.respond_info("Wait for calculations..", log=False)
eventtime = reactor.pause(eventtime + .1)
# Return results
is_err, res = parent_conn.recv()
if is_err:
raise self.error("Error in remote calculation: %s" % (res,))
calc_proc.join()
parent_conn.close()
return res
def _split_into_windows(self, x, window_size, overlap):
# Memory-efficient algorithm to split an input 'x' into a series
# of overlapping windows
step_between_windows = window_size - overlap
n_windows = (x.shape[-1] - overlap) // step_between_windows
shape = (window_size, n_windows)
strides = (x.strides[-1], step_between_windows * x.strides[-1])
return self.numpy.lib.stride_tricks.as_strided(
x, shape=shape, strides=strides, writeable=False)
def _psd(self, x, fs, nfft):
# Calculate power spectral density (PSD) using Welch's algorithm
np = self.numpy
window = np.kaiser(nfft, 6.)
# Compensation for windowing loss
scale = 1.0 / (window**2).sum()
# Split into overlapping windows of size nfft
overlap = nfft // 2
x = self._split_into_windows(x, nfft, overlap)
# First detrend, then apply windowing function
x = window[:, None] * (x - np.mean(x, axis=0))
# Calculate frequency response for each window using FFT
result = np.fft.rfft(x, n=nfft, axis=0)
result = np.conjugate(result) * result
result *= scale / fs
# For one-sided FFT output the response must be doubled, except
# the last point for unpaired Nyquist frequency (assuming even nfft)
# and the 'DC' term (0 Hz)
result[1:-1,:] *= 2.
# Welch's algorithm: average response over windows
psd = result.real.mean(axis=-1)
# Calculate the frequency bins
freqs = np.fft.rfftfreq(nfft, 1. / fs)
return freqs, psd
def calc_freq_response(self, raw_values):
np = self.numpy
if raw_values is None:
return None
if isinstance(raw_values, np.ndarray):
data = raw_values
else:
samples = raw_values.get_samples()
if not samples:
return None
data = np.array(samples)
N = data.shape[0]
T = data[-1,0] - data[0,0]
SAMPLING_FREQ = N / T
# Round up to the nearest power of 2 for faster FFT
M = 1 << int(SAMPLING_FREQ * WINDOW_T_SEC - 1).bit_length()
if N <= M:
return None
# Calculate PSD (power spectral density) of vibrations per
# frequency bins (the same bins for X, Y, and Z)
fx, px = self._psd(data[:,1], SAMPLING_FREQ, M)
fy, py = self._psd(data[:,2], SAMPLING_FREQ, M)
fz, pz = self._psd(data[:,3], SAMPLING_FREQ, M)
return CalibrationData(fx, px+py+pz, px, py, pz)
def process_accelerometer_data(self, data):
calibration_data = self.background_process_exec(
self.calc_freq_response, (data,))
if calibration_data is None:
raise self.error(
"Internal error processing accelerometer data %s" % (data,))
calibration_data.set_numpy(self.numpy)
return calibration_data
def _estimate_shaper(self, shaper, test_damping_ratio, test_freqs):
np = self.numpy
A, T = np.array(shaper[0]), np.array(shaper[1])
inv_D = 1. / A.sum()
omega = 2. * math.pi * test_freqs
damping = test_damping_ratio * omega
omega_d = omega * math.sqrt(1. - test_damping_ratio**2)
W = A * np.exp(np.outer(-damping, (T[-1] - T)))
S = W * np.sin(np.outer(omega_d, T))
C = W * np.cos(np.outer(omega_d, T))
return np.sqrt(S.sum(axis=1)**2 + C.sum(axis=1)**2) * inv_D
def _estimate_remaining_vibrations(self, shaper, test_damping_ratio,
freq_bins, psd):
vals = self._estimate_shaper(shaper, test_damping_ratio, freq_bins)
# The input shaper can only reduce the amplitude of vibrations by
# SHAPER_VIBRATION_REDUCTION times, so all vibrations below that
# threshold can be igonred
vibr_threshold = psd.max() / shaper_defs.SHAPER_VIBRATION_REDUCTION
remaining_vibrations = self.numpy.maximum(
vals * psd - vibr_threshold, 0).sum()
all_vibrations = self.numpy.maximum(psd - vibr_threshold, 0).sum()
return (remaining_vibrations / all_vibrations, vals)
def _get_shaper_smoothing(self, shaper, accel=5000, scv=5.):
half_accel = accel * .5
A, T = shaper
inv_D = 1. / sum(A)
n = len(T)
# Calculate input shaper shift
ts = sum([A[i] * T[i] for i in range(n)]) * inv_D
# Calculate offset for 90 and 180 degrees turn
offset_90 = offset_180 = 0.
for i in range(n):
if T[i] >= ts:
# Calculate offset for one of the axes
offset_90 += A[i] * (scv + half_accel * (T[i]-ts)) * (T[i]-ts)
offset_180 += A[i] * half_accel * (T[i]-ts)**2
offset_90 *= inv_D * math.sqrt(2.)
offset_180 *= inv_D
return max(offset_90, offset_180)
def fit_shaper(self, shaper_cfg, calibration_data, max_smoothing):
np = self.numpy
test_freqs = np.arange(shaper_cfg.min_freq, MAX_SHAPER_FREQ, .2)
freq_bins = calibration_data.freq_bins
psd = calibration_data.psd_sum[freq_bins <= MAX_FREQ]
freq_bins = freq_bins[freq_bins <= MAX_FREQ]
best_res = None
results = []
for test_freq in test_freqs[::-1]:
shaper_vibrations = 0.
shaper_vals = np.zeros(shape=freq_bins.shape)
shaper = shaper_cfg.init_func(
test_freq, shaper_defs.DEFAULT_DAMPING_RATIO)
shaper_smoothing = self._get_shaper_smoothing(shaper)
if max_smoothing and shaper_smoothing > max_smoothing and best_res:
return best_res
# Exact damping ratio of the printer is unknown, pessimizing
# remaining vibrations over possible damping values
for dr in TEST_DAMPING_RATIOS:
vibrations, vals = self._estimate_remaining_vibrations(
shaper, dr, freq_bins, psd)
shaper_vals = np.maximum(shaper_vals, vals)
if vibrations > shaper_vibrations:
shaper_vibrations = vibrations
max_accel = self.find_shaper_max_accel(shaper)
# The score trying to minimize vibrations, but also accounting
# the growth of smoothing. The formula itself does not have any
# special meaning, it simply shows good results on real user data
shaper_score = shaper_smoothing * (shaper_vibrations**1.5 +
shaper_vibrations * .2 + .01)
results.append(
CalibrationResult(
name=shaper_cfg.name, freq=test_freq, vals=shaper_vals,
vibrs=shaper_vibrations, smoothing=shaper_smoothing,
score=shaper_score, max_accel=max_accel))
if best_res is None or best_res.vibrs > results[-1].vibrs:
# The current frequency is better for the shaper.
best_res = results[-1]
# Try to find an 'optimal' shapper configuration: the one that is not
# much worse than the 'best' one, but gives much less smoothing
selected = best_res
for res in results[::-1]:
if res.vibrs < best_res.vibrs * 1.1 and res.score < selected.score:
selected = res
return selected
def _bisect(self, func):
left = right = 1.
while not func(left):
right = left
left *= .5
if right == left:
while func(right):
right *= 2.
while right - left > 1e-8:
middle = (left + right) * .5
if func(middle):
left = middle
else:
right = middle
return left
def find_shaper_max_accel(self, shaper):
# Just some empirically chosen value which produces good projections
# for max_accel without much smoothing
TARGET_SMOOTHING = 0.12
max_accel = self._bisect(lambda test_accel: self._get_shaper_smoothing(
shaper, test_accel) <= TARGET_SMOOTHING)
return max_accel
def find_best_shaper(self, calibration_data, max_smoothing, logger=None):
best_shaper = None
all_shapers = []
resp = {}
for shaper_cfg in shaper_defs.INPUT_SHAPERS:
if shaper_cfg.name not in AUTOTUNE_SHAPERS:
continue
shaper = self.background_process_exec(self.fit_shaper, (
shaper_cfg, calibration_data, max_smoothing))
if logger is not None:
resp[shaper.name] = {
'freq': shaper.freq,
'vib': shaper.vibrs * 100.,
'smooth': shaper.smoothing,
'max_acel': round(shaper.max_accel / 100.) * 100.
}
all_shapers.append(shaper)
if (best_shaper is None or shaper.score * 1.2 < best_shaper.score or
(shaper.score * 1.05 < best_shaper.score and
shaper.smoothing * 1.1 < best_shaper.smoothing)):
# Either the shaper significantly improves the score (by 20%),
# or it improves the score and smoothing (by 5% and 10% resp.)
best_shaper = shaper
return best_shaper, all_shapers, {'shapers': resp, 'best': best_shaper.name}
def save_params(self, configfile, axis, shaper_name, shaper_freq):
if axis == 'xy':
self.save_params(configfile, 'x', shaper_name, shaper_freq)
self.save_params(configfile, 'y', shaper_name, shaper_freq)
else:
configfile.set('input_shaper', 'shaper_type_'+axis, shaper_name)
configfile.set('input_shaper', 'shaper_freq_'+axis,
'%.1f' % (shaper_freq,))
def apply_params(self, input_shaper, axis, shaper_name, shaper_freq):
if axis == 'xy':
self.apply_params(input_shaper, 'x', shaper_name, shaper_freq)
self.apply_params(input_shaper, 'y', shaper_name, shaper_freq)
return
gcode = self.printer.lookup_object("gcode")
axis = axis.upper()
input_shaper.cmd_SET_INPUT_SHAPER(gcode.create_gcode_command(
"SET_INPUT_SHAPER", "SET_INPUT_SHAPER", {
"SHAPER_TYPE_" + axis: shaper_name,
"SHAPER_FREQ_" + axis: shaper_freq}))
def save_calibration_data(self, output, calibration_data, shapers=None):
try:
with open(output, "w") as csvfile:
csvfile.write("freq,psd_x,psd_y,psd_z,psd_xyz")
if shapers:
for shaper in shapers:
csvfile.write(",%s(%.1f)" % (shaper.name, shaper.freq))
csvfile.write("\n")
num_freqs = calibration_data.freq_bins.shape[0]
for i in range(num_freqs):
if calibration_data.freq_bins[i] >= MAX_FREQ:
break
csvfile.write("%.1f,%.3e,%.3e,%.3e,%.3e" % (
calibration_data.freq_bins[i],
calibration_data.psd_x[i],
calibration_data.psd_y[i],
calibration_data.psd_z[i],
calibration_data.psd_sum[i]))
if shapers:
for shaper in shapers:
csvfile.write(",%.3f" % (shaper.vals[i],))
csvfile.write("\n")
except IOError as e:
raise self.error("Error writing to file '%s': %s", output, str(e))

View file

@ -0,0 +1,102 @@
# Definitions of the supported input shapers
#
# Copyright (C) 2020-2021 Dmitry Butyugin <dmbutyugin@google.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import collections, math
SHAPER_VIBRATION_REDUCTION=20.
DEFAULT_DAMPING_RATIO = 0.1
InputShaperCfg = collections.namedtuple(
'InputShaperCfg', ('name', 'init_func', 'min_freq'))
def get_none_shaper():
return ([], [])
def get_zv_shaper(shaper_freq, damping_ratio):
df = math.sqrt(1. - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
t_d = 1. / (shaper_freq * df)
A = [1., K]
T = [0., .5*t_d]
return (A, T)
def get_zvd_shaper(shaper_freq, damping_ratio):
df = math.sqrt(1. - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
t_d = 1. / (shaper_freq * df)
A = [1., 2.*K, K**2]
T = [0., .5*t_d, t_d]
return (A, T)
def get_mzv_shaper(shaper_freq, damping_ratio):
df = math.sqrt(1. - damping_ratio**2)
K = math.exp(-.75 * damping_ratio * math.pi / df)
t_d = 1. / (shaper_freq * df)
a1 = 1. - 1. / math.sqrt(2.)
a2 = (math.sqrt(2.) - 1.) * K
a3 = a1 * K * K
A = [a1, a2, a3]
T = [0., .375*t_d, .75*t_d]
return (A, T)
def get_ei_shaper(shaper_freq, damping_ratio):
v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
df = math.sqrt(1. - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
t_d = 1. / (shaper_freq * df)
a1 = .25 * (1. + v_tol)
a2 = .5 * (1. - v_tol) * K
a3 = a1 * K * K
A = [a1, a2, a3]
T = [0., .5*t_d, t_d]
return (A, T)
def get_2hump_ei_shaper(shaper_freq, damping_ratio):
v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
df = math.sqrt(1. - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
t_d = 1. / (shaper_freq * df)
V2 = v_tol**2
X = pow(V2 * (math.sqrt(1. - V2) + 1.), 1./3.)
a1 = (3.*X*X + 2.*X + 3.*V2) / (16.*X)
a2 = (.5 - a1) * K
a3 = a2 * K
a4 = a1 * K * K * K
A = [a1, a2, a3, a4]
T = [0., .5*t_d, t_d, 1.5*t_d]
return (A, T)
def get_3hump_ei_shaper(shaper_freq, damping_ratio):
v_tol = 1. / SHAPER_VIBRATION_REDUCTION # vibration tolerance
df = math.sqrt(1. - damping_ratio**2)
K = math.exp(-damping_ratio * math.pi / df)
t_d = 1. / (shaper_freq * df)
K2 = K*K
a1 = 0.0625 * (1. + 3. * v_tol + 2. * math.sqrt(2. * (v_tol + 1.) * v_tol))
a2 = 0.25 * (1. - v_tol) * K
a3 = (0.5 * (1. + v_tol) - 2. * a1) * K2
a4 = a2 * K2
a5 = a1 * K2 * K2
A = [a1, a2, a3, a4, a5]
T = [0., .5*t_d, t_d, 1.5*t_d, 2.*t_d]
return (A, T)
# min_freq for each shaper is chosen to have projected max_accel ~= 1500
INPUT_SHAPERS = [
InputShaperCfg('zv', get_zv_shaper, min_freq=21.),
InputShaperCfg('mzv', get_mzv_shaper, min_freq=23.),
InputShaperCfg('zvd', get_zvd_shaper, min_freq=29.),
InputShaperCfg('ei', get_ei_shaper, min_freq=29.),
InputShaperCfg('2hump_ei', get_2hump_ei_shaper, min_freq=39.),
InputShaperCfg('3hump_ei', get_3hump_ei_shaper, min_freq=48.),
]

View file

@ -0,0 +1,93 @@
###########################################
# Adaptive Meshing for Creality K1 Series
###########################################
[gcode_macro BED_MESH_CALIBRATE]
rename_existing: _BED_MESH_CALIBRATE
gcode:
{% set all_points = printer.exclude_object.objects | map(attribute='polygon') | sum(start=[]) %}
{% set bed_mesh_min = printer.configfile.settings.bed_mesh.mesh_min %}
{% set bed_mesh_max = printer.configfile.settings.bed_mesh.mesh_max %}
{% set probe_count = printer.configfile.settings.bed_mesh.probe_count %}
{% set kamp_settings = printer["gcode_macro _KAMP_Settings"] %}
{% set verbose_enable = kamp_settings.verbose_enable | abs %}
{% set mesh_margin = kamp_settings.mesh_margin | float %}
{% set fuzz_amount = kamp_settings.fuzz_amount | float %}
{% set probe_count = probe_count if probe_count|length > 1 else probe_count * 2 %}
{% set max_probe_point_distance_x = ( bed_mesh_max[0] - bed_mesh_min[0] ) / (probe_count[0] - 1) %}
{% set max_probe_point_distance_y = ( bed_mesh_max[1] - bed_mesh_min[1] ) / (probe_count[1] - 1) %}
{% set x_min = all_points | map(attribute=0) | min | default(bed_mesh_min[0]) %}
{% set y_min = all_points | map(attribute=1) | min | default(bed_mesh_min[1]) %}
{% set x_max = all_points | map(attribute=0) | max | default(bed_mesh_max[0]) %}
{% set y_max = all_points | map(attribute=1) | max | default(bed_mesh_max[1]) %}
{% set fuzz_range = range((0) | int, (fuzz_amount * 100) | int + 1) %}
{% set adapted_x_min = x_min - mesh_margin - (fuzz_range | random / 100.0) %}
{% set adapted_y_min = y_min - mesh_margin - (fuzz_range | random / 100.0) %}
{% set adapted_x_max = x_max + mesh_margin + (fuzz_range | random / 100.0) %}
{% set adapted_y_max = y_max + mesh_margin + (fuzz_range | random / 100.0) %}
{% set adapted_x_min = [adapted_x_min , bed_mesh_min[0]] | max %}
{% set adapted_y_min = [adapted_y_min , bed_mesh_min[1]] | max %}
{% set adapted_x_max = [adapted_x_max , bed_mesh_max[0]] | min %}
{% set adapted_y_max = [adapted_y_max , bed_mesh_max[1]] | min %}
{% set points_x = (((adapted_x_max - adapted_x_min) / max_probe_point_distance_x) | round(method='ceil') | int) + 1 %}
{% set points_y = (((adapted_y_max - adapted_y_min) / max_probe_point_distance_y) | round(method='ceil') | int) + 1 %}
{% if (points_x > points_y) %}
{% set points_y = points_x %}
{% endif %}
{% if (points_x < points_y) %}
{% set points_x = points_y %}
{% endif %}
{% if (([points_x, points_y]|max) > 6) %}
{% set algorithm = "bicubic" %}
{% set min_points = 4 %}
{% else %}
{% set algorithm = "lagrange" %}
{% set min_points = 3 %}
{% endif %}
{% set points_x = [points_x , min_points]|max %}
{% set points_y = [points_y , min_points]|max %}
{% set points_x = [points_x , probe_count[0]]|min %}
{% set points_y = [points_y , probe_count[1]]|min %}
{% if verbose_enable == True %}
{% if printer.exclude_object.objects != [] %}
RESPOND TYPE=command MSG="Algorithm: {algorithm}"
RESPOND TYPE=command MSG="Default probe count: {probe_count[0]},{probe_count[1]}"
RESPOND TYPE=command MSG="Adapted probe count: {points_x},{points_y}"
RESPOND TYPE=command MSG="Default mesh bounds: {bed_mesh_min[0]},{bed_mesh_min[1]}, {bed_mesh_max[0]},{bed_mesh_max[1]}"
{% if mesh_margin > 0 %}
RESPOND TYPE=command MSG="Mesh margin is {mesh_margin}, mesh bounds extended by {mesh_margin}mm."
{% else %}
RESPOND TYPE=command MSG="Mesh margin is 0, margin not increased."
{% endif %}
{% if fuzz_amount > 0 %}
RESPOND TYPE=command MSG="Mesh point fuzzing enabled, points fuzzed up to {fuzz_amount}mm"
{% else %}
RESPOND TYPE=command MSG="Fuzz amount is 0, mesh points not fuzzed."
{% endif %}
RESPOND TYPE=command MSG="Adapted mesh bounds: {adapted_x_min},{adapted_y_min}, {adapted_x_max},{adapted_y_max}"
RESPOND TYPE=command MSG="KAMP adjustments successful. Happy KAMPing!"
{% else %}
RESPOND TYPE=command MSG="No object detected! Make sure you have enabled Exclude Objets setting in your slicer. Using Full Bed Mesh."
G4 P5000
{% endif %}
{% endif %}
_BED_MESH_CALIBRATE mesh_min={adapted_x_min},{adapted_y_min} mesh_max={adapted_x_max},{adapted_y_max} ALGORITHM={algorithm} PROBE_COUNT={points_x},{points_y}

View file

@ -0,0 +1,40 @@
###########################################
# KAMP Settings for Creality K1 Series
###########################################
# Below you can enable or disable specific configuration files depending on what you want KAMP to do:
[include Start_Print.cfg] # START_PRINT macro for Creality K1 Series.
[include Adaptive_Meshing.cfg] # Adaptive Meshing configurations.
[include Line_Purge.cfg] # Adaptive Line Purging configurations.
[include Smart_Park.cfg] # Smart Park feature, which parks the printhead near the print area for final heating.
#[include Prusa_Slicer.cfg] # Enable this if you use Prusa Slicer, it's the necessary macros to enable Exclude Objects functionality.
[respond] # Necessary to receive messages from KAMP
[gcode_macro _KAMP_Settings]
description: This macro contains all adjustable settings for KAMP
# The following variables are settings for KAMP as a whole:
variable_verbose_enable: True # Set to True to enable KAMP information output when running. This is useful for debugging.
# The following variables are for adjusting Adaptive Meshing settings for KAMP:
variable_mesh_margin: 0 # Expands the mesh size in millimeters if desired. Leave at 0 to disable.
variable_fuzz_amount: 0 # Slightly randomizes mesh points to spread out wear from nozzle-based probes. Leave at 0 to disable.
# The following variables are for adjusting Adaptive Line Purging settings for KAMP:
variable_purge_height: 0.8 # Z position of nozzle during purge. Default is 0.8.
variable_tip_distance: 0 # Distance between tip of filament and nozzle before purge. Should be similar to PRINT_END final retract amount. Default is 0.
variable_purge_margin: 10 # Distance the purge will be in front of the print area. Default is 10.
variable_purge_amount: 50 # Amount of filament to be purged prior to printing. Default is 50.
variable_flow_rate: 12 # Flow rate of purge in mm3/s. Default is 12.
# The following variables are for adjusting the Smart Park feature for KAMP, which will park the printhead near the print area at a specified height:
variable_smart_park_height: 10 # Z position for Smart Park. Default is 10.
gcode:
RESPOND TYPE=command MSG="Running the KAMP_Settings macro does nothing, it's only used for storing KAMP settings."

200
files/kamp/Line_Purge.cfg Normal file
View file

@ -0,0 +1,200 @@
###########################################
# Line Purge for Creality K1 Series
###########################################
[gcode_macro _LINE_PURGE]
description: A purge macro that adapts to be near your actual printed objects
gcode:
{% set travel_speed = (printer.toolhead.max_velocity) * 60 | float %}
{% set cross_section = printer.configfile.settings.extruder.max_extrude_cross_section | float %}
{% if printer.firmware_retraction is defined %}
{% set RETRACT = G10 | string %}
{% set UNRETRACT = G11 | string %}
{% else %}
{% set RETRACT = 'G1 E-0.5 F2400' | string %}
{% set UNRETRACT = 'G1 E0.5 F2400' | string %}
{% endif %}
{% set bed_x_max = printer["gcode_macro PRINTER_PARAM"].max_x_position | float %}
{% set bed_y_max = printer["gcode_macro PRINTER_PARAM"].max_y_position | float %}
{% set verbose_enable = printer["gcode_macro _KAMP_Settings"].verbose_enable | abs %}
{% set purge_height = printer["gcode_macro _KAMP_Settings"].purge_height | float %}
{% set tip_distance = printer["gcode_macro _KAMP_Settings"].tip_distance | float %}
{% set purge_margin = printer["gcode_macro _KAMP_Settings"].purge_margin | float %}
{% set purge_amount = printer["gcode_macro _KAMP_Settings"].purge_amount | float %}
{% set flow_rate = printer["gcode_macro _KAMP_Settings"].flow_rate | float %}
{% set rapid_move = 10 %}
{% set all_points = printer.exclude_object.objects | map(attribute='polygon') | sum(start=[]) %}
{% set purge_x_min = (all_points | map(attribute=0) | min | default(0)) %}
{% set purge_x_max = (all_points | map(attribute=0) | max | default(0)) %}
{% set purge_y_min = (all_points | map(attribute=1) | min | default(0)) %}
{% set purge_y_max = (all_points | map(attribute=1) | max | default(0)) %}
{% set detect_object = purge_x_min + purge_x_max + purge_y_min + purge_y_max %}
{% set purge_x_center = ([((purge_x_max + purge_x_min) / 2) - (purge_amount / 2), 0] | max) %}
{% set purge_y_center = ([((purge_y_max + purge_y_min) / 2) - (purge_amount / 2), 0] | max) %}
{% if (purge_x_center + purge_amount + rapid_move) > bed_x_max %}
{% set purge_x_center = (bed_x_max - (purge_amount + rapid_move)) %}
{% endif %}
{% if (purge_y_center + purge_amount + rapid_move) > bed_y_max %}
{% set purge_y_center = (bed_y_max - (purge_amount + rapid_move)) %}
{% endif %}
{% set purge_x_origin_low = (purge_x_min - purge_margin) %}
{% set purge_x_origin_high = (purge_x_max + purge_margin) %}
{% set purge_y_origin_low = (purge_y_min - purge_margin) %}
{% set purge_y_origin_high = (purge_y_max + purge_margin) %}
{% set purge_move_speed = (flow_rate / 5.0) * 60 | float %}
{% if cross_section < 5 %}
RESPOND TYPE=command MSG="[Extruder] max_extrude_cross_section is insufficient for line purge, please set it to 5 or greater. Purge skipped."
{% else %}
{% if verbose_enable == True %}
RESPOND TYPE=command MSG="Moving filament tip {tip_distance}mm"
{% endif %}
{% if detect_object == 0 %}
RESPOND TYPE=command MSG="No object detected! Using classic purge line."
{% elif purge_y_origin_low > 0 %}
RESPOND TYPE=command MSG="KAMP line purge starting at {purge_x_center}, {purge_y_origin_low} and purging {purge_amount}mm of filament, requested flow rate is {flow_rate}mm3/s."
{% elif purge_x_origin_low > 0 %}
RESPOND TYPE=command MSG="KAMP line purge starting at {purge_x_origin_low}, {purge_y_center} and purging {purge_amount}mm of filament, requested flow rate is {flow_rate}mm3/s."
{% elif purge_y_origin_high < bed_y_max %}
RESPOND TYPE=command MSG="KAMP line purge starting at {purge_x_center}, {purge_y_origin_high} and purging {purge_amount}mm of filament, requested flow rate is {flow_rate}mm3/s."
{% elif purge_x_origin_high < bed_x_max %}
RESPOND TYPE=command MSG="KAMP line purge starting at {purge_x_origin_high}, {purge_y_center} and purging {purge_amount}mm of filament, requested flow rate is {flow_rate}mm3/s."
{% else %}
RESPOND TYPE=command MSG="No space for purge line! Using classic purge line."
{% endif %}
SAVE_GCODE_STATE NAME=Prepurge_State
{% if detect_object == 0 %}
G92 E0
G1 Z0.1 F600
M83
{RETRACT}
SET_VELOCITY_LIMIT SQUARE_CORNER_VELOCITY=5
M204 S12000
SET_VELOCITY_LIMIT ACCEL_TO_DECEL=6000
M220 S100
M221 S100
G1 Z2.0 F1200
G1 X0.1 Y20 Z0.3 F6000.0
G1 X0.1 Y180.0 Z0.3 F3000.0 E10.0
G1 X0.4 Y180.0 Z0.3 F3000.0
G1 X0.4 Y20.0 Z0.3 F3000.0 E10.0
G1 Y10.0 F3000.0
G1 Z2.0 F3000.0
G92 E0
M82
G1 F12000
G21
{% elif purge_y_origin_low > 0 %}
G92 E0
G0 F{travel_speed}
G90
G0 X{purge_x_center} Y{purge_y_origin_low}
G0 Z{purge_height}
M83
G1 E{tip_distance} F{purge_move_speed}
G1 X{purge_x_center + purge_amount} E{purge_amount} F{purge_move_speed}
{RETRACT}
G0 X{purge_x_center + purge_amount + rapid_move} F{travel_speed}
G92 E0
M82
G0 Z{purge_height * 2} F{travel_speed}
{% elif purge_x_origin_low > 0 %}
G92 E0
G0 F{travel_speed}
G90
G0 X{purge_x_origin_low} Y{purge_y_center}
G0 Z{purge_height}
M83
G1 E{tip_distance} F{purge_move_speed}
G1 Y{purge_y_center + purge_amount} E{purge_amount} F{purge_move_speed}
{RETRACT}
G0 Y{purge_y_center + purge_amount + rapid_move} F{travel_speed}
G92 E0
M82
G0 Z{purge_height * 2} F{travel_speed}
{% elif purge_y_origin_high < bed_y_max %}
G92 E0
G0 F{travel_speed}
G90
G0 X{purge_x_center} Y{purge_y_origin_high}
G0 Z{purge_height}
M83
G1 E{tip_distance} F{purge_move_speed}
G1 X{purge_x_center + purge_amount} E{purge_amount} F{purge_move_speed}
{RETRACT}
G0 X{purge_x_center + purge_amount + rapid_move} F{travel_speed}
G92 E0
M82
G0 Z{purge_height * 2} F{travel_speed}
{% elif purge_x_origin_high < bed_x_max %}
G92 E0
G0 F{travel_speed}
G90
G0 X{purge_x_origin_high} Y{purge_y_center}
G0 Z{purge_height}
M83
G1 E{tip_distance} F{purge_move_speed}
G1 Y{purge_y_center + purge_amount} E{purge_amount} F{purge_move_speed}
{RETRACT}
G0 Y{purge_y_center + purge_amount + rapid_move} F{travel_speed}
G92 E0
M82
G0 Z{purge_height * 2} F{travel_speed}
{% else %}
G92 E0
G1 Z0.1 F600
M83
{RETRACT}
SET_VELOCITY_LIMIT SQUARE_CORNER_VELOCITY=5
M204 S12000
SET_VELOCITY_LIMIT ACCEL_TO_DECEL=6000
M220 S100
M221 S100
G1 Z2.0 F1200
G1 X0.1 Y20 Z0.3 F6000.0
G1 X0.1 Y180.0 Z0.3 F3000.0 E10.0
G1 X0.4 Y180.0 Z0.3 F3000.0
G1 X0.4 Y20.0 Z0.3 F3000.0 E10.0
G1 Y10.0 F3000.0
G1 Z2.0 F3000.0
G92 E0
M82
G1 F12000
G21
{% endif %}
RESTORE_GCODE_STATE NAME=Prepurge_State
{% endif %}

View file

@ -0,0 +1,32 @@
###########################################
# PrusaSlicer Macros for Creality K1 Series
###########################################
[gcode_macro DEFINE_OBJECT]
gcode:
EXCLUDE_OBJECT_DEFINE {rawparams}
[gcode_macro START_CURRENT_OBJECT]
gcode:
EXCLUDE_OBJECT_START NAME={params.NAME}
[gcode_macro END_CURRENT_OBJECT]
gcode:
EXCLUDE_OBJECT_END {% if params.NAME %}NAME={params.NAME}{% endif %}
[gcode_macro LIST_OBJECTS]
gcode:
EXCLUDE_OBJECT_DEFINE
[gcode_macro LIST_EXCLUDED_OBJECTS]
gcode:
EXCLUDE_OBJECT
[gcode_macro REMOVE_ALL_EXCLUDED]
gcode:
EXCLUDE_OBJECT RESET=1

79
files/kamp/Smart_Park.cfg Normal file
View file

@ -0,0 +1,79 @@
###########################################
# Smart Park for Creality K1 Series
###########################################
[gcode_macro _SMART_PARK]
description: Parks your printhead near the print area for pre-print hotend heating.
gcode:
{% set kamp_settings = printer["gcode_macro _KAMP_Settings"] %}
{% set bed_x_max = printer["gcode_macro PRINTER_PARAM"].max_x_position | float %}
{% set bed_y_max = printer["gcode_macro PRINTER_PARAM"].max_y_position | float %}
{% set z_height = kamp_settings.smart_park_height | float %}
{% set purge_margin = kamp_settings.purge_margin | float %}
{% set purge_amount = kamp_settings.purge_amount | float %}
{% set verbose_enable = kamp_settings.verbose_enable | abs %}
{% set center_x = bed_x_max / 2 %}
{% set center_y = bed_y_max / 2 %}
{% set axis_minimum_x = printer.toolhead.axis_minimum.x | float %}
{% set axis_minimum_y = printer.toolhead.axis_minimum.y | float %}
{% set all_points = printer.exclude_object.objects | map(attribute='polygon') | sum(start=[]) %}
{% set x_min = (all_points | map(attribute=0) | min | default(0)) %}
{% set x_max = (all_points | map(attribute=0) | max | default(0)) %}
{% set y_min = (all_points | map(attribute=1) | min | default(0)) %}
{% set y_max = (all_points | map(attribute=1) | max | default(0)) %}
{% set travel_speed = (printer.toolhead.max_velocity) * 60 | float %}
{% set rapid_move = 10 %}
{% set park_x_center = ([((x_max + x_min) / 2) - (purge_amount / 2), 0] | max) %}
{% set park_y_center = ([((y_max + y_min) / 2) - (purge_amount / 2), 0] | max) %}
{% if (park_x_center + purge_amount + rapid_move) > bed_x_max %}
{% set park_x_center = (bed_x_max - (purge_amount + rapid_move)) %}
{% endif %}
{% if (park_y_center + purge_amount + rapid_move) > bed_y_max %}
{% set park_y_center = (bed_y_max - (purge_amount + rapid_move)) %}
{% endif %}
{% set park_x_origin_low = (x_min - purge_margin) %}
{% set park_x_origin_high = (x_max + purge_margin) %}
{% set park_y_origin_low = (y_min - purge_margin) %}
{% set park_y_origin_high = (y_max + purge_margin) %}
{% set detect_object = (x_min + x_max + y_min + y_max) %}
{% if detect_object == 0 %}
{% set x_min = 10 %}
{% set y_min = 10 %}
{% set z_height = 2 %}
{% elif park_y_origin_low > 0 %}
{% set x_min = park_x_center %}
{% set y_min = park_y_origin_low %}
{% elif park_x_origin_low > 0 %}
{% set x_min = park_x_origin_low %}
{% set y_min = park_y_center %}
{% elif park_y_origin_high < bed_y_max %}
{% set x_min = park_x_center %}
{% set y_min = park_y_origin_high %}
{% elif park_x_origin_high < bed_x_max %}
{% set x_min = park_x_origin_high %}
{% set y_min = park_y_center %}
{% else %}
{% set x_min = 10 %}
{% set y_min = 10 %}
{% set z_height = 2 %}
{% endif %}
{% if verbose_enable == True %}
RESPOND TYPE=command MSG="Smart Park location: {x_min},{y_min}"
{% endif %}
SAVE_GCODE_STATE NAME=Presmartpark_State
G90
{% if printer.toolhead.position.z < z_height %}
G0 Z{z_height}
{% endif %}
G0 X{x_min} Y{y_min} F{travel_speed}
G0 Z{z_height}
RESTORE_GCODE_STATE NAME=Presmartpark_State

View file

@ -0,0 +1,62 @@
###########################################
# Start Print Macro for Creality K1 Series
###########################################
[respond]
[virtual_pins]
[output_pin KAMP]
pin: virtual_pin:KAMP_pin
value: 1
[output_pin BED_LEVELING]
pin: virtual_pin:BED_LEVELING_pin
value: 1
[gcode_macro START_PRINT]
variable_prepare: 0
gcode:
WAIT_TEMP_END
CLEAR_PAUSE
{% set g28_extruder_temp = printer.custom_macro.g28_ext_temp %}
{% set bed_temp = printer.custom_macro.default_bed_temp %}
{% set extruder_temp = printer.custom_macro.default_extruder_temp %}
{% if 'BED_TEMP' in params|upper and (params.BED_TEMP|float) %}
{% set bed_temp = params.BED_TEMP %}
{% endif %}
{% if 'EXTRUDER_TEMP' in params|upper and (params.EXTRUDER_TEMP|float) %}
{% set extruder_temp = params.EXTRUDER_TEMP %}
{% endif %}
{% if printer['gcode_macro START_PRINT'].prepare|int == 0 %}
PRINT_PREPARE_CLEAR
CX_ROUGH_G28 EXTRUDER_TEMP={extruder_temp} BED_TEMP={bed_temp}
CX_NOZZLE_CLEAR
ACCURATE_G28
{% if printer.exclude_object.objects != [] and printer['output_pin KAMP'].value == 1 %}
RESPOND TYPE=command MSG="Starting KAMP Bed Mesh..."
BED_MESH_CLEAR
BED_MESH_CALIBRATE PROFILE=kamp
BED_MESH_PROFILE LOAD="kamp"
{% else %}
{% if printer['output_pin BED_LEVELING'].value == 1 %}
RESPOND TYPE=command MSG="Starting Full Bed Mesh..."
CX_PRINT_LEVELING_CALIBRATION
{% endif %}
BED_MESH_PROFILE LOAD="default"
{% endif %}
{% else %}
PRINT_PREPARE_CLEAR
{% endif %}
{% if printer.exclude_object.objects != [] and printer['output_pin KAMP'].value == 1 %}
_SMART_PARK
M109 S{extruder_temp}
M190 S{bed_temp}
RESPOND TYPE=command MSG="Starting KAMP line purge..."
_LINE_PURGE
{% else %}
RESPOND TYPE=command MSG="Starting classic line purge..."
CX_PRINT_DRAW_ONE_LINE
{% endif %}
SET_VELOCITY_LIMIT ACCEL={printer.configfile.settings.printer.max_accel}

View file

@ -0,0 +1,246 @@
# Virtual Pins support
#
# Copyright (C) 2023 Pedro Lamas <pedrolamas@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
class VirtualPins:
def __init__(self, config):
self._printer = config.get_printer()
ppins = self._printer.lookup_object('pins')
ppins.register_chip('virtual_pin', self)
self._pins = {}
self._oid_count = 0
self._config_callbacks = []
self._printer.register_event_handler("klippy:connect",
self.handle_connect)
def handle_connect(self):
for cb in self._config_callbacks:
cb()
def setup_pin(self, pin_type, pin_params):
ppins = self._printer.lookup_object('pins')
name = pin_params['pin']
if name in self._pins:
return self._pins[name]
if pin_type == 'digital_out':
pin = DigitalOutVirtualPin(self, pin_params)
elif pin_type == 'pwm':
pin = PwmVirtualPin(self, pin_params)
elif pin_type == 'adc':
pin = AdcVirtualPin(self, pin_params)
elif pin_type == 'endstop':
pin = EndstopVirtualPin(self, pin_params)
else:
raise ppins.error("unable to create virtual pin of type %s" % (
pin_type,))
self._pins[name] = pin
return pin
def create_oid(self):
self._oid_count += 1
return self._oid_count - 1
def register_config_callback(self, cb):
self._config_callbacks.append(cb)
def add_config_cmd(self, cmd, is_init=False, on_restart=False):
pass
def get_query_slot(self, oid):
return 0
def seconds_to_clock(self, time):
return 0
def get_printer(self):
return self._printer
def register_response(self, cb, msg, oid=None):
pass
def alloc_command_queue(self):
pass
def lookup_command(self, msgformat, cq=None):
return VirtualCommand()
def lookup_query_command(self, msgformat, respformat, oid=None,
cq=None, is_async=False):
return VirtualCommandQuery(respformat, oid)
def get_enumerations(self):
return {}
def print_time_to_clock(self, print_time):
return 0
def estimated_print_time(self, eventtime):
return 0
def register_stepqueue(self, stepqueue):
pass
def request_move_queue_slot(self):
pass
def get_status(self, eventtime):
return {
'pins': {
name : pin.get_status(eventtime)
for name, pin in self._pins.items()
}
}
class VirtualCommand:
def send(self, data=(), minclock=0, reqclock=0):
pass
def get_command_tag(self):
pass
class VirtualCommandQuery:
def __init__(self, respformat, oid):
entries = respformat.split()
self._response = {}
for entry in entries[1:]:
key, _ = entry.split('=')
self._response[key] = oid if key == 'oid' else 1
def send(self, data=(), minclock=0, reqclock=0):
return self._response
def send_with_preface(self, preface_cmd, preface_data=(), data=(),
minclock=0, reqclock=0):
return self._response
class VirtualPin:
def __init__(self, mcu, pin_params):
self._mcu = mcu
self._name = pin_params['pin']
self._pullup = pin_params['pullup']
self._invert = pin_params['invert']
self._value = self._pullup
printer = self._mcu.get_printer()
self._real_mcu = printer.lookup_object('mcu')
gcode = printer.lookup_object('gcode')
gcode.register_mux_command("SET_VIRTUAL_PIN", "PIN", self._name,
self.cmd_SET_VIRTUAL_PIN,
desc=self.cmd_SET_VIRTUAL_PIN_help)
cmd_SET_VIRTUAL_PIN_help = "Set the value of an output pin"
def cmd_SET_VIRTUAL_PIN(self, gcmd):
self._value = gcmd.get_float('VALUE', minval=0., maxval=1.)
def get_mcu(self):
return self._real_mcu
class DigitalOutVirtualPin(VirtualPin):
def __init__(self, mcu, pin_params):
VirtualPin.__init__(self, mcu, pin_params)
def setup_max_duration(self, max_duration):
pass
def setup_start_value(self, start_value, shutdown_value):
self._value = start_value
def set_digital(self, print_time, value):
self._value = value
def get_status(self, eventtime):
return {
'value': self._value,
'type': 'digital_out'
}
class PwmVirtualPin(VirtualPin):
def __init__(self, mcu, pin_params):
VirtualPin.__init__(self, mcu, pin_params)
def setup_max_duration(self, max_duration):
pass
def setup_start_value(self, start_value, shutdown_value):
self._value = start_value
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
pass
def set_pwm(self, print_time, value, cycle_time=None):
self._value = value
def get_status(self, eventtime):
return {
'value': self._value,
'type': 'pwm'
}
class AdcVirtualPin(VirtualPin):
def __init__(self, mcu, pin_params):
VirtualPin.__init__(self, mcu, pin_params)
self._callback = None
self._min_sample = 0.
self._max_sample = 0.
printer = self._mcu.get_printer()
printer.register_event_handler("klippy:connect",
self.handle_connect)
def handle_connect(self):
reactor = self._mcu.get_printer().get_reactor()
reactor.register_timer(self._raise_callback, reactor.monotonic() + 2.)
def setup_adc_callback(self, report_time, callback):
self._callback = callback
def setup_minmax(self, sample_time, sample_count,
minval=0., maxval=1., range_check_count=0):
self._min_sample = minval
self._max_sample = maxval
def _raise_callback(self, eventtime):
range = self._max_sample - self._min_sample
sample_value = (self._value * range) + self._min_sample
self._callback(eventtime, sample_value)
return eventtime + 2.
def get_status(self, eventtime):
return {
'value': self._value,
'type': 'adc'
}
class EndstopVirtualPin(VirtualPin):
def __init__(self, mcu, pin_params):
VirtualPin.__init__(self, mcu, pin_params)
self._steppers = []
def add_stepper(self, stepper):
self._steppers.append(stepper)
def query_endstop(self, print_time):
return self._value
def home_start(self, print_time, sample_time, sample_count, rest_time,
triggered=True):
reactor = self._mcu.get_printer().get_reactor()
completion = reactor.completion()
completion.complete(True)
return completion
def home_wait(self, home_end_time):
return 1
def get_steppers(self):
return list(self._steppers)
def get_status(self, eventtime):
return {
'value': self._value,
'type': 'endstop'
}
def load_config(config):
return VirtualPins(config)

View file

@ -0,0 +1,133 @@
########################################
# M600 Support
########################################
[respond]
[idle_timeout]
gcode:
RESPOND TYPE=command MSG="Stopping hotend heating..."
M104 S0
timeout: 99999999
[filament_switch_sensor filament_sensor]
pause_on_runout: true
switch_pin: !PC15
runout_gcode:
M600
[gcode_macro M600]
description: Filament Change
variable_m600_state: 0
gcode:
{% set E = printer["gcode_macro PAUSE"].extrude|float %}
{% set y_park = printer.toolhead.axis_minimum.y|float + 5.0 %}
{% set x_park = printer.toolhead.axis_maximum.x|float - 10.0 %}
{% set max_z = printer["gcode_macro PRINTER_PARAM"].max_z_position|float %}
{% set act_z = printer.toolhead.position.z|float %}
{% set z_safe = 0.0 %}
{% if act_z < 48.0 %}
{% set z_safe = 50.0 - act_z %}
{% elif act_z < (max_z - 2.0) %}
{% set z_safe = 2.0 %}
{% elif act_z < max_z %}
{% set z_safe = max_z - act_z %}
{% endif %}
{action_respond_info("z_safe = %s"% (z_safe))}
SET_GCODE_VARIABLE MACRO=M600 VARIABLE=m600_state VALUE=1
SET_GCODE_VARIABLE MACRO=PRINTER_PARAM VARIABLE=hotend_temp VALUE={printer.extruder.target}
RESPOND TYPE=command MSG="Print paused for filament change!"
PAUSE_BASE
G91
{% if "xyz" in printer.toolhead.homed_axes %}
{% if printer.extruder.can_extrude|lower == 'true' %}
G1 E-1.0 F180
G1 E-{E} F4000
{% else %}
RESPOND TYPE=command MSG="Extruder not hot enough!"
{% endif %}
G1 Z{z_safe} F600
M400
G90
G1 X{x_park} Y{y_park} F30000
{% endif %}
G91
{% if printer.extruder.can_extrude|lower == 'true' %}
RESPOND TYPE=command MSG="Extracting filament..."
G1 E20 F180
G1 E-30 F180
G1 E-50 F2000
{% else %}
RESPOND TYPE=command MSG="Extruder not hot enough!"
{% endif %}
SET_GCODE_VARIABLE MACRO=PRINTER_PARAM VARIABLE=fan2_speed VALUE={printer['output_pin fan2'].value}
SET_GCODE_VARIABLE MACRO=PRINTER_PARAM VARIABLE=fan0_speed VALUE={printer['output_pin fan0'].value}
M106 P0 S0
M106 P2 S0
SET_IDLE_TIMEOUT TIMEOUT=900
SET_E_MIN_CURRENT
RESPOND TYPE=command MSG="Replace filament at the extruder inlet and click on Resume button!"
[gcode_macro RESUME]
description: Resume the current print
rename_existing: RESUME_BASE
gcode:
RESTORE_E_CURRENT
{% if printer['gcode_macro PRINTER_PARAM'].hotend_temp|int != 0 %}
{% if printer['gcode_macro PRINTER_PARAM'].hotend_temp|int > printer.extruder.temperature %}
RESPOND TYPE=command MSG="Starting hotend heating..."
M109 S{printer['gcode_macro PRINTER_PARAM'].hotend_temp|int}
{% else %}
RESPOND TYPE=command MSG="Starting hotend heating..."
M104 S{printer['gcode_macro PRINTER_PARAM'].hotend_temp|int}
{% endif %}
SET_GCODE_VARIABLE MACRO=PRINTER_PARAM VARIABLE=hotend_temp VALUE=0
{% endif %}
{% if printer['gcode_macro PRINTER_PARAM'].fan2_speed > 0 %}
{% set s_value = (printer['gcode_macro PRINTER_PARAM'].fan2_speed * 255 - printer['gcode_macro PRINTER_PARAM'].fan2_min) * 255 / (255 - printer['gcode_macro PRINTER_PARAM'].fan2_min)|float %}
M106 P2 S{s_value}
{% endif %}
{% set z_resume_move = printer['gcode_macro PRINTER_PARAM'].z_safe_pause|int %}
{% if z_resume_move > 2 %}
{% set z_resume_move = z_resume_move - 2 %}
G91
G1 Z-{z_resume_move} F600
M400
{% endif %}
{% set E = printer["gcode_macro PAUSE"].extrude|float + 1.0 %}
{% if 'VELOCITY' in params|upper %}
{% set get_params = ('VELOCITY=' + params.VELOCITY) %}
{%else %}
{% set get_params = "" %}
{% endif %}
{% if printer["gcode_macro M600"].m600_state == 1 %}
{% if printer.extruder.can_extrude|lower == 'true' %}
RESPOND TYPE=command MSG="Loading and purging filament..."
G91
G1 E180 F180
G90
M400
{% else %}
RESPOND TYPE=command MSG="Extruder not hot enough!"
{% endif %}
{% if printer['gcode_macro PRINTER_PARAM'].fan0_speed > 0 %}
{% set s_value = (printer['gcode_macro PRINTER_PARAM'].fan0_speed * 255 - printer['gcode_macro PRINTER_PARAM'].fan0_min) * 255 / (255 - printer['gcode_macro PRINTER_PARAM'].fan0_min)|float %}
M106 P0 S{s_value}
{% endif %}
SET_GCODE_VARIABLE MACRO=M600 VARIABLE=m600_state VALUE=0
SET_IDLE_TIMEOUT TIMEOUT=99999999
{% else %}
{% if printer.extruder.can_extrude|lower == 'true' %}
G91
G1 E{E} F2100
G90
M400
{% else %}
RESPOND TYPE=command MSG="Extruder not hot enough!"
{% endif %}
{% endif %}
RESPOND TYPE=command MSG="Restarting print..."
RESUME_BASE {get_params}

View file

@ -0,0 +1,142 @@
########################################
# Fans Control
########################################
[respond]
[duplicate_pin_override]
pins: PC0, PC5, PB2, ADC_TEMPERATURE
[temperature_fan chamber_fan]
pin: PC0
cycle_time: 0.0100
hardware_pwm: false
max_power: 1
shutdown_speed: 0
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PC5
min_temp: 0
max_temp: 70
control: watermark
max_delta: 2
target_temp: 35.0
max_speed: 1.0
min_speed: 0.0
[temperature_fan mcu_fan]
pin: PB2
cycle_time: 0.0100
hardware_pwm: false
max_power: 1
shutdown_speed: 0
sensor_type: temperature_mcu
min_temp: 0
max_temp: 100
control: watermark
max_delta: 2
target_temp: 50.0
max_speed: 1.0
min_speed: 0.0
[output_pin mcu_fan]
pin: PB2
pwm: True
cycle_time: 0.0100
hardware_pwm: false
value: 0.00
scale: 255
shutdown_value: 0.0
[gcode_macro M141]
description: Set Chamber Temperature with slicers
gcode:
{% set s = params.S|float %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=chamber_fan TARGET={s}
RESPOND TYPE=command MSG="Chamber target temperature: {s}°C"
[gcode_macro M191]
description: Wait for Chamber Temperature to heat up
gcode:
{% set s = params.S|float %}
{% set chamber_temp = printer["temperature_sensor chamber_temp"].temperature|float %}
{% if s > 0 %}
M141 S{s}
{% endif %}
{% if s > chamber_temp and s <= 90 %}
M140 S100
RESPOND TYPE=command MSG="Waiting for the bed to heat up the chamber..."
TEMPERATURE_WAIT SENSOR="temperature_fan chamber_fan" MINIMUM={s-1}
RESPOND TYPE=command MSG="Chamber target temperature reached: {s}°C"
M140 S{s}
{% endif %}
[gcode_macro M106]
gcode:
{% set fans = printer["gcode_macro PRINTER_PARAM"].fans|int %}
{% set fan = 0 %}
{% set value = 0 %}
{% if params.P is defined %}
{% set tmp = params.P|int %}
{% if tmp < fans %}
{% set fan = tmp %}
{% endif %}
{% endif %}
{% if params.S is defined %}
{% set tmp = params.S|float %}
{% else %}
{% set tmp = 255 %}
{% endif %}
{% if tmp > 0 %}
{% if fan == 0 %}
{% set value = (255 - printer["gcode_macro PRINTER_PARAM"].fan0_min) / 255 * tmp %}
{% if printer['gcode_macro Qmode'].flag | int == 1 %}
SET_GCODE_VARIABLE MACRO=Qmode VARIABLE=fan0_value VALUE={printer["gcode_macro PRINTER_PARAM"].fan0_min + value}
{% if value > (255 - printer['gcode_macro PRINTER_PARAM'].fan0_min) / 2 %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan0_min + (255 - printer['gcode_macro PRINTER_PARAM'].fan0_min) / 2 %}
{% else %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan0_min + value %}
{% endif %}
{% else %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan0_min + value %}
{% endif %}
{% endif %}
{% if fan == 1 %}
{% set value = (255 - printer["gcode_macro PRINTER_PARAM"].fan1_min) / 255 * tmp %}
{% if printer['gcode_macro Qmode'].flag | int == 1 %}
SET_GCODE_VARIABLE MACRO=Qmode VARIABLE=fan1_value VALUE={printer["gcode_macro PRINTER_PARAM"].fan1_min + value}
{% if value > (255 - printer['gcode_macro PRINTER_PARAM'].fan1_min) / 2 %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan1_min + (255 - printer['gcode_macro PRINTER_PARAM'].fan1_min) / 2 %}
{% else %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan1_min + value %}
{% endif %}
{% else %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan1_min + value %}
{% endif %}
{% endif %}
{% if fan == 2 %}
{% set value = (255 - printer["gcode_macro PRINTER_PARAM"].fan2_min) / 255 * tmp %}
{% if printer['gcode_macro Qmode'].flag | int == 1 %}
SET_GCODE_VARIABLE MACRO=Qmode VARIABLE=fan2_value VALUE={printer["gcode_macro PRINTER_PARAM"].fan2_min + value}
{% if value > (255 - printer['gcode_macro PRINTER_PARAM'].fan2_min) / 2 %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan2_min + (255 - printer['gcode_macro PRINTER_PARAM'].fan2_min) / 2 %}
{% else %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan2_min + value %}
{% endif %}
{% else %}
{% set value = printer["gcode_macro PRINTER_PARAM"].fan2_min + value %}
{% endif %}
{% endif %}
{% endif %}
{% if value >= 255 %}
{% set value = 255 %}
{% endif %}
{% if params.P is defined and params.P|int == 3 %}
{% set fan = 1 %}
{% endif %}
SET_PIN PIN=fan{fan} VALUE={value}

View file

@ -0,0 +1,38 @@
########################################
# Save Z-Offset
########################################
[save_variables]
filename: /usr/data/printer_data/config/Helper-Script/variables.cfg
[respond]
[gcode_macro SET_GCODE_OFFSET]
description: Saving Z-Offset
rename_existing: _SET_GCODE_OFFSET
gcode:
{% if printer.save_variables.variables.zoffset %}
{% set zoffset = printer.save_variables.variables.zoffset %}
{% else %}
{% set zoffset = {'z': None} %}
{% endif %}
{% set ns = namespace(zoffset={'z': zoffset.z}) %}
_SET_GCODE_OFFSET {% for p in params %}{'%s=%s '% (p, params[p])}{% endfor %}
{%if 'Z' in params %}{% set null = ns.zoffset.update({'z': params.Z}) %}{% endif %}
{%if 'Z_ADJUST' in params %}
{%if ns.zoffset.z == None %}{% set null = ns.zoffset.update({'z': 0}) %}{% endif %}
{% set null = ns.zoffset.update({'z': (ns.zoffset.z | float) + (params.Z_ADJUST | float)}) %}
{% endif %}
SAVE_VARIABLE VARIABLE=zoffset VALUE="{ns.zoffset}"
[delayed_gcode LOAD_GCODE_OFFSETS]
initial_duration: 2
gcode:
{% if printer.save_variables.variables.zoffset %}
{% set zoffset = printer.save_variables.variables.zoffset %}
_SET_GCODE_OFFSET {% for axis, offset in zoffset.items() if zoffset[axis] %}{ "%s=%s " % (axis, offset) }{% endfor %}
RESPOND TYPE=command MSG="Loaded Z-Offset from variables.cfg: {zoffset.z}mm"
{% endif %}

View file

@ -0,0 +1,205 @@
########################################
# Useful Macros
########################################
[gcode_shell_command Klipper_Backup]
command: sh /usr/data/helper-script/files/scripts/useful_macros.sh -backup_klipper
timeout: 600.0
verbose: true
[gcode_shell_command Klipper_Restore]
command: sh /usr/data/helper-script/files/scripts/useful_macros.sh -restore_klipper
timeout: 600.0
verbose: true
[gcode_shell_command Moonraker_Backup]
command: sh /usr/data/helper-script/files/scripts/useful_macros.sh -backup_moonraker
timeout: 600.0
verbose: true
[gcode_shell_command Moonraker_Restore]
command: sh /usr/data/helper-script/files/scripts/useful_macros.sh -restore_moonraker
timeout: 600.0
verbose: true
[gcode_macro KLIPPER_BACKUP_CONFIG]
gcode:
RUN_SHELL_COMMAND CMD=Klipper_Backup
[gcode_macro KLIPPER_RESTORE_CONFIG]
gcode:
RUN_SHELL_COMMAND CMD=Klipper_Restore
[gcode_macro MOONRAKER_BACKUP_DATABASE]
gcode:
RUN_SHELL_COMMAND CMD=Moonraker_Backup
[gcode_macro MOONRAKER_RESTORE_DATABASE]
gcode:
RUN_SHELL_COMMAND CMD=Moonraker_Restore
[gcode_macro BED_LEVELING]
description: Start Bed Leveling
gcode:
{% if 'PROBE_COUNT' in params|upper %}
{% set get_count = ('PROBE_COUNT=' + params.PROBE_COUNT) %}
{%else %}
{% set get_count = "" %}
{% endif %}
{% set bed_temp = params.BED_TEMP|default(50)|float %}
{% set hotend_temp = params.HOTEND_TEMP|default(140)|float %}
{% set nozzle_clear_temp = params.NOZZLE_CLEAR_TEMP|default(240)|float %}
SET_FILAMENT_SENSOR SENSOR=filament_sensor ENABLE=0
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
BED_MESH_CLEAR
NOZZLE_CLEAR HOT_MIN_TEMP={hotend_temp} HOT_MAX_TEMP={nozzle_clear_temp} BED_MAX_TEMP={bed_temp}
ACCURATE_G28
M204 S5000
SET_VELOCITY_LIMIT ACCEL_TO_DECEL=5000
BED_MESH_CALIBRATE {get_count}
BED_MESH_OUTPUT
{% set y_park = printer.toolhead.axis_maximum.y/2 %}
{% set x_park = printer.toolhead.axis_maximum.x|float - 10.0 %}
G1 X{x_park} Y{y_park} F20000
TURN_OFF_HEATERS
SET_FILAMENT_SENSOR SENSOR=filament_sensor ENABLE=1
[gcode_macro PID_BED]
description: Start Bed PID
gcode:
G90
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
G1 Z10 F600
M106
PID_CALIBRATE HEATER=heater_bed TARGET={params.BED_TEMP|default(70)}
M107
{% set y_park = printer.toolhead.axis_maximum.y/2 %}
{% set x_park = printer.toolhead.axis_maximum.x|float - 10.0 %}
G1 X{x_park} Y{y_park} F20000
[gcode_macro PID_HOTEND]
description: Start Hotend PID
gcode:
G90
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
G1 Z10 F600
M106
PID_CALIBRATE HEATER=extruder TARGET={params.HOTEND_TEMP|default(250)}
M107
{% set y_park = printer.toolhead.axis_maximum.y/2 %}
{% set x_park = printer.toolhead.axis_maximum.x|float - 10.0 %}
G1 X{x_park} Y{y_park} F20000
WAIT_TEMP_START
[gcode_macro LUBRICATE_RODS]
description: Distribute lubricant on Rods
gcode:
{% set min_speed = 3000 %} # Minimum speed in mm/min
{% set max_speed = 18000 %} # Maximum speed in mm/min
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
G1 Z50 F300
{% set x_max = printer.toolhead.axis_maximum.x|int %}
{% set y_max = printer.toolhead.axis_maximum.y|int %}
{% set edge_offset_x = x_max * 0.05 %}
{% set edge_offset_y = y_max * 0.05 %}
{% set x_range = x_max - edge_offset_x %}
{% set y_range = y_max - edge_offset_y %}
{% set num_steps_x = (x_range / 10)|int %}
{% set num_steps_y = (y_range / 10)|int %}
{% set speed_increment_x = (max_speed - min_speed) / num_steps_x %}
{% set speed_increment_y = (max_speed - min_speed) / num_steps_y %}
{% set current_speed_x = min_speed %}
{% set current_speed_y = min_speed %}
{% for i in range(num_steps_x) %}
G1 X{edge_offset_x + i * 10} Y{edge_offset_y} F{current_speed_x}
G1 X{edge_offset_x + i * 10} Y{y_range} F{current_speed_x}
{% set current_speed_x = current_speed_x + speed_increment_x %}
{% endfor %}
{% for j in range(num_steps_y) %}
G1 Y{edge_offset_y + j * 10} X{edge_offset_x} F{current_speed_y}
G1 Y{edge_offset_y + j * 10} X{x_range} F{current_speed_y}
{% set current_speed_y = current_speed_y + speed_increment_y %}
{% endfor %}
[gcode_macro WARMUP]
description: Stress Test
variable_maxd: 14142.14 ; = SQRT(2*maxy)
gcode:
{% set min_loops = 2 %}
{% set max_loops = params.LOOPS|default(3)|int %}
{% if 'LOOPS' in params|upper %}
{% if max_loops < min_loops %}
{% set max_loops = min_loops %}
{% endif %}
{% endif %}
{% set loop_cnt = max_loops %}
{% if 'X_ACCEL_MAX' in params|upper %}
{% set maxx = params.X_ACCEL_MAX|default(10000)|int %}
{% endif %}
{% if 'Y_ACCEL_MAX' in params|upper %}
{% set maxy = params.Y_ACCEL_MAX|default(10000)|int %}
{% endif %}
{% set max_x = (printer.toolhead.axis_maximum.x|int-5) %}
{% set max_y = (printer.toolhead.axis_maximum.y|int-5) %}
{% set loop_step_y = max_y//(loop_cnt-1) %}
{% set loop_step_x = max_x//(loop_cnt-1) %}
{% set y_park = printer.toolhead.axis_maximum.y/2 %}
{% set x_park = printer.toolhead.axis_maximum.x|float - 10.0 %}
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
SET_VELOCITY_LIMIT ACCEL={maxx} ACCEL_TO_DECEL={maxx/2}
{% for number in range(10,max_y+11,loop_step_y) %}
{% if number >= max_y %}
{% set number = max_y %}
{% endif %}
G1 F{maxy} X10 Y{number}
G1 F{maxx} X{max_x} Y{number}
{% endfor %}
SET_VELOCITY_LIMIT ACCEL={maxy} ACCEL_TO_DECEL={maxy/2}
{% for number in range(10,max_x+11,loop_step_y) %}
{% if number >= max_x %}
{% set number = max_x %}
{% endif %}
G1 F{maxy} X{number} Y{max_y}
G1 F{maxy} X{number} Y10
{% endfor %}
SET_VELOCITY_LIMIT ACCEL={maxd} ACCEL_TO_DECEL={maxd/2}
{% for times in range(loop_cnt) %}
G1 F{maxx} X10 Y10
G1 F{maxd} X{max_x} Y{max_y}
G1 F{maxx} X10 Y{max_y}
G1 F{maxd} X{max_x} Y10
G1 F{maxy} X{max_x} Y{max_y}
G1 F{maxd} X10 Y10
G1 F{maxy} X10 Y{max_y}
G1 F{maxd} X{max_x} Y10
{% endfor %}
SET_VELOCITY_LIMIT ACCEL={maxx} ACCEL_TO_DECEL={maxx/2}
{% for times in range(loop_cnt) %}
G1 F{maxy} X10 Y10
G1 F{maxy} X10 Y{max_y}
G1 F{maxx} X{max_x} Y{max_y}
G1 F{maxy} X{max_x} Y10
G1 F{maxx} X10 Y10
G1 F{maxx} X{max_x} Y10
G1 F{maxy} X{max_x} Y{max_y}
G1 F{maxx} X10 Y{max_y}
{% endfor %}
G1 X{x_park} Y{y_park} F30000

View file

@ -0,0 +1,427 @@
# Timelapse klipper macro definition
#
# Copyright (C) 2021 Christoph Frei <fryakatkop@gmail.com>
# Copyright (C) 2021 Alex Zellner <alexander.zellner@googlemail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license
#
# Macro version 1.15
#
##### DO NOT CHANGE ANY MACRO!!! #####
##########################################################################
# #
# GET_TIMELAPSE_SETUP: Print the Timelapse setup to console #
# #
##########################################################################
[gcode_macro GET_TIMELAPSE_SETUP]
description: Print the Timelapse setup
gcode:
{% set tl = printer['gcode_macro TIMELAPSE_TAKE_FRAME'] %}
{% set output_txt = ["Timelapse Setup:"] %}
{% set _dummy = output_txt.append("enable: %s" % tl.enable) %}
{% set _dummy = output_txt.append("park: %s" % tl.park.enable) %}
{% if tl.park.enable %}
{% set _dummy = output_txt.append("park position: %s time: %s s" % (tl.park.pos, tl.park.time)) %}
{% set _dummy = output_txt.append("park cord x:%s y:%s dz:%s" % (tl.park.coord.x, tl.park.coord.y, tl.park.coord.dz)) %}
{% set _dummy = output_txt.append("travel speed: %s mm/s" % tl.speed.travel) %}
{% endif %}
{% set _dummy = output_txt.append("fw_retract: %s" % tl.extruder.fw_retract) %}
{% if not tl.extruder.fw_retract %}
{% set _dummy = output_txt.append("retract: %s mm speed: %s mm/s" % (tl.extruder.retract, tl.speed.retract)) %}
{% set _dummy = output_txt.append("extrude: %s mm speed: %s mm/s" % (tl.extruder.extrude, tl.speed.extrude)) %}
{% endif %}
{% set _dummy = output_txt.append("verbose: %s" % tl.verbose) %}
{action_respond_info(output_txt|join("\n"))}
################################################################################################
# #
# Use _SET_TIMELAPSE_SETUP [ENABLE=value] [VERBOSE=value] [PARK_ENABLE=value] [PARK_POS=value] #
# [PARK_TIME=value] [CUSTOM_POS_X=value] [CUSTOM_POS_Y=value] #
# [CUSTOM_POS_DZ=value][TRAVEL_SPEED=value] [RETRACT_SPEED=value] #
# [EXTRUDE_SPEED=value] [EXTRUDE_DISTANCE=value] #
# [RETRACT_DISTANCE=value] [FW_RETRACT=value] #
# #
################################################################################################
[gcode_macro _SET_TIMELAPSE_SETUP]
description: Set user parameters for timelapse
gcode:
{% set tl = printer['gcode_macro TIMELAPSE_TAKE_FRAME'] %}
##### get min and max bed size #####
{% set min = printer.toolhead.axis_minimum %}
{% set max = printer.toolhead.axis_maximum %}
{% set round_bed = True if printer.configfile.settings.printer.kinematics is in ['delta','polar','rotary_delta','winch']
else False %}
{% set park = {'min' : {'x': (min.x / 1.42)|round(3) if round_bed else min.x|round(3),
'y': (min.y / 1.42)|round(3) if round_bed else min.y|round(3)},
'max' : {'x': (max.x / 1.42)|round(3) if round_bed else max.x|round(3),
'y': (max.y / 1.42)|round(3) if round_bed else max.y|round(3)},
'center': {'x': (max.x-(max.x-min.x)/2)|round(3),
'y': (max.y-(max.y-min.y)/2)|round(3)}} %}
##### set new values #####
{% if params.ENABLE %}
{% if params.ENABLE|lower is in ['true', 'false'] %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=enable VALUE={True if params.ENABLE|lower == 'true' else False}
{% else %}
{action_raise_error("ENABLE=%s not supported. Allowed values are [True, False]" % params.ENABLE|capitalize)}
{% endif %}
{% endif %}
{% if params.VERBOSE %}
{% if params.VERBOSE|lower is in ['true', 'false'] %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=verbose VALUE={True if params.VERBOSE|lower == 'true' else False}
{% else %}
{action_raise_error("VERBOSE=%s not supported. Allowed values are [True, False]" % params.VERBOSE|capitalize)}
{% endif %}
{% endif %}
{% if params.CUSTOM_POS_X %}
{% if params.CUSTOM_POS_X|float >= min.x and params.CUSTOM_POS_X|float <= max.x %}
{% set _dummy = tl.park.custom.update({'x':params.CUSTOM_POS_X|float|round(3)}) %}
{% else %}
{action_raise_error("CUSTOM_POS_X=%s must be within [%s - %s]" % (params.CUSTOM_POS_X, min.x, max.x))}
{% endif %}
{% endif %}
{% if params.CUSTOM_POS_Y %}
{% if params.CUSTOM_POS_Y|float >= min.y and params.CUSTOM_POS_Y|float <= max.y %}
{% set _dummy = tl.park.custom.update({'y':params.CUSTOM_POS_Y|float|round(3)}) %}
{% else %}
{action_raise_error("CUSTOM_POS_Y=%s must be within [%s - %s]" % (params.CUSTOM_POS_Y, min.y, max.y))}
{% endif %}
{% endif %}
{% if params.CUSTOM_POS_DZ %}
{% if params.CUSTOM_POS_DZ|float >= min.z and params.CUSTOM_POS_DZ|float <= max.z %}
{% set _dummy = tl.park.custom.update({'dz':params.CUSTOM_POS_DZ|float|round(3)}) %}
{% else %}
{action_raise_error("CUSTOM_POS_DZ=%s must be within [%s - %s]" % (params.CUSTOM_POS_DZ, min.z, max.z))}
{% endif %}
{% endif %}
{% if params.PARK_ENABLE %}
{% if params.PARK_ENABLE|lower is in ['true', 'false'] %}
{% set _dummy = tl.park.update({'enable':True if params.PARK_ENABLE|lower == 'true' else False}) %}
{% else %}
{action_raise_error("PARK_ENABLE=%s not supported. Allowed values are [True, False]" % params.PARK_ENABLE|capitalize)}
{% endif %}
{% endif %}
{% if params.PARK_POS %}
{% if params.PARK_POS|lower is in ['center','front_left','front_right','back_left','back_right','custom','x_only','y_only'] %}
{% set dic = {'center' : {'x': park.center.x , 'y': park.center.y , 'dz': 1 },
'front_left' : {'x': park.min.x , 'y': park.min.y , 'dz': 0 },
'front_right' : {'x': park.max.x , 'y': park.min.y , 'dz': 0 },
'back_left' : {'x': park.min.x , 'y': park.max.y , 'dz': 0 },
'back_right' : {'x': park.max.x , 'y': park.max.y , 'dz': 0 },
'custom' : {'x': tl.park.custom.x, 'y': tl.park.custom.y, 'dz': tl.park.custom.dz},
'x_only' : {'x': tl.park.custom.x, 'y': 'none' , 'dz': tl.park.custom.dz},
'y_only' : {'x': 'none' , 'y': tl.park.custom.y, 'dz': tl.park.custom.dz}} %}
{% set _dummy = tl.park.update({'pos':params.PARK_POS|lower}) %}
{% set _dummy = tl.park.update({'coord':dic[tl.park.pos]}) %}
{% else %}
{action_raise_error("PARK_POS=%s not supported. Allowed values are [CENTER, FRONT_LEFT, FRONT_RIGHT, BACK_LEFT, BACK_RIGHT, CUSTOM, X_ONLY, Y_ONLY]"
% params.PARK_POS|upper)}
{% endif %}
{% endif %}
{% if params.PARK_TIME %}
{% if params.PARK_TIME|float >= 0.0 %}
{% set _dummy = tl.park.update({'time':params.PARK_TIME|float|round(3)}) %}
{% else %}
{action_raise_error("PARK_TIME=%s must be a positive number" % params.PARK_TIME)}
{% endif %}
{% endif %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=park VALUE="{tl.park}"
{% if params.TRAVEL_SPEED %}
{% if params.TRAVEL_SPEED|float > 0.0 %}
{% set _dummy = tl.speed.update({'travel':params.TRAVEL_SPEED|float|round(3)}) %}
{% else %}
{action_raise_error("TRAVEL_SPEED=%s must be larger than 0" % params.TRAVEL_SPEED)}
{% endif %}
{% endif %}
{% if params.RETRACT_SPEED %}
{% if params.RETRACT_SPEED|float > 0.0 %}
{% set _dummy = tl.speed.update({'retract':params.RETRACT_SPEED|float|round(3)}) %}
{% else %}
{action_raise_error("RETRACT_SPEED=%s must be larger than 0" % params.RETRACT_SPEED)}
{% endif %}
{% endif %}
{% if params.EXTRUDE_SPEED %}
{% if params.EXTRUDE_SPEED|float > 0.0 %}
{% set _dummy = tl.speed.update({'extrude':params.EXTRUDE_SPEED|float|round(3)}) %}
{% else %}
{action_raise_error("EXTRUDE_SPEED=%s must be larger than 0" % params.EXTRUDE_SPEED)}
{% endif %}
{% endif %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=speed VALUE="{tl.speed}"
{% if params.EXTRUDE_DISTANCE %}
{% if params.EXTRUDE_DISTANCE|float >= 0.0 %}
{% set _dummy = tl.extruder.update({'extrude':params.EXTRUDE_DISTANCE|float|round(3)}) %}
{% else %}
{action_raise_error("EXTRUDE_DISTANCE=%s must be specified as positiv number" % params.EXTRUDE_DISTANCE)}
{% endif %}
{% endif %}
{% if params.RETRACT_DISTANCE %}
{% if params.RETRACT_DISTANCE|float >= 0.0 %}
{% set _dummy = tl.extruder.update({'retract':params.RETRACT_DISTANCE|float|round(3)}) %}
{% else %}
{action_raise_error("RETRACT_DISTANCE=%s must be specified as positiv number" % params.RETRACT_DISTANCE)}
{% endif %}
{% endif %}
{% if params.FW_RETRACT %}
{% if params.FW_RETRACT|lower is in ['true', 'false'] %}
{% if 'firmware_retraction' in printer.configfile.settings %}
{% set _dummy = tl.extruder.update({'fw_retract': True if params.FW_RETRACT|lower == 'true' else False}) %}
{% else %}
{% set _dummy = tl.extruder.update({'fw_retract':False}) %}
{% if params.FW_RETRACT|capitalize == 'True' %}
{action_raise_error("[firmware_retraction] not defined in printer.cfg. Can not enable fw_retract")}
{% endif %}
{% endif %}
{% else %}
{action_raise_error("FW_RETRACT=%s not supported. Allowed values are [True, False]" % params.FW_RETRACT|capitalize)}
{% endif %}
{% endif %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=extruder VALUE="{tl.extruder}"
{% if printer.configfile.settings['gcode_macro pause'] is defined %}
{% set _dummy = tl.macro.update({'pause': printer.configfile.settings['gcode_macro pause'].rename_existing}) %}
{% endif %}
{% if printer.configfile.settings['gcode_macro resume'] is defined %}
{% set _dummy = tl.macro.update({'resume': printer.configfile.settings['gcode_macro resume'].rename_existing}) %}
{% endif %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=macro VALUE="{tl.macro}"
##########################################################################
# #
# TIMELAPSE_TAKE_FRAME: take the next picture #
# #
##########################################################################
######################### definition #########################
## enable: enable or disable the next frame. Valid inputs: [True, False]
## takingframe: internal use. Valid inputs: [True, False]
##
## park.enable: enable or disable to park the head while taking a picture. Valid inputs: [True, False]
## park.pos : used position for parking. Valid inputs: [center, front_left, front_right, back_left, back_right, custom, x_only, y_only]
## park.time : used for the debug macro. Time in s
## park.custom.x, park.custom.y: coordinates of the custom parkposition. Unit [mm]
## park.custom.dz : custom z hop for the picture. Unit [mm]
## park.coord : internal use
##
## extruder.fw_retract: enable disable fw retraction [True,False]
## extruder.extrude : filament extruded at the end of park. Unit [mm]
## extruder.retract : filament retract at the start of park. Unit [mm]
##
## speed.travel : used speed for travel from and to the park positon. Unit: [mm/min]
## speed.retract: used speed for retract [mm/min]
## speed.extrude: used speed for extrude [mm/min]
##
## verbose: Enable mesage output of TIMELAPSE_TAKE_FRAME
##
## check_time: time when the status of the taken picture is checked. Default 0.5 sec
##
## restore.absolute.coordinates: internal use
## restore.absolute.extrude : internal use
## restore.speed : internal use
## restore.e : internal use
## restore.factor.speed : internal use
## restore.factor.extrude : internal use
##
## macro.pause : internal use
## macro.resume : internal use
##
## is_paused: internal use
###############################################################
[gcode_macro TIMELAPSE_TAKE_FRAME]
description: Take Timelapse shoot
variable_enable: False
variable_takingframe: False
variable_park: {'enable': False,
'pos' : 'center',
'time' : 0.1,
'custom': {'x': 0, 'y': 0, 'dz': 0},
'coord' : {'x': 0, 'y': 0, 'dz': 0}}
variable_extruder: {'fw_retract': False,
'retract': 1.0,
'extrude': 1.0}
variable_speed: {'travel': 100,
'retract': 15,
'extrude': 15}
variable_verbose: True
variable_check_time: 0.5
variable_restore: {'absolute': {'coordinates': True, 'extrude': True}, 'speed': 1500, 'e':0, 'factor': {'speed': 1.0, 'extrude': 1.0}}
variable_macro: {'pause': 'PAUSE', 'resume': 'RESUME'}
variable_is_paused: False
gcode:
{% set hyperlapse = True if params.HYPERLAPSE and params.HYPERLAPSE|lower =='true' else False %}
{% if enable %}
{% if (hyperlapse and printer['gcode_macro HYPERLAPSE'].run) or
(not hyperlapse and not printer['gcode_macro HYPERLAPSE'].run) %}
{% if park.enable %}
{% set pos = {'x': 'X' + park.coord.x|string if park.pos != 'y_only' else '',
'y': 'Y' + park.coord.y|string if park.pos != 'x_only' else '',
'z': 'Z'+ [printer.gcode_move.gcode_position.z + park.coord.dz, printer.toolhead.axis_maximum.z]|min|string} %}
{% set restore = {'absolute': {'coordinates': printer.gcode_move.absolute_coordinates,
'extrude' : printer.gcode_move.absolute_extrude},
'speed' : printer.gcode_move.speed,
'e' : printer.gcode_move.gcode_position.e,
'factor' : {'speed' : printer.gcode_move.speed_factor,
'extrude': printer.gcode_move.extrude_factor}} %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=restore VALUE="{restore}"
{% if not printer[printer.toolhead.extruder].can_extrude %}
{% if verbose %}{action_respond_info("Timelapse: Warning, minimum extruder temperature not reached!")}{% endif %}
{% else %}
{% if extruder.fw_retract %}
G10
{% else %}
M83 ; insure relative extrusion
G0 E-{extruder.retract} F{speed.retract * 60}
{% endif %}
{% endif %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=is_paused VALUE=True
{macro.pause} ; execute the klipper PAUSE command
SET_GCODE_OFFSET X=0 Y=0 ; this will insure that the head parks always at the same position in a multi setup
G90 ; insure absolute move
{% if "xyz" not in printer.toolhead.homed_axes %}
{% if verbose %}{action_respond_info("Timelapse: Warning, axis not homed yet!")}{% endif %}
{% else %}
G0 {pos.x} {pos.y} {pos.z} F{speed.travel * 60}
{% endif %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=takingframe VALUE=True
UPDATE_DELAYED_GCODE ID=_WAIT_TIMELAPSE_TAKE_FRAME DURATION={check_time}
M400
{% endif %}
_TIMELAPSE_NEW_FRAME HYPERLAPSE={hyperlapse}
{% endif %}
{% else %}
{% if verbose %}{action_respond_info("Timelapse: disabled, take frame ignored")}{% endif %}
{% endif %}
[gcode_macro _TIMELAPSE_NEW_FRAME]
description: action call for timelapse shoot. must be a seperate macro
gcode:
{action_call_remote_method("timelapse_newframe",
macropark=printer['gcode_macro TIMELAPSE_TAKE_FRAME'].park,
hyperlapse=params.HYPERLAPSE)}
[delayed_gcode _WAIT_TIMELAPSE_TAKE_FRAME]
gcode:
{% set tl = printer['gcode_macro TIMELAPSE_TAKE_FRAME'] %}
{% set factor = {'speed': printer.gcode_move.speed_factor, 'extrude': printer.gcode_move.extrude_factor} %}
{% if tl.takingframe %}
UPDATE_DELAYED_GCODE ID=_WAIT_TIMELAPSE_TAKE_FRAME DURATION={tl.check_time}
{% else %}
{tl.macro.resume} VELOCITY={tl.speed.travel} ; execute the klipper RESUME command
SET_GCODE_VARIABLE MACRO=TIMELAPSE_TAKE_FRAME VARIABLE=is_paused VALUE=False
{% if not printer[printer.toolhead.extruder].can_extrude %}
{action_respond_info("Timelapse: Warning minimum extruder temperature not reached!")}
{% else %}
{% if tl.extruder.fw_retract %}
G11
{% else %}
G0 E{tl.extruder.extrude} F{tl.speed.extrude * 60}
G0 F{tl.restore.speed}
{% if tl.restore.absolute.extrude %}
M82
G92 E{tl.restore.e}
{% endif %}
{% endif %}
{% endif %}
{% if tl.restore.factor.speed != factor.speed %} M220 S{(factor.speed*100)|round(0)} {% endif %}
{% if tl.restore.factor.extrude != factor.extrude %} M221 S{(factor.extrude*100)|round(0)} {% endif %}
{% if not tl.restore.absolute.coordinates %} G91 {% endif %}
{% endif %}
####################################################################################################
# #
# HYPERLAPSE: Starts or stops a Hyperlapse video #
# Usage: HYPERLAPSE ACTION=START [CYCLE=time] starts a hyperlapse with cycle time (default 30 sec) #
# HYPERLAPSE ACTION=STOP stops the hyperlapse recording #
# #
####################################################################################################
######################### definition #########################
## cycle: cycle time in seconds
## run: internal use [True/False]
###############################################################
[gcode_macro HYPERLAPSE]
description: Start/Stop a hyperlapse recording
variable_cycle: 0
variable_run: False
gcode:
{% set cycle = params.CYCLE|default(30)|int %}
{% if params.ACTION and params.ACTION|lower == 'start' %}
{action_respond_info("Hyperlapse: frames started (Cycle %d sec)" % cycle)}
SET_GCODE_VARIABLE MACRO=HYPERLAPSE VARIABLE=run VALUE=True
SET_GCODE_VARIABLE MACRO=HYPERLAPSE VARIABLE=cycle VALUE={cycle}
UPDATE_DELAYED_GCODE ID=_HYPERLAPSE_LOOP DURATION={cycle}
TIMELAPSE_TAKE_FRAME HYPERLAPSE=True
{% elif params.ACTION and params.ACTION|lower == 'stop' %}
{% if run %}{action_respond_info("Hyperlapse: frames stopped")}{% endif %}
SET_GCODE_VARIABLE MACRO=HYPERLAPSE VARIABLE=run VALUE=False
UPDATE_DELAYED_GCODE ID=_HYPERLAPSE_LOOP DURATION=0
{% else %}
{action_raise_error("Hyperlapse: No valid input parameter
Use:
- HYPERLAPSE ACTION=START [CYCLE=time]
- HYPERLAPSE ACTION=STOP")}
{% endif %}
[delayed_gcode _HYPERLAPSE_LOOP]
gcode:
UPDATE_DELAYED_GCODE ID=_HYPERLAPSE_LOOP DURATION={printer["gcode_macro HYPERLAPSE"].cycle}
TIMELAPSE_TAKE_FRAME HYPERLAPSE=True
##########################################################################
# #
# TIMELAPSE_RENDER: Render the video at print end #
# #
##########################################################################
######################### definition #########################
## render: internal use. Valid inputs: [True, False]
## run_identifier: internal use. Valid input [0 .. 3]
###############################################################
[gcode_macro TIMELAPSE_RENDER]
description: Render Timelapse video and wait for the result
variable_render: False
variable_run_identifier: 0
gcode:
{action_respond_info("Timelapse: Rendering started")}
{action_call_remote_method("timelapse_render", byrendermacro="True")}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_RENDER VARIABLE=render VALUE=True
{printer.configfile.settings['gcode_macro pause'].rename_existing} ; execute the klipper PAUSE command
UPDATE_DELAYED_GCODE ID=_WAIT_TIMELAPSE_RENDER DURATION=0.5
[delayed_gcode _WAIT_TIMELAPSE_RENDER]
gcode:
{% set ri = printer['gcode_macro TIMELAPSE_RENDER'].run_identifier % 4 %}
SET_GCODE_VARIABLE MACRO=TIMELAPSE_RENDER VARIABLE=run_identifier VALUE={ri + 1}
{% if printer['gcode_macro TIMELAPSE_RENDER'].render %}
M117 Rendering {['-','\\','|','/'][ri]}
UPDATE_DELAYED_GCODE ID=_WAIT_TIMELAPSE_RENDER DURATION=0.5
{% else %}
{action_respond_info("Timelapse: Rendering finished")}
M117
{printer.configfile.settings['gcode_macro resume'].rename_existing} ; execute the klipper RESUME command
{% endif %}
##########################################################################
# #
# TEST_STREAM_DELAY: Helper macro to find stream and park delay #
# #
##########################################################################
[gcode_macro TEST_STREAM_DELAY]
description: Helper macro to find stream and park delay
gcode:
{% set min = printer.toolhead.axis_minimum %}
{% set max = printer.toolhead.axis_maximum %}
{% set act = printer.toolhead.position %}
{% set tl = printer['gcode_macro TIMELAPSE_TAKE_FRAME'] %}
{% if act.z > 5.0 %}
G0 X{min.x + 5.0} F{tl.speed.travel|int * 60}
G0 X{(max.x-min.x)/2}
G4 P{tl.park.time|float * 1000}
_TIMELAPSE_NEW_FRAME HYPERLAPSE=FALSE
G0 X{max.x - 5.0}
{% else %}
{action_raise_error("Toolhead z %.3f to low. Please place head above z = 5.0" % act.z)}
{% endif %}

View file

@ -0,0 +1,842 @@
# Moonraker Timelapse component for K1 Series
#
# Copyright (C) 2021 Christoph Frei <fryakatkop@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from __future__ import annotations
import logging
import os
import glob
import re
import shutil
import asyncio
from datetime import datetime
from tornado.ioloop import IOLoop
from zipfile import ZipFile
# Annotation imports
from typing import (
TYPE_CHECKING,
Dict,
Any
)
if TYPE_CHECKING:
from confighelper import ConfigHelper
from .webcam import WebcamManager, WebCam
from websockets import WebRequest
from . import shell_command
from . import klippy_apis
from . import database
APIComp = klippy_apis.KlippyAPI
SCMDComp = shell_command.ShellCommandFactory
DBComp = database.MoonrakerDatabase
class Timelapse:
def __init__(self, confighelper: ConfigHelper) -> None:
# setup vars
self.renderisrunning = False
self.saveisrunning = False
self.takingframe = False
self.framecount = 0
self.lastframefile = ""
self.lastrenderprogress = 0
self.lastcmdreponse = ""
self.byrendermacro = False
self.hyperlapserunning = False
self.printing = False
self.noWebcamDb = False
self.confighelper = confighelper
self.server = confighelper.get_server()
self.klippy_apis: APIComp = self.server.lookup_component('klippy_apis')
self.database: DBComp = self.server.lookup_component("database")
# setup static (nonDB) settings
out_dir_cfg = confighelper.get(
"output_path", "~/timelapse/")
temp_dir_cfg = confighelper.get(
"frame_path", "/tmp/timelapse/")
self.ffmpeg_binary_path = confighelper.get(
"ffmpeg_binary_path", "/opt/bin/ffmpeg")
self.wget_skip_cert = confighelper.getboolean(
"wget_skip_cert_check", False)
# Setup default config
self.config: Dict[str, Any] = {
'enabled': True,
'mode': "layermacro",
'camera': "",
'snapshoturl': "http://localhost:8080/?action=snapshot",
'stream_delay_compensation': 0.05,
'gcode_verbose': False,
'parkhead': False,
'parkpos': "back_right",
'park_custom_pos_x': 10.0,
'park_custom_pos_y': 10.0,
'park_custom_pos_dz': 0.0,
'park_travel_speed': 400,
'park_retract_speed': 40,
'park_extrude_speed': 40,
'park_retract_distance': 0.5,
'park_extrude_distance': 0.5,
'park_time': 0.1,
'fw_retract': False,
'hyperlapse_cycle': 30,
'autorender': True,
'constant_rate_factor': 23,
'output_framerate': 30,
'pixelformat': "yuv420p",
'time_format_code': "%d-%m-%Y_%Hh%M",
'extraoutputparams': "",
'variable_fps': False,
'targetlength': 10,
'variable_fps_min': 5,
'variable_fps_max': 60,
'rotation': 0,
'flip_x': False,
'flip_y': False,
'duplicatelastframe': 5,
'previewimage': True,
'saveframes': False
}
# Get Config from Database and overwrite defaults
dbconfig: Dict[str, Any] = self.database.get_item("timelapse",
"config",
self.config)
if isinstance(dbconfig, asyncio.Future):
self.config.update(dbconfig.result())
else:
self.config.update(dbconfig)
# Overwrite Config with fixed config made in moonraker.conf
# this is a fallback to older setups and when the Frontend doesn't
# support the settings endpoint
self.overwriteDbconfigWithConfighelper()
# check if ffmpeg is installed
self.ffmpeg_installed = os.path.isfile(self.ffmpeg_binary_path)
if not self.ffmpeg_installed:
self.config['autorender'] = False
logging.info(f"timelapse: {self.ffmpeg_binary_path} \
not found please install to use render functionality")
# setup directories
# remove trailing "/"
out_dir_cfg = os.path.join(out_dir_cfg, '')
temp_dir_cfg = os.path.join(temp_dir_cfg, '')
# evaluate and expand "~"
self.out_dir = os.path.expanduser(out_dir_cfg)
self.temp_dir = os.path.expanduser(temp_dir_cfg)
# create directories if they doesn't exist
os.makedirs(self.temp_dir, exist_ok=True)
os.makedirs(self.out_dir, exist_ok=True)
# setup eventhandlers and endpoints
file_manager = self.server.lookup_component("file_manager")
file_manager.register_directory("timelapse",
self.out_dir,
full_access=True
)
file_manager.register_directory("timelapse_frames", self.temp_dir)
self.server.register_notification("timelapse:timelapse_event")
self.server.register_event_handler(
"server:gcode_response", self.handle_gcode_response)
self.server.register_event_handler(
"server:status_update", self.handle_status_update)
self.server.register_event_handler(
"server:klippy_ready", self.handle_klippy_ready)
self.server.register_remote_method(
"timelapse_newframe", self.call_newframe)
self.server.register_remote_method(
"timelapse_saveFrames", self.call_saveFramesZip)
self.server.register_remote_method(
"timelapse_render", self.call_render)
self.server.register_endpoint(
"/machine/timelapse/render", ['POST'], self.render)
self.server.register_endpoint(
"/machine/timelapse/saveframes", ['POST'], self.saveFramesZip)
self.server.register_endpoint(
"/machine/timelapse/settings", ['GET', 'POST'],
self.webrequest_settings)
self.server.register_endpoint(
"/machine/timelapse/lastframeinfo", ['GET'],
self.webrequest_lastframeinfo)
async def component_init(self) -> None:
await self.getWebcamConfig()
def overwriteDbconfigWithConfighelper(self) -> None:
blockedsettings = []
for config in self.confighelper.get_options():
if config in self.config:
configtype = type(self.config[config])
if configtype == str:
self.config[config] = self.confighelper.get(config)
elif configtype == bool:
self.config[config] = self.confighelper.getboolean(config)
elif configtype == int:
self.config[config] = self.confighelper.getint(config)
elif configtype == float:
self.config[config] = self.confighelper.getfloat(config)
# add the config to list of blockedsettings
blockedsettings.append(config)
# append the list of blockedsettings to the config dict
self.config.update({'blockedsettings': blockedsettings})
logging.debug(f"blockedsettings {self.config['blockedsettings']}")
async def getWebcamConfig(self) -> None:
# Read Webcam config from Database
webcam_name = self.config['camera']
try:
wcmgr: WebcamManager = self.server.lookup_component("webcam")
cams = wcmgr.get_webcams()
if not cams:
logging.info("WARNING: no camera configured, " +
"using the fallback config")
fallback = {'snapshot_url': self.config['snapshoturl'],
'rotation': self.config['rotation'],
'flip_horizontal': self.config['flip_x'],
'flip_vertical': self.config['flip_y']
}
self.parseWebcamConfig(fallback)
return
if webcam_name and webcam_name in cams:
camera = cams[webcam_name]
else:
camera = list(cams.values())[0]
self.parseWebcamConfig(camera.as_dict())
except Exception as e:
logging.info(f"something went wrong getting"
f"Cam Camera:{webcam_name} from Database. "
f"Exception: {e}"
)
def parseWebcamConfig(self, webcamconfig) -> None:
snapshoturl = webcamconfig['snapshot_url']
flip_x = webcamconfig['flip_horizontal']
flip_y = webcamconfig['flip_vertical']
rotation = webcamconfig['rotation']
oldWebcamConfig = {"url": self.config['snapshoturl'],
"flip_x": self.config['flip_x'],
"flip_y": self.config['flip_y'],
"rotation": self.config['rotation']
}
self.config['snapshoturl'] = self.confighelper.get('snapshoturl',
snapshoturl
)
self.config['flip_x'] = self.confighelper.getboolean('flip_x',
flip_x
)
self.config['flip_y'] = self.confighelper.getboolean('flip_y',
flip_y
)
self.config['rotation'] = self.confighelper.getint('rotation',
rotation
)
if not self.config['snapshoturl'].startswith('http'):
if not self.config['snapshoturl'].startswith('/'):
self.config['snapshoturl'] = "http://localhost/" + \
self.config['snapshoturl']
else:
self.config['snapshoturl'] = "http://localhost" + \
self.config['snapshoturl']
# check if settings have changed and if so creat log entry
newWebcamConfig = {"url": self.config['snapshoturl'],
"flip_x": self.config['flip_x'],
"flip_y": self.config['flip_y'],
"rotation": self.config['rotation']
}
if not oldWebcamConfig == newWebcamConfig:
logging.info("snapshoturl: "
f"{self.config['snapshoturl']}, "
f"Flip V/H: {self.config['flip_y']}/"
f"{self.config['flip_y']}, "
f"rotation: {self.config['rotation']}"
)
async def webrequest_lastframeinfo(self,
webrequest: WebRequest
) -> Dict[str, Any]:
return {
'framecount': self.framecount,
'lastframefile': self.lastframefile
}
async def webrequest_settings(self,
webrequest: WebRequest
) -> Dict[str, Any]:
action = webrequest.get_action()
if action == 'POST':
args = webrequest.get_args()
logging.debug("webreq_args: " + str(args))
gcodechange = False
settingsWithGcodechange = [
'enabled', 'parkhead',
'parkpos', 'park_custom_pos_x',
'park_custom_pos_y', 'park_custom_pos_dz',
'park_travel_speed', 'park_retract_speed',
'park_extrude_speed', 'park_retract_distance',
'park_extrude_distance', 'park_time', 'fw_retract'
]
modechanged = False
for setting in args:
if setting in self.config:
settingtype = type(self.config[setting])
if setting == "snapshoturl":
logging.debug(
"snapshoturl cannot be changed via webrequest")
elif settingtype == str:
settingvalue = webrequest.get(setting)
elif settingtype == bool:
settingvalue = webrequest.get_boolean(setting)
elif settingtype == int:
settingvalue = webrequest.get_int(setting)
elif settingtype == float:
settingvalue = webrequest.get_float(setting)
self.config[setting] = settingvalue
self.database.insert_item(
"timelapse",
f"config.{setting}",
settingvalue
)
if setting == "camera":
if not self.noWebcamDb:
await self.getWebcamConfig()
else:
logging.info("Webcam Namespace not intialized, "
"please restart moonraker service!")
if setting in settingsWithGcodechange:
gcodechange = True
if setting == "mode":
modechanged = True
logging.debug(f"changed setting: {setting} "
f"value: {settingvalue} "
f"type: {settingtype}"
)
if modechanged:
if self.config['mode'] == "hyperlapse":
if not self.hyperlapserunning:
if self.printing:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.start_hyperlapse)
else:
if self.hyperlapserunning:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.stop_hyperlapse)
if gcodechange:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.setgcodevariables)
return self.config
async def handle_klippy_ready(self) -> None:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.setgcodevariables)
ioloop = IOLoop.current()
ioloop.spawn_callback(self.stop_hyperlapse)
async def setgcodevariables(self) -> None:
gcommand = "_SET_TIMELAPSE_SETUP " \
+ f" ENABLE={self.config['enabled']}" \
+ f" VERBOSE={self.config['gcode_verbose']}" \
+ f" PARK_ENABLE={self.config['parkhead']}" \
+ f" PARK_POS={self.config['parkpos']}" \
+ f" CUSTOM_POS_X={self.config['park_custom_pos_x']}" \
+ f" CUSTOM_POS_Y={self.config['park_custom_pos_y']}" \
+ f" CUSTOM_POS_DZ={self.config['park_custom_pos_dz']}" \
+ f" TRAVEL_SPEED={self.config['park_travel_speed']}" \
+ f" RETRACT_SPEED={self.config['park_retract_speed']}" \
+ f" EXTRUDE_SPEED={self.config['park_extrude_speed']}" \
+ f" RETRACT_DISTANCE={self.config['park_retract_distance']}" \
+ f" EXTRUDE_DISTANCE={self.config['park_extrude_distance']}" \
+ f" PARK_TIME={self.config['park_time']}" \
+ f" FW_RETRACT={self.config['fw_retract']}" \
logging.debug(f"run gcommand: {gcommand}")
try:
await self.klippy_apis.run_gcode(gcommand)
except self.server.error:
msg = f"Error executing GCode {gcommand}"
logging.exception(msg)
def call_newframe(self, macropark=False, hyperlapse=False) -> None:
if self.config['enabled']:
if self.config['mode'] == "hyperlapse":
if hyperlapse:
if not self.takingframe:
self.takingframe = True
self.spawn_newframe_callbacks()
else:
logging.info("last take frame hasn't completed"
+ " ignoring take frame command"
)
else:
logging.info("ignoring non hyperlapse triggered macros"
+ "in hyperlapse mode"
)
else:
self.spawn_newframe_callbacks()
else:
logging.info("NEW_FRAME macro ignored timelapse is disabled")
def spawn_newframe_callbacks(self) -> None:
ioloop = IOLoop.current()
# release parked head after park time is passed
park_time = self.config['park_time']
ioloop.call_later(delay=park_time, callback=self.release_parkedhead)
# capture the frame after stream delay is passed
stream_delay = self.config['stream_delay_compensation']
ioloop.call_later(delay=stream_delay, callback=self.newframe)
async def release_parkedhead(self) -> None:
gcommand = "SET_GCODE_VARIABLE " \
+ "MACRO=TIMELAPSE_TAKE_FRAME " \
+ "VARIABLE=takingframe VALUE=False"
logging.debug(f"run gcommand: {gcommand}")
try:
await self.klippy_apis.run_gcode(gcommand)
except self.server.error:
msg = f"Error executing GCode {gcommand}"
logging.exception(msg)
async def start_hyperlapse(self) -> None:
hyperlapse_cycle = self.config['hyperlapse_cycle']
park_time = self.config['park_time']
timediff = hyperlapse_cycle - park_time
if timediff >= 1:
gcommand = "HYPERLAPSE ACTION=START" \
+ f" CYCLE={hyperlapse_cycle}"
logging.debug(f"run gcommand: {gcommand}")
try:
await self.klippy_apis.run_gcode(gcommand)
except self.server.error:
msg = f"Error executing GCode {gcommand}"
logging.exception(msg)
self.hyperlapserunning = True
else:
logging.info("WARNING: Blocked start of Hyperlapse, because "
f"hyperlapse_cycle ({hyperlapse_cycle}s) is smaller "
f"then or to close to park_time ({park_time}s)"
)
async def stop_hyperlapse(self) -> None:
gcommand = "HYPERLAPSE ACTION=STOP"
logging.debug(f"run gcommand: {gcommand}")
try:
await self.klippy_apis.run_gcode(gcommand)
except self.server.error:
msg = f"Error executing GCode {gcommand}"
logging.exception(msg)
self.hyperlapserunning = False
async def newframe(self) -> None:
# make sure webcamconfig is uptodate before grabbing a new frame
await self.getWebcamConfig()
options = ""
if self.wget_skip_cert:
options += "--no-check-certificate "
self.framecount += 1
framefile = "frame" + str(self.framecount).zfill(6) + ".jpg"
cmd = "wget " + options + self.config['snapshoturl'] \
+ " -O " + self.temp_dir + framefile
self.lastframefile = framefile
logging.debug(f"cmd: {cmd}")
shell_cmd: SCMDComp = self.server.lookup_component('shell_command')
scmd = shell_cmd.build_shell_command(cmd, None)
try:
cmdstatus = await scmd.run(timeout=2., verbose=False)
except Exception:
logging.exception(f"Error running cmd '{cmd}'")
result = {'action': 'newframe'}
if cmdstatus:
result.update({
'frame': str(self.framecount),
'framefile': framefile,
'status': 'success'
})
else:
logging.info(f"getting newframe failed: {cmd}")
self.framecount -= 1
result.update({'status': 'error'})
self.notify_event(result)
self.takingframe = False
async def handle_status_update(self, status: Dict[str, Any]) -> None:
if 'print_stats' in status:
printstats = status['print_stats']
if 'state' in printstats:
state = printstats['state']
if state == 'cancelled':
self.printing = False
ioloop = IOLoop.current()
ioloop.spawn_callback(self.stop_hyperlapse)
async def handle_gcode_response(self, gresponse: str) -> None:
if gresponse == "File selected":
# print_started
self.cleanup()
self.printing = True
# start hyperlapse if mode is set
if self.config['mode'] == "hyperlapse":
ioloop = IOLoop.current()
ioloop.spawn_callback(self.start_hyperlapse)
elif gresponse == "Done printing file":
# print_done
self.printing = False
# stop hyperlapse if mode is set
if self.config['mode'] == "hyperlapse":
ioloop = IOLoop.current()
ioloop.spawn_callback(self.stop_hyperlapse)
if self.config['enabled']:
if self.config['saveframes']:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.saveFramesZip)
if self.config['autorender']:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.render)
def cleanup(self) -> None:
logging.debug("cleanup frame directory")
filelist = glob.glob(self.temp_dir + "frame*.jpg")
if filelist:
for filepath in filelist:
os.remove(filepath)
self.framecount = 0
self.lastframefile = ""
def call_saveFramesZip(self) -> None:
ioloop = IOLoop.current()
ioloop.spawn_callback(self.saveFramesZip)
async def saveFramesZip(self, webrequest=None):
filelist = sorted(glob.glob(self.temp_dir + "frame*.jpg"))
self.framecount = len(filelist)
result = {'action': 'saveframes'}
if not filelist:
msg = "no frames to save, skip"
status = "skipped"
elif self.saveisrunning:
msg = "saving frames already"
status = "running"
else:
self.saveisrunning = True
# get printed filename
kresult = await self.klippy_apis.query_objects(
{'print_stats': None})
pstats = kresult.get("print_stats", {})
gcodefilename = pstats.get("filename", "").split("/")[-1]
# prepare output filename
now = datetime.now()
date_time = now.strftime(self.config['time_format_code'])
outfile = f"k1_{gcodefilename}_{date_time}"
outfileFull = outfile + "_frames.zip"
zipObj = ZipFile(self.out_dir + outfileFull, "w")
for frame in filelist:
zipObj.write(frame, frame.split("/")[-1])
logging.info(f"saved frames: {outfile}_frames.zip")
result.update({
'status': 'finished',
'zipfile': outfileFull
})
self.saveisrunning = False
return result
def call_render(self, byrendermacro=False) -> None:
self.byrendermacro = byrendermacro
ioloop = IOLoop.current()
ioloop.spawn_callback(self.render)
async def render(self, webrequest=None):
filelist = sorted(glob.glob(self.temp_dir + "frame*.jpg"))
self.framecount = len(filelist)
result = {'action': 'render'}
# make sure webcamconfig is uptodate for the rotation/flip feature
await self.getWebcamConfig()
if not filelist:
msg = "no frames to render, skip"
status = "skipped"
elif self.renderisrunning:
msg = "render is already running"
status = "running"
elif not self.ffmpeg_installed:
msg = f"{self.ffmpeg_binary_path} not found, please install ffmpeg"
status = "error"
# cmd = outfile = None
logging.info(f"timelapse: {msg}")
else:
self.renderisrunning = True
# get printed filename
kresult = await self.klippy_apis.query_objects(
{'print_stats': None})
pstats = kresult.get("print_stats", {})
gcodefilename = pstats.get("filename", "").split("/")[-1]
# prepare output filename
now = datetime.now()
date_time = now.strftime(self.config['time_format_code'])
inputfiles = self.temp_dir + "frame%6d.jpg"
outfile = f"k1_{gcodefilename}_{date_time}"
# dublicate last frame
duplicates = []
if self.config['duplicatelastframe'] > 0:
lastframe = filelist[-1:][0]
for i in range(self.config['duplicatelastframe']):
nextframe = str(self.framecount + i + 1).zfill(6)
duplicate = "frame" + nextframe + ".jpg"
duplicatePath = self.temp_dir + duplicate
duplicates.append(duplicatePath)
try:
shutil.copy(lastframe, duplicatePath)
except OSError as err:
logging.info(f"duplicating last frame failed: {err}")
# update Filelist
filelist = sorted(glob.glob(self.temp_dir + "frame*.jpg"))
self.framecount = len(filelist)
# variable framerate
if self.config['variable_fps']:
fps = int(self.framecount / self.config['targetlength'])
fps = max(min(fps,
self.config['variable_fps_max']),
self.config['variable_fps_min'])
else:
fps = self.config['output_framerate']
# apply rotation
filterParam = ""
if self.config['rotation'] == 90 and self.config['flip_y']:
filterParam = " -vf 'transpose=3'"
elif self.config['rotation'] == 90:
filterParam = " -vf 'transpose=1'"
elif self.config['rotation'] == 180:
filterParam = " -vf 'hflip,vflip'"
elif self.config['rotation'] == 270:
filterParam = " -vf 'transpose=2'"
elif self.config['rotation'] == 270 and self.config['flip_y']:
filterParam = " -vf 'transpose=0'"
elif self.config['rotation'] > 0:
pi = 3.141592653589793
rot = str(self.config['rotation']*(pi/180))
filterParam = " -vf 'rotate=" + rot + "'"
elif self.config['flip_x'] and self.config['flip_y']:
filterParam = " -vf 'hflip,vflip'"
elif self.config['flip_x']:
filterParam = " -vf 'hflip'"
elif self.config['flip_y']:
filterParam = " -vf 'vflip'"
# build shell command
cmd = self.ffmpeg_binary_path \
+ " -r " + str(fps) \
+ " -i '" + inputfiles + "'" \
+ filterParam \
+ " -threads 2 -g 5" \
+ " -crf " + str(self.config['constant_rate_factor']) \
+ " -vcodec libx264" \
+ " -pix_fmt " + self.config['pixelformat'] \
+ " -preset superfast" \
+ " -an" \
+ " " + self.config['extraoutputparams'] \
+ " '" + self.temp_dir + outfile + ".mp4' -y"
# log and notify ws
logging.info(f"start FFMPEG: {cmd}")
result.update({
'status': 'started',
'framecount': str(self.framecount),
'settings': {
'framerate': fps,
'crf': self.config['constant_rate_factor'],
'pixelformat': self.config['pixelformat']
}
})
# run the command
shell_cmd: SCMDComp = self.server.lookup_component('shell_command')
self.notify_event(result)
scmd = shell_cmd.build_shell_command(cmd, self.ffmpeg_cb)
try:
cmdstatus = await scmd.run(verbose=True,
log_complete=False,
timeout=9999999999,
)
except Exception:
logging.exception(f"Error running cmd '{cmd}'")
# check success
if cmdstatus:
status = "success"
msg = f"Rendering Video successful: {outfile}.mp4"
result.update({
'filename': f"{outfile}.mp4",
'printfile': gcodefilename
})
# result.pop("framecount")
result.pop("settings")
# move finished output file to output directory
try:
shutil.move(self.temp_dir + outfile + ".mp4",
self.out_dir + outfile + ".mp4")
except OSError as err:
logging.info(f"moving output file failed: {err}")
# copy image preview
if self.config['previewimage']:
previewFile = f"{outfile}.jpg"
previewFilePath = self.out_dir + previewFile
previewSrc = filelist[-1:][0]
try:
shutil.copy(previewSrc, previewFilePath)
except OSError as err:
logging.info(f"copying preview image failed: {err}")
else:
result.update({
'previewimage': previewFile
})
# apply rotation previewimage if needed
if filterParam or self.config['extraoutputparams']:
cmd = self.ffmpeg_binary_path \
+ " -i '" + previewFilePath + "'" \
+ filterParam \
+ " -an" \
+ " " + self.config['extraoutputparams'] \
+ " '" + previewFilePath + "' -y"
logging.info(f"Rotate preview image cmd: {cmd}")
scmd = shell_cmd.build_shell_command(cmd)
try:
cmdstatus = await scmd.run(verbose=True,
log_complete=False,
timeout=9999999999,
)
except Exception:
logging.exception(f"Error running cmd '{cmd}'")
else:
status = "error"
msg = f"Rendering Video failed: {cmd} : {self.lastcmdreponse}"
result.update({
'cmd': cmd,
'cmdresponse': self.lastcmdreponse
})
self.renderisrunning = False
# cleanup duplicates
if duplicates:
for dupe in duplicates:
try:
os.remove(dupe)
except OSError as err:
logging.info(f"remove duplicate failed: {err}")
# log and notify ws
logging.info(msg)
result.update({
'status': status,
'msg': msg
})
self.notify_event(result)
# confirm render finish to stop the render macro loop
if self.byrendermacro:
gcommand = "SET_GCODE_VARIABLE " \
+ "MACRO=TIMELAPSE_RENDER VARIABLE=render VALUE=False"
logging.debug(f"run gcommand: {gcommand}")
try:
await self.klippy_apis.run_gcode(gcommand)
except self.server.error:
msg = f"Error executing GCode {gcommand}"
logging.exception(msg)
self.byrendermacro = False
return result
def ffmpeg_cb(self, response):
# logging.debug(f"ffmpeg_cb: {response}")
self.lastcmdreponse = response.decode("utf-8")
try:
frame = re.search(
r'(?<=frame=)*(\d+)(?=.+fps)', self.lastcmdreponse
).group()
except AttributeError:
return
percent = int(frame) / self.framecount * 100
if percent > 100:
percent = 100
if self.lastrenderprogress != int(percent):
self.lastrenderprogress = int(percent)
# logging.debug(f"ffmpeg Progress: {self.lastrenderprogress}% ")
result = {
'action': 'render',
'status': 'running',
'progress': self.lastrenderprogress
}
self.notify_event(result)
def notify_event(self, result: Dict[str, Any]) -> None:
logging.debug(f"notify_event: {result}")
self.server.send_event("timelapse:timelapse_event", result)
def load_component(config: ConfigHelper) -> Timelapse:
return Timelapse(config)

View file

@ -0,0 +1,13 @@
klipper_mcu
webcamd
MoonCord
KlipperScreen
moonraker-telegram-bot
moonraker-obico
sonar
crowsnest
octoeverywhere
ratos-configurator
mobileraker
guppyscreen
Git-Backup

View file

@ -0,0 +1,103 @@
[server]
host: 0.0.0.0
port: 7125
klippy_uds_address: /tmp/klippy_uds
max_upload_size: 1024
[file_manager]
queue_gcode_uploads: False
enable_object_processing: True
[database]
[data_store]
temperature_store_size: 600
gcode_store_size: 1000
[machine]
provider: supervisord_cli
validate_service: False
validate_config: False
[authorization]
force_logins: False
cors_domains:
*.lan
*.local
*://localhost
*://localhost:*
*://my.mainsail.xyz
*://app.fluidd.xyz
trusted_clients:
10.0.0.0/8
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.168.0.0/16
FE80::/10
::1/128
[octoprint_compat]
[history]
[update_manager]
enable_auto_refresh: True
refresh_interval: 24
enable_system_updates: False
# Remove '#' after this line to keep Creality Helper Script up to date
[update_manager Creality-Helper-Script]
type: git_repo
channel: dev
path: /usr/data/helper-script
origin: https://github.com/Guilouz/Creality-Helper-Script.git
primary_branch: master
managed_services: klipper
# Remove '#' after this line to enable camera configuration with Moonraker and replace 'xxx.xxx.xxx.xxx' by your IP addresses
#[webcam Camera]
#location: printer
#enabled: True
#service: mjpegstreamer
#target_fps: 15
#target_fps_idle: 5
#stream_url: http://xxx.xxx.xxx.xxx:8080/?action=stream
#snapshot_url: http://xxx.xxx.xxx.xxx:8080/?action=snapshot
#flip_horizontal: False
#flip_vertical: False
#rotation: 0
#aspect_ratio: 4:3
# Remove '#' after this line if you use Timelapse function and replace port '4408' by '4409' in snapshoturl if you use Mainsail
#[timelapse]
#output_path: /usr/data/printer_data/timelapse/
#frame_path: /usr/data/printer_data/frames/
#ffmpeg_binary_path: /opt/bin/ffmpeg
#snapshoturl: http://localhost:8080/?action=snapshot
# Remove '#' after this line if you use Fluidd
#[update_manager fluidd]
#type: web
#channel: beta
#repo: fluidd-core/fluidd
#path: /usr/data/fluidd
# Remove '#' after this line if you use Mainsail
#[update_manager mainsail]
#type: web
#channel: beta
#repo: mainsail-crew/mainsail
#path: /usr/data/mainsail
# Remove '#' after this line if you use Mobileraker Companion
#[update_manager mobileraker]
#type: git_repo
#path: /usr/data/mobileraker_companion
#origin: https://github.com/Clon1998/mobileraker_companion.git
#virtualenv: /usr/data/mobileraker-env
#primary_branch:main
#requirements: scripts/mobileraker-requirements.txt
#install_script: scripts/install.sh
#managed_services: mobileraker

Binary file not shown.

View file

@ -0,0 +1,5 @@
from .prtouch_v2_fan import PRTouchFan
def load_config(config):
return PRTouchFan(config)

View file

@ -0,0 +1,6 @@
########################################
# Nozzle Cleaning Fan Control
########################################
[prtouch_v2_fan]
max_speed: 0.5

Binary file not shown.

View file

@ -0,0 +1,25 @@
########################################
# Screws Tilt Adjust for K1
########################################
[screws_tilt_adjust]
screw1: 25,20
screw1_name: front left screw
screw2: 195,20
screw2_name: front right screw
screw3: 195,190
screw3_name: rear right screw
screw4: 25,190
screw4_name: rear left screw
speed: 100
horizontal_move_z: 5
screw_thread: CW-M4
[gcode_macro SCREWS_CALIBRATION]
description: Start Bed Screws Calibration
gcode:
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
SCREWS_TILT_CALCULATE

View file

@ -0,0 +1,25 @@
########################################
# Screws Tilt Adjust for K1 Max
########################################
[screws_tilt_adjust]
screw1: 19,23
screw1_name: front left screw
screw2: 278,23
screw2_name: front right screw
screw3: 248,272
screw3_name: rear right screw
screw4: 48,272
screw4_name: rear left screw
horizontal_move_z: 5
speed: 150
screw_thread: CW-M4
[gcode_macro SCREWS_CALIBRATION]
description: Start Bed Screws Calibration
gcode:
{% if printer.toolhead.homed_axes != "xyz" %}
G28
{% endif %}
SCREWS_TILT_CALCULATE

View file

@ -0,0 +1,131 @@
# Helper script to adjust bed screws tilt using Z probe
#
# Copyright (C) 2019 Rui Caridade <rui.mcbc@gmail.com>
# Copyright (C) 2021 Matthew Lloyd <github@matthewlloyd.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math
from . import probe
class ScrewsTiltAdjust:
def __init__(self, config):
self.config = config
self.printer = config.get_printer()
self.screws = []
self.results = []
self.max_diff = None
self.max_diff_error = False
# Read config
for i in range(99):
prefix = "screw%d" % (i + 1,)
if config.get(prefix, None) is None:
break
screw_coord = config.getfloatlist(prefix, count=2)
screw_name = "screw at %.3f,%.3f" % screw_coord
screw_name = config.get(prefix + "_name", screw_name)
self.screws.append((screw_coord, screw_name))
if len(self.screws) < 3:
raise config.error("screws_tilt_adjust: Must have "
"at least three screws")
self.threads = {'CW-M3': 0, 'CCW-M3': 1, 'CW-M4': 2, 'CCW-M4': 3,
'CW-M5': 4, 'CCW-M5': 5, 'CW-M6': 6, 'CCW-M6': 7}
self.thread = config.getchoice('screw_thread', self.threads,
default='CW-M3')
# Initialize ProbePointsHelper
points = [coord for coord, name in self.screws]
self.probe_helper = probe.ProbePointsHelper(self.config,
self.probe_finalize,
default_points=points)
self.probe_helper.minimum_points(3)
# Register command
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("SCREWS_TILT_CALCULATE",
self.cmd_SCREWS_TILT_CALCULATE,
desc=self.cmd_SCREWS_TILT_CALCULATE_help)
cmd_SCREWS_TILT_CALCULATE_help = "Tool to help adjust bed leveling " \
"screws by calculating the number " \
"of turns to level it."
def cmd_SCREWS_TILT_CALCULATE(self, gcmd):
self.max_diff = gcmd.get_float("MAX_DEVIATION", None)
# Option to force all turns to be in the given direction (CW or CCW)
direction = gcmd.get("DIRECTION", default=None)
if direction is not None:
direction = direction.upper()
if direction not in ('CW', 'CCW'):
raise gcmd.error(
"Error on '%s': DIRECTION must be either CW or CCW" % (
gcmd.get_commandline(),))
self.direction = direction
self.probe_helper.start_probe(gcmd)
def get_status(self, eventtime):
return {'error': self.max_diff_error,
'max_deviation': self.max_diff,
'results': self.results}
def probe_finalize(self, offsets, positions):
self.results = {}
self.max_diff_error = False
# Factors used for CW-M3, CCW-M3, CW-M4, CCW-M4, CW-M5, CCW-M5, CW-M6
#and CCW-M6
threads_factor = {0: 0.5, 1: 0.5, 2: 0.7, 3: 0.7, 4: 0.8, 5: 0.8,
6: 1.0, 7: 1.0}
is_clockwise_thread = (self.thread & 1) == 0
screw_diff = []
# Process the read Z values
if self.direction is not None:
# Lowest or highest screw is the base position used for comparison
use_max = ((is_clockwise_thread and self.direction == 'CW')
or (not is_clockwise_thread and self.direction == 'CCW'))
min_or_max = max if use_max else min
i_base, z_base = min_or_max(
enumerate([pos[2] for pos in positions]), key=lambda v: v[1])
else:
# First screw is the base position used for comparison
i_base, z_base = 0, positions[0][2]
# Provide the user some information on how to read the results
self.gcode.respond_info("01:20 means 1 full turn and 20 minutes, "
"CW=clockwise, CCW=counter-clockwise")
for i, screw in enumerate(self.screws):
z = positions[i][2]
coord, name = screw
if i == i_base:
# Show the results
self.gcode.respond_info(
"%s : x=%.1f, y=%.1f, z=%.5f" %
(name + ' (base)', coord[0], coord[1], z))
sign = "CW" if is_clockwise_thread else "CCW"
self.results["screw%d" % (i + 1,)] = {'z': z, 'sign': sign,
'adjust': '00:00', 'is_base': True}
else:
# Calculate how knob must be adjusted for other positions
diff = z_base - z
screw_diff.append(abs(diff))
if abs(diff) < 0.001:
adjust = 0
else:
adjust = diff / threads_factor.get(self.thread, 0.5)
if is_clockwise_thread:
sign = "CW" if adjust >= 0 else "CCW"
else:
sign = "CCW" if adjust >= 0 else "CW"
adjust = abs(adjust)
full_turns = math.trunc(adjust)
decimal_part = adjust - full_turns
minutes = round(decimal_part * 60, 0)
# Show the results
self.gcode.respond_info(
"%s : x=%.1f, y=%.1f, z=%.5f : adjust %s %02d:%02d" %
(name, coord[0], coord[1], z, sign, full_turns, minutes))
self.results["screw%d" % (i + 1,)] = {'z': z, 'sign': sign,
'adjust':"%02d:%02d" % (full_turns, minutes),
'is_base': False}
if self.max_diff and any((d > self.max_diff) for d in screw_diff):
self.max_diff_error = True
raise self.gcode.error(
"bed level exceeds configured limits ({}mm)! " \
"Adjust screws and restart print.".format(self.max_diff))
def load_config(config):
return ScrewsTiltAdjust(config)

72
files/scripts/useful_macros.sh Executable file
View file

@ -0,0 +1,72 @@
#!/bin/sh
set -e
function backup_klipper(){
if [ -f /usr/data/printer_data/config/backup_config.tar.gz ]; then
rm -f /usr/data/printer_data/config/backup_config.tar.gz
fi
cd /usr/data/printer_data
echo -e "Info: Compressing files..."
tar -czvf /usr/data/printer_data/config/backup_config.tar.gz config
echo -e "Info: Klipper configuration files have been saved successfully!"
exit 0
}
function restore_klipper(){
if [ ! -f /usr/data/printer_data/config/backup_config.tar.gz ]; then
echo -e "Info: Please backup Klipper configuration files before restore!"
exit 1
fi
cd /usr/data/printer_data
mv config/backup_config.tar.gz backup_config.tar.gz
if [ -d config ]; then
rm -rf config
fi
echo -e "Info: Restoring files..."
tar -xvf backup_config.tar.gz
mv backup_config.tar.gz config/backup_config.tar.gz
echo -e "Info: Klipper configuration files have been restored successfully!"
exit 0
}
function backup_moonraker(){
if [ -f /usr/data/printer_data/config/backup_database.tar.gz ]; then
rm -f /usr/data/printer_data/config/backup_database.tar.gz
fi
cd /usr/data/printer_data
echo -e "Info: Compressing files..."
tar -czvf /usr/data/printer_data/config/backup_database.tar.gz database
echo -e "Info: Moonraker database has been saved successfully!"
exit 0
}
function restore_moonraker(){
if [ ! -f /usr/data/printer_data/config/backup_database.tar.gz ]; then
echo -e "Info: Please backup Moonraker database before restore!"
exit 1
fi
cd /usr/data/printer_data
mv config/backup_database.tar.gz backup_database.tar.gz
if [ -d database ]; then
rm -rf database
fi
echo -e "Info: Restoring files..."
tar -xvf backup_database.tar.gz
mv backup_database.tar.gz config/backup_database.tar.gz
echo -e "Info: Moonraker database has been restored successfully!"
exit 0
}
if [ "$1" == "-backup_klipper" ]; then
backup_klipper
elif [ "$1" == "-restore_klipper" ]; then
restore_klipper
elif [ "$1" == "-backup_moonraker" ]; then
backup_moonraker
elif [ "$1" == "-restore_moonraker" ]; then
restore_moonraker
else
echo -e "Invalid argument. Usage: $0 [-backup_klipper | -restore_klipper | -backup_moonraker | -restore_moonraker]"
exit 1
fi

32
files/services/S50nginx Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
#
# Start/stop nginx
#
NGINX=/usr/data/nginx/sbin/nginx
PIDFILE=/var/run/nginx.pid
NGINX_ARGS="-c /usr/data/nginx/nginx/nginx.conf"
case "$1" in
start)
echo "Starting nginx..."
mkdir -p /var/log/nginx /var/tmp/nginx
start-stop-daemon -S -p "$PIDFILE" --exec "$NGINX" -- $NGINX_ARGS
;;
stop)
echo "Stopping nginx..."
start-stop-daemon -K -x "$NGINX" -p "$PIDFILE" -o
;;
reload|force-reload)
echo "Reloading nginx configuration..."
"$NGINX" -s reload
;;
restart)
"$0" stop
sleep 1 # Prevent race condition: ensure nginx stops before start.
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart|reload|force-reload}"
exit 1
esac

View file

@ -0,0 +1,54 @@
#!/bin/sh
#
# Starts klipper service.
#
USER_DATA=/usr/data
PROG=/usr/share/klippy-env/bin/python
PY_SCRIPT=/usr/share/klipper/klippy/klippy.py
PRINTER_DATA_DIR=$USER_DATA/printer_data
PRINTER_CONFIG_DIR=$PRINTER_DATA_DIR/config
PRINTER_LOGS_DIR=$PRINTER_DATA_DIR/logs
PID_FILE=/var/run/klippy.pid
mcu_reset()
{
[ -z $(pidof klipper_mcu) ] || /etc/init.d/S57klipper_mcu restart
}
start() {
mcu_reset
HOME=/root start-stop-daemon -S -q -b -m -p $PID_FILE \
--exec $PROG -- $PY_SCRIPT \
$PRINTER_CONFIG_DIR/printer.cfg \
-l $PRINTER_LOGS_DIR/klippy.log \
-a /tmp/klippy_uds
}
stop() {
start-stop-daemon -K -q -p $PID_FILE
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?

View file

@ -0,0 +1,51 @@
#!/bin/sh
#
# Starts moonraker service.
#
USER_DATA=/usr/data
PROG=/usr/data/moonraker/moonraker-env/bin/python
PY_SCRIPT=/usr/data/moonraker/moonraker/moonraker/moonraker.py
DEFAULT_CFG=/usr/data/moonraker//moonraker/moonraker.conf
PRINTER_DATA_DIR=$USER_DATA/printer_data
PRINTER_CONFIG_DIR=$PRINTER_DATA_DIR/config
PRINTER_LOGS_DIR=$PRINTER_DATA_DIR/logs
PID_FILE=/var/run/moonraker.pid
start() {
[ -d $PRINTER_DATA_DIR ] || mkdir -p $PRINTER_DATA_DIR
[ -d $PRINTER_CONFIG_DIR ] || mkdir -p $PRINTER_CONFIG_DIR
[ -d $PRINTER_LOGS_DIR ] || mkdir -p $PRINTER_LOGS_DIR
[ -s $PRINTER_CONFIG_DIR/moonraker.conf ] || cp $DEFAULT_CFG $PRINTER_CONFIG_DIR/moonraker.conf
rm -rf /usr/data/moonraker/tmp; mkdir -p /usr/data/moonraker/tmp
TMPDIR=/usr/data/moonraker/tmp HOME=/root start-stop-daemon -S -q -b -m -p $PID_FILE \
--exec $PROG -- $PY_SCRIPT -d $PRINTER_DATA_DIR
}
stop() {
start-stop-daemon -K -q -p $PID_FILE
}
restart() {
stop
sleep 1
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?

69
helper.sh Executable file
View file

@ -0,0 +1,69 @@
#!/bin/sh
set -e
clear
HELPER_SCRIPT_FOLDER="$( cd "$( dirname "${0}" )" && pwd )"
for script in "${HELPER_SCRIPT_FOLDER}/scripts/"*.sh; do . "${script}"; done
for script in "${HELPER_SCRIPT_FOLDER}/scripts/menu/"*.sh; do . "${script}"; done
for script in "${HELPER_SCRIPT_FOLDER}/scripts/menu/KE/"*.sh; do . "${script}"; done
function update_helper_script() {
echo -e "${white}"
echo -e "Info: Updating Creality Helper Script..."
cd "${HELPER_SCRIPT_FOLDER}"
git reset --hard && git pull
ok_msg "Creality Helper Script has been updated!"
echo -e " ${green}Please restart script to load the new version.${white}"
echo
exit 0
}
function update_available() {
[[ ! -d "${HELPER_SCRIPT_FOLDER}/.git" ]] && return
local remote current
cd "${HELPER_SCRIPT_FOLDER}"
! git branch -a | grep -q "\* main" && return
git fetch -q > /dev/null 2>&1
remote=$(git rev-parse --short=8 FETCH_HEAD)
current=$(git rev-parse --short=8 HEAD)
if [[ ${remote} != "${current}" ]]; then
echo "true"
fi
}
function update_menu() {
local update_available=$(update_available)
if [[ "$update_available" == "true" ]]; then
top_line
title "A new script version is available!" "${green}"
inner_line
hr
echo -e "${cyan}It's recommended to keep script up to date. Updates usually ${white}"
echo -e "${cyan}contain bug fixes, important changes or new features. ${white}"
echo -e "${cyan}Please consider updating! ${white}"
hr
echo -e " │ See changelog here: ${yellow}https://tinyurl.com/223jc4zr ${white}"
hr
bottom_line
local yn
while true; do
read -p " Do you want to update now? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
run "update_helper_script"
break;;
N|n)
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
fi
}
rm -rf /root/.cache
set_paths
set_permissions
update_menu
main_menu

View file

@ -0,0 +1,77 @@
#!/bin/sh
set -e
function backup_klipper_config_files_message(){
top_line
title 'Backup Klipper configuration files' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to backup Klipper configuration files in a ${white}"
echo -e "${cyan}backup_config.tar.gz compressed file. ${white}"
hr
bottom_line
}
function restore_klipper_config_files_message(){
top_line
title 'Restore Klipper configuration files' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to restore Klipper configuration files from a ${white}"
echo -e "${cyan}backup_config.tar.gz compressed file. ${white}"
hr
bottom_line
}
function backup_klipper_config_files(){
backup_klipper_config_files_message
local yn
while true; do
backup_msg "Klipper configuration files" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$KLIPPER_CONFIG_FOLDER"/backup_config.tar.gz ]; then
rm -f "$KLIPPER_CONFIG_FOLDER"/backup_config.tar.gz
fi
cd "$PRINTER_DATA_FOLDER"
echo -e "Info: Compressing files..."
tar -czvf "$KLIPPER_CONFIG_FOLDER"/backup_config.tar.gz config
ok_msg "Klipper configuration files have been saved successfully in ${yellow}/usr/data/printer_data/config ${green}folder!"
return;;
N|n)
error_msg "Backup canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restore_klipper_config_files(){
restore_klipper_config_files_message
local yn
while true; do
restore_msg "Klipper configuration files" yn
case "${yn}" in
Y|y)
echo -e "${white}"
cd "$PRINTER_DATA_FOLDER"
mv config/backup_config.tar.gz backup_config.tar.gz
if [ -d config ]; then
rm -rf config
fi
echo -e "Info: Restoring files..."
tar -xvf backup_config.tar.gz
mv backup_config.tar.gz config/backup_config.tar.gz
ok_msg "Klipper configuration files have been restored successfully!"
return;;
N|n)
error_msg "Restoration canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

View file

@ -0,0 +1,77 @@
#!/bin/sh
set -e
function backup_moonraker_database_message(){
top_line
title 'Backup Moonraker database' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to backup Moonraker database in a ${white}"
echo -e "${cyan}backup_database.tar.gz compressed file. ${white}"
hr
bottom_line
}
function restore_moonraker_database_message(){
top_line
title 'Restore Moonraker database' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to restore Moonraker database from a ${white}"
echo -e "${cyan}backup_database.tar.gz compressed file. ${white}"
hr
bottom_line
}
function backup_moonraker_database(){
backup_moonraker_database_message
local yn
while true; do
backup_msg "Moonraker database" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$KLIPPER_CONFIG_FOLDER"/backup_database.tar.gz ]; then
rm -f "$KLIPPER_CONFIG_FOLDER"/backup_database.tar.gz
fi
cd "$PRINTER_DATA_FOLDER"
echo -e "Info: Compressing files..."
tar -czvf "$KLIPPER_CONFIG_FOLDER"/backup_database.tar.gz database
ok_msg "Moonraker database has been saved successfully in ${yellow}/usr/data/printer_data/config ${green}folder!"
return;;
N|n)
error_msg "Backup canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restore_moonraker_database(){
restore_moonraker_database_message
local yn
while true; do
restore_msg "Moonraker database" yn
case "${yn}" in
Y|y)
echo -e "${white}"
cd "$PRINTER_DATA_FOLDER"
mv config/backup_database.tar.gz backup_database.tar.gz
if [ -d database ]; then
rm -rf database
fi
echo -e "Info: Restoring files..."
tar -xvf backup_database.tar.gz
mv backup_database.tar.gz config/backup_database.tar.gz
ok_msg "Moonraker database has been restored successfully!"
return;;
N|n)
error_msg "Restoration canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

81
scripts/buzzer_support.sh Executable file
View file

@ -0,0 +1,81 @@
#!/bin/sh
set -e
function buzzer_support_message(){
top_line
title 'Buzzer Support' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to play sounds using the motherboard buzzer. ${white}"
hr
bottom_line
}
function install_buzzer_support(){
buzzer_support_message
local yn
while true; do
install_msg "Buzzer Support" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/buzzer-support.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/buzzer-support.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Linking file..."
ln -sf "$BUZZER_URL" "$HS_CONFIG_FOLDER"/buzzer-support.cfg
if grep -q "include Helper-Script/buzzer-support" "$PRINTER_CFG" ; then
echo -e "Info: Buzzer Support configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Buzzer Support configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/buzzer-support\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Buzzer Support has been installed successfully!"
echo -e " You can now use ${yellow}BEEP ${white}command in your macros to play sound."
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_buzzer_support(){
buzzer_support_message
local yn
while true; do
remove_msg "Buzzer Support" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing file..."
rm -f "$HS_CONFIG_FOLDER"/buzzer-support.cfg
if grep -q "include Helper-Script/buzzer-support" "$PRINTER_CFG" ; then
echo -e "Info: Removing Buzzer Support configurations in printer.cfg file..."
sed -i '/include Helper-Script\/buzzer-support\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Buzzer Support configurations are already removed in printer.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Buzzer Support has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

View file

@ -0,0 +1,81 @@
#!/bin/sh
set -e
function camera_settings_control_message(){
top_line
title 'Camera Settings Control' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to install needed macros to adjust camera ${white}"
echo -e "${cyan}settings like brightness, saturation, contrast, etc... ${white}"
hr
bottom_line
}
function install_camera_settings_control(){
camera_settings_control_message
local yn
while true; do
install_msg "Camera Settings Control" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/camera-settings.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/camera-settings.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Linking file..."
cp "$CAMERA_SETTINGS_URL" "$HS_CONFIG_FOLDER"/camera-settings.cfg
if grep -q "include Helper-Script/camera-settings" "$PRINTER_CFG" ; then
echo -e "Info: Camera Settings configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Camera Settings configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/camera-settings\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Camera Settings Control has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_camera_settings_control(){
camera_settings_control_message
local yn
while true; do
remove_msg "Camera Settings Control" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing file..."
"$HS_CONFIG_FOLDER"/camera-settings.cfg
if grep -q "include Helper-Script/camera-settings" "$PRINTER_CFG" ; then
echo -e "Info: Removing Camera Settings configurations in printer.cfg file..."
sed -i '/include Helper-Script\/camera-settings\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Camera Settings configurations are already removed in printer.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Camera Settings Control has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

View file

@ -0,0 +1,39 @@
#!/bin/sh
set -e
function creality_dynamic_logos_message(){
top_line
title 'Creality Dynamic Logos for Fluidd' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to have the dynamic Creality logos on the Fluidd ${white}"
echo -e "${cyan}Web interface. ${white}"
hr
bottom_line
}
function install_creality_dynamic_logos(){
creality_dynamic_logos_message
local yn
while true; do
install_msg "Creality Dynamic Logos for Fluidd" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Copying files..."
cp "$FLUIDD_LOGO_URL1" "$FLUIDD_FOLDER"/logo_creality_v1.svg
cp "$FLUIDD_LOGO_URL2" "$FLUIDD_FOLDER"/logo_creality_v2.svg
rm -f "$FLUIDD_FOLDER"/config.json
cp "$FLUIDD_LOGO_URL3" "$FLUIDD_FOLDER"/config.json
ok_msg "Creality Dynamic Logos for Fluidd have been installed successfully!"
echo -e " You can now select ${yellow}Creality V1 ${white}or ${yellow}Creality V2 ${white}theme in Fluidd settings."
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

138
scripts/creality_web_interface.sh Executable file
View file

@ -0,0 +1,138 @@
#!/bin/sh
set -e
function remove_creality_web_interface_message(){
top_line
title 'Remove Creality Web Interface' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to remove Creality Web Interface and replace ${white}"
echo -e "${cyan}it with Fluidd or Mainsail on port 80. ${white}"
hr
bottom_line
}
function restore_creality_web_interface_message(){
top_line
title 'Restore Creality Web Interface' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to restore Creality Web Interface on port 80. ${white}"
hr
bottom_line
}
function remove_creality_web_interface(){
remove_creality_web_interface_message
local yn
while true; do
remove_msg "Creality Web Interface" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Disabling files..."
if [ -f /usr/bin/web-server ]; then
mv /usr/bin/web-server /usr/bin/web-server.disabled
fi
if [ -f /usr/bin/Monitor ]; then
mv /usr/bin/Monitor /usr/bin/Monitor.disabled
fi
echo -e "Info: Stopping services..."
set +e
killall -q Monitor
killall -q web-server
set -e
echo
if [ -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
echo -e "Info: Applying changes..."
sed -i '/listen 4408 default_server;/a \ listen 80;' /usr/data/nginx/nginx/nginx.conf
echo -e "Info: Restarting Nginx service..."
restart_nginx
ok_msg "Creality Web Interface has been removed successfully!"
echo -e " ${white}You can now connect to Fluidd Web Interface with ${yellow}http://$(check_ipaddress)${white}"
elif [ ! -d "$FLUIDD_FOLDER" ] && [ -d "$FLUIDD_FOLDER" ]; then
echo -e "Info: Applying changes..."
sed -i '/listen 4409 default_server;/a \ listen 80;' /usr/data/nginx/nginx/nginx.conf
echo -e "Info: Restarting Nginx service..."
restart_nginx
ok_msg "Creality Web Interface has been removed successfully!"
echo -e " ${white}You can now connect to Mainsail Web Interface with ${yellow}http://$(check_ipaddress)${white}"
elif [ -d "$FLUIDD_FOLDER" ] && [ -d "$FLUIDD_FOLDER" ]; then
local interface_choice
while true; do
read -p " ${white}Which Web Interface do you want to set as default (on port 80)? (${yellow}fluidd${white}/${yellow}mainsail${white}): ${yellow}" interface_choice
case "${interface_choice}" in
FLUIDD|fluidd)
echo -e "${white}"
echo -e "Info: Applying changes..."
sed -i '/listen 4408 default_server;/a \ listen 80;' /usr/data/nginx/nginx/nginx.conf
echo -e "Info: Restarting Nginx service..."
restart_nginx
ok_msg "Creality Web Interface has been removed successfully!"
echo -e " You can now connect to Fluidd Web Interface with ${yellow}http://$(check_ipaddress) ${white}or ${yellow}http://$(check_ipaddress):4408${white}"
break;;
MAINSAIL|mainsail)
echo -e "${white}"
echo -e "Info: Applying changes..."
sed -i '/listen 4409 default_server;/a \ listen 80;' /usr/data/nginx/nginx/nginx.conf
echo -e "Info: Restarting Nginx service..."
restart_nginx
ok_msg "Creality Web Interface has been removed successfully!"
echo -e " You can now connect to Mainsail Web Interface with ${yellow}http://$(check_ipaddress) ${white}or ${yellow}http://$(check_ipaddress):4409${white}"
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
fi
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restore_creality_web_interface(){
restore_creality_web_interface_message
local yn
while true; do
restore_msg "Creality Web Interface" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoring files..."
if [ -f /usr/bin/web-server.disabled ] && [ -f "$INITD_FOLDER"/S99start_app ]; then
mv /usr/bin/web-server.disabled /usr/bin/web-server
fi
if [ -f /usr/bin/Monitor.disabled ] && [ ! -d "$GUPPY_SCREEN_FOLDER" ]; then
mv /usr/bin/Monitor.disabled /usr/bin/Monitor
fi
echo -e "Info: Restoring changes..."
sed -i '/listen 80;/d' /usr/data/nginx/nginx/nginx.conf
echo -e "Info: Restarting services..."
restart_nginx
set +e
killall -q Monitor
killall -q web-server
set -e
if [ -f /usr/bin/web-server.disabled ] && [ -f "$INITD_FOLDER"/S99start_app ]; then
/usr/bin/web-server > /dev/null 2>&1 &
fi
if [ -f /usr/bin/Monitor.disabled ] && [ ! -d "$GUPPY_SCREEN_FOLDER" ]; then
/usr/bin/Monitor > /dev/null 2>&1 &
fi
ok_msg "Creality Web Interface has been restored successfully!"
echo -e " You can now connect to Creality Web Interface with ${yellow}http://$(check_ipaddress) ${white}and with ${yellow}Creality Print${white}."
return;;
N|n)
error_msg "Restoration canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

91
scripts/custom_boot_display.sh Executable file
View file

@ -0,0 +1,91 @@
#!/bin/sh
set -e
function install_custom_boot_display_message(){
top_line
title 'Install Custom Boot Display' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to install a custom Creality-themed boot ${white}"
echo -e "${cyan}display. ${white}"
hr
bottom_line
}
function remove_custom_boot_display_message(){
top_line
title 'Remove Custom Boot Display' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to restore stock boot display. ${white}"
hr
bottom_line
}
function install_custom_boot_display(){
install_custom_boot_display_message
local yn
while true; do
install_msg "Custom Boot Display" yn
case "${yn}" in
Y|y)
echo -e "${white}"
local printer_choice
while true; do
read -p " ${white}Do you want install it for ${yellow}K1${white} or ${yellow}K1 Max${white}? (${yellow}k1${white}/${yellow}k1max${white}): ${yellow}" printer_choice
case "${printer_choice}" in
K1|k1)
echo -e "${white}"
echo -e "Info: Removing stock files..."
rm -rf "$BOOT_DISPLAY_FOLDER"/part0
rm -f "$BOOT_DISPLAY_FOLDER"/boot-display.conf
echo -e "Info: Extracting custom files..."
tar -xvf "$BOOT_DISPLAY_K1_URL" -C "$BOOT_DISPLAY_FOLDER"
break;;
K1MAX|k1max)
echo -e "${white}"
echo -e "Info: Removing stock files..."
rm -rf "$BOOT_DISPLAY_FOLDER"/part0
rm -f "$BOOT_DISPLAY_FOLDER"/boot-display.conf
echo -e "Info: Extracting custom files..."
tar -xvf "$BOOT_DISPLAY_K1M_URL" -C "$BOOT_DISPLAY_FOLDER"
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
ok_msg "Custom Boot Display has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_custom_boot_display(){
remove_custom_boot_display_message
local yn
while true; do
remove_msg "Custom Boot Display" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing custom files..."
rm -rf "$BOOT_DISPLAY_FOLDER"/part0
rm -f "$BOOT_DISPLAY_FOLDER"/boot-display.conf
echo -e "Info: Extracting stock files..."
tar -xvf "$BOOT_DISPLAY_STOCK_URL" -C "$BOOT_DISPLAY_FOLDER"
ok_msg "Custom Boot Display has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

72
scripts/entware.sh Executable file
View file

@ -0,0 +1,72 @@
#!/bin/sh
set -e
function entware_message(){
top_line
title 'Entware' "${yellow}"
inner_line
hr
echo -e "${cyan}Entware is a software repository for devices which use Linux ${white}"
echo -e "${cyan}kernel. It allows packages to be added to your printer. ${white}"
hr
bottom_line
}
function install_entware(){
entware_message
local yn
while true; do
install_msg "Entware" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Running Entware installer..."
set +e
chmod 755 "$ENTWARE_URL"
sh "$ENTWARE_URL"
set -e
ok_msg "Entware has been installed successfully!"
echo -e " Disconnect and reconnect SSH session, and you can now install packages with: ${yellow}opkg install <packagename>${white}"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_entware(){
entware_message
local yn
while true; do
remove_msg "Entware" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing startup script..."
rm -f /etc/init.d/S50unslung
echo -e "Info: Removing directories..."
rm -rf /usr/data/opt
if [ -L /opt ]; then
rm /opt
mkdir -p /opt
chmod 755 /opt
fi
echo -e "Info: Removing SFTP server symlink..."
[ -L /usr/libexec/sftp-server ] && rm /usr/libexec/sftp-server
echo -e "Info: Removing changes in system profile..."
rm -f /etc/profile.d/entware.sh
sed -i 's/\/opt\/bin:\/opt\/sbin:\/bin:/\/bin:/' /etc/profile
ok_msg "Entware has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

129
scripts/fans_control_macros.sh Executable file
View file

@ -0,0 +1,129 @@
#!/bin/sh
set -e
function fans_control_macros_message(){
top_line
title 'Fans Control Macros' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows to control Motherboard fan from Web interfaces ${white}"
echo -e "${cyan}or with slicers. ${white}"
hr
bottom_line
}
function install_fans_control_macros(){
fans_control_macros_message
local yn
while true; do
install_msg "Fans Control Macros" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/fans-control.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/fans-control.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Linking file..."
ln -sf "$FAN_CONTROLS_URL" "$HS_CONFIG_FOLDER"/fans-control.cfg
if grep -q "include Helper-Script/fans-control" "$PRINTER_CFG" ; then
echo -e "Info: Fans Control Macros configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Fans Control Macros configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/fans-control\.cfg\]' "$PRINTER_CFG"
fi
if grep -q "\[duplicate_pin_override\]" "$PRINTER_CFG" ; then
echo -e "Info: Disabling [duplicate_pin_override] configuration in printer.cfg file..."
sed -i '/^\[duplicate_pin_override\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$PRINTER_CFG"
else
echo -e "Info: [duplicate_pin_override] configuration is already disabled in printer.cfg file..."
fi
if grep -q "\[temperature_fan chamber_fan\]" "$PRINTER_CFG" ; then
echo -e "Info: Disabling [temperature_fan chamber_fan] configuration in printer.cfg file..."
sed -i '/^\[temperature_fan chamber_fan\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$PRINTER_CFG"
else
echo -e "Info: [temperature_fan chamber_fan] configuration is already disabled in printer.cfg file..."
fi
if grep -q "\[gcode_macro M106\]" "$MACROS_CFG" ; then
echo -e "Info: Disabling [gcode_macro M106] in gcode_macro.cfg file..."
sed -i '/^\[gcode_macro M106\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro M106] macro is already disabled in gcode_macro.cfg file..."
fi
if grep -q "\[gcode_macro M141\]" "$MACROS_CFG" ; then
echo -e "Info: Disabling [gcode_macro M141] in gcode_macro.cfg file..."
sed -i '/^\[gcode_macro M141\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro M141] macro is already disabled in gcode_macro.cfg file..."
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Fans Control Macros have been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_fans_control_macros(){
fans_control_macros_message
local yn
while true; do
remove_msg "Fans Control Macros" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing file..."
rm -f "$HS_CONFIG_FOLDER"/fans-control.cfg
if grep -q "include Helper-Script/fans-control" "$PRINTER_CFG" ; then
echo -e "Info: Removing Fans Control Macros configurations in printer.cfg file..."
sed -i '/include Helper-Script\/fans-control\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Fans Control Macros configurations are already removed in printer.cfg file..."
fi
if grep -q "#\[duplicate_pin_override\]" "$PRINTER_CFG" ; then
echo -e "Info: Enabling [duplicate_pin_override] in printer.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[duplicate_pin_override\]/[duplicate_pin_override]/' -e '/^\[duplicate_pin_override\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$PRINTER_CFG"
else
echo -e "Info: [duplicate_pin_override] is already enabled in printer.cfg file..."
fi
if grep -q "#\[temperature_fan chamber_fan\]" "$PRINTER_CFG" ; then
echo -e "Info: Enabling [temperature_fan chamber_fan] in printer.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[temperature_fan chamber_fan\]/[temperature_fan chamber_fan]/' -e '/^\[temperature_fan chamber_fan\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$PRINTER_CFG"
else
echo -e "Info: [temperature_fan chamber_fan] is already enabled in printer.cfg file..."
fi
if grep -q "#\[gcode_macro M106\]" "$MACROS_CFG" ; then
echo -e "Info: Enabling [gcode_macro M106] in gcode_macro.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[gcode_macro M106\]/[gcode_macro M106]/' -e '/^\[gcode_macro M106\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro M106] is already enabled in gcode_macro.cfg file..."
fi
if grep -q "#\[gcode_macro M141\]" "$MACROS_CFG" ; then
echo -e "Info: Enabling [gcode_macro M141] in gcode_macro.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[gcode_macro M141\]/[gcode_macro M141]/' -e '/^\[gcode_macro M141\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro M141] is already enabled in gcode_macro.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Fans Control Macros have been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

90
scripts/fluidd.sh Executable file
View file

@ -0,0 +1,90 @@
#!/bin/sh
set -e
function fluidd_message(){
top_line
title 'Fluidd' "${yellow}"
inner_line
hr
echo -e "${cyan}Fluidd is a free and open-source Klipper Web interface for ${white}"
echo -e "${cyan}managing your 3d printer. ${white}"
echo -e "${cyan}It will be accessible on port 4408. ${white}"
hr
bottom_line
}
function install_fluidd(){
fluidd_message
local yn
while true; do
install_msg "Fluidd" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Downloading Fluidd file..."
"$CURL" -L "$FLUIDD_URL" -o "$USR_DATA"/fluidd.zip
echo -e "Info: Creating directory..."
if [ -d "$FLUIDD_FOLDER" ]; then
rm -rf "$FLUIDD_FOLDER"
fi
mkdir -p "$FLUIDD_FOLDER"
mv "$USR_DATA"/fluidd.zip "$FLUIDD_FOLDER"
echo -e "Info: Extracting files..."
unzip "$FLUIDD_FOLDER"/fluidd.zip -d "$FLUIDD_FOLDER"
echo -e "Info: Removing file..."
rm -f "$FLUIDD_FOLDER"/fluidd.zip
if grep -q "#\[update_manager fluidd\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Enabling Fluidd configurations for Update Manager..."
sed -i -e 's/^\s*#[[:space:]]*\[update_manager fluidd\]/[update_manager fluidd]/' -e '/^\[update_manager fluidd\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MOONRAKER_CFG"
else
echo -e "Info: Fluidd configurations are already enabled for Update Manager..."
fi
echo -e "Info: Retarting Nginx service..."
restart_nginx
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
ok_msg "Fluidd has been installed successfully!"
echo -e " You can now connect to Fluidd Web Interface with ${yellow}http://$(check_ipaddress):4408${white}"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_fluidd(){
fluidd_message
local yn
while true; do
remove_msg "Fluidd" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -rf "$FLUIDD_FOLDER"
if grep -q "\[update_manager fluidd\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Disabling Fluidd configurations for Update Manager..."
sed -i '/^\[update_manager fluidd\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MOONRAKER_CFG"
echo -e "Info: Retarting Nginx service..."
restart_nginx
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
else
echo -e "Info: Fluidd configurations are already disabled for Update Manager..."
fi
ok_msg "Fluidd has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

62
scripts/gcode_shell_command.sh Executable file
View file

@ -0,0 +1,62 @@
#!/bin/sh
set -e
function gcode_shell_command_message(){
top_line
title 'Klipper Gcode Shell Command' "${yellow}"
inner_line
hr
echo -e "${cyan}After installing this extension you can execute Linux ${white}"
echo -e "${cyan}commands or even scripts from Klipper with custom commands ${white}"
echo -e "${cyan}defined in your configuration files. ${white}"
hr
bottom_line
}
function install_gcode_shell_command(){
gcode_shell_command_message
local yn
while true; do
install_msg "Klipper Gcode Shell Command" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Linking file..."
ln -sf "$KLIPPER_SHELL_URL" "$KLIPPER_EXTRAS_FOLDER"/gcode_shell_command.py
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Klipper Gcode Shell Command has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_gcode_shell_command(){
gcode_shell_command_message
local yn
while true; do
remove_msg "Klipper Gcode Shell Command" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -f "$KLIPPER_EXTRAS_FOLDER"/gcode_shell_command.py
rm -f "$KLIPPER_EXTRAS_FOLDER"/gcode_shell_command.pyc
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Klipper Gcode Shell Command has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

91
scripts/git_backup.sh Executable file
View file

@ -0,0 +1,91 @@
#!/bin/sh
set -e
function git_backup_message(){
top_line
title 'Git Backup' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to watch Klipper configuration folder and ${white}"
echo -e "${cyan}automatically backup to GitHub whenever a change is made in ${white}"
echo -e "${cyan}that directory. ${white}"
hr
bottom_line
}
function install_git_backup(){
git_backup_message
local yn
while true; do
install_msg "Git Backup" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/git-backup.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/git-backup.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Running Git Backup installer..."
chmod 755 "$GIT_BACKUP_INSTALLER"
sh "$GIT_BACKUP_INSTALLER" -i
echo -e "Info: Linking file..."
ln -sf "$GIT_BACKUP_URL"/git-backup.cfg "$HS_CONFIG_FOLDER"/git-backup.cfg
if grep -q "include Helper-Script/git-backup" "$PRINTER_CFG" ; then
echo -e "Info: Git Backup configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Git Backup configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/git-backup\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Git Backup has been installed and configured successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_git_backup(){
git_backup_message
local yn
while true; do
remove_msg "Git Backup" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -f "$HS_CONFIG_FOLDER"/git-backup.cfg
rm -f "INITD_FOLDER"/S52Git-Backup
if grep -q "include Helper-Script/git-backup" "$PRINTER_CFG" ; then
echo -e "Info: Removing Git Backup configurations in printer.cfg file..."
sed -i '/include Helper-Script\/git-backup\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Git Backup configurations are already removed in printer.cfg file..."
fi
if [ -f "$ENTWARE_FILE" ]; then
echo -e "Info: Removing packages..."
"$ENTWARE_FILE" --autoremove remove inotifywait
"$ENTWARE_FILE" --autoremove remove procps-ng-pkill
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Git Backup has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

250
scripts/guppy_screen.sh Executable file
View file

@ -0,0 +1,250 @@
#!/bin/sh
set -e
function guppy_screen_message(){
top_line
title 'Guppy Screen' "${yellow}"
inner_line
hr
echo -e "${cyan}Guppy Screen is a touch UI for Klipper using APIs exposed by ${white}"
echo -e "${cyan}Moonraker. It replace Creality touch UI. ${white}"
hr
bottom_line
}
function install_guppy_screen(){
guppy_screen_message
local yn
while true; do
install_msg "Guppy Screen" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$USR_DATA"/guppyscreen.tar.gz ]; then
rm -f "$USR_DATA"/guppyscreen.tar.gz
fi
if [ $K1 -eq 1 ]; then
local theme_choice
while true; do
read -p " Do you want to install it with ${green}Material Design ${white}or ${green}Z-Bolt ${white}theme? (${yellow}material${white}/${yellow}zbolt${white}): ${yellow}" theme_choice
case "${theme_choice}" in
MATERIAL|material)
echo -e "${white}"
echo -e "Info: Downloading Guppy Screen..."
"$CURL" -L https://github.com/ballaswag/guppyscreen/releases/latest/download/guppyscreen.tar.gz -o "$USR_DATA"/guppyscreen.tar.gz
break;;
ZBOLT|zbolt)
echo -e "${white}"
echo -e "Info: Downloading Guppy Screen..."
"$CURL" -L https://github.com/ballaswag/guppyscreen/releases/latest/download/guppyscreen-zbolt.tar.gz -o "$USR_DATA"/guppyscreen.tar.gz
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
else
echo -e "Info: Downloading Guppy Screen..."
"$CURL" -L https://github.com/ballaswag/guppyscreen/releases/latest/download/guppyscreen-smallscreen.tar.gz -o "$USR_DATA"/guppyscreen.tar.gz
fi
echo -e "Info: Installing files..."
tar -xvf "$USR_DATA"/guppyscreen.tar.gz -C "$USR_DATA"
rm -f "$USR_DATA"/guppyscreen.tar.gz
if [ ! -d "$HS_BACKUP_FOLDER"/guppyscreen ]; then
echo -e "Info: Backing up original file..."
mkdir -p "$HS_BACKUP_FOLDER"/guppyscreen
mv "$INITD_FOLDER"/S12boot_display "$HS_BACKUP_FOLDER"/guppyscreen
cp "$INITD_FOLDER"/S50dropbear "$HS_BACKUP_FOLDER"/guppyscreen
cp "$INITD_FOLDER"/S99start_app "$HS_BACKUP_FOLDER"/guppyscreen
fi
if [ ! -f "$HS_BACKUP_FOLDER"/guppyscreen/ft2font.cpython-38-mipsel-linux-gnu.so ]; then
mv /usr/lib/python3.8/site-packages/matplotlib/ft2font.cpython-38-mipsel-linux-gnu.so "$HS_BACKUP_FOLDER"/guppyscreen
fi
local yn
while true; do
echo
echo -e " ${white}Do you want to disable all Creality services ?"
echo -e " ${yellow}Benefits: ${white}\e[97mFrees up system resources on your K1 for critical services such as Klipper (recommended)${white}"
echo -e " ${yellow}Disadvantages: ${white}\e[97mDisabling all Creality services breaks Creality Cloud and Creality Print${white}"
echo
read -p " Do you want to disable all Creality Services? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Disabling Creality services..."
rm -f "$INITD_FOLDER"/S99start_app
set +e
killall -q master-server
killall -q audio-server
killall -q wifi-server
killall -q app-server
killall -q upgrade-server
killall -q web-server
set -e
break;;
N|n)
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
if [ ! -d "/usr/lib/python3.8/site-packages/matplotlib-2.2.3-py3.8.egg-info" ]; then
echo -e "Info: mathplotlib ft2font module is not replaced. PSD graphs might not work..."
else
echo -e "Info: Replacing mathplotlib ft2font module to generate PSD graphs..."
cp "$GUPPY_SCREEN_FOLDER"/k1_mods/ft2font.cpython-38-mipsel-linux-gnu.so /usr/lib/python3.8/site-packages/matplotlib/ft2font.cpython-38-mipsel-linux-gnu.so
fi
echo -e "Info: Setting up Guppy Screen..."
cp "$GUPPY_SCREEN_FOLDER"/k1_mods/S50dropbear "$INITD_FOLDER"/S50dropbear
cp "$GUPPY_SCREEN_FOLDER"/k1_mods/S99guppyscreen "$INITD_FOLDER"/S99guppyscreen
ln -sf "$GUPPY_SCREEN_FOLDER"/k1_mods/calibrate_shaper_config.py "$KLIPPER_EXTRAS_FOLDER"/calibrate_shaper_config.py
ln -sf "$GUPPY_SCREEN_FOLDER"/k1_mods/guppy_module_loader.py "$KLIPPER_EXTRAS_FOLDER"/guppy_module_loader.py
ln -sf "$GUPPY_SCREEN_FOLDER"/k1_mods/guppy_config_helper.py "$KLIPPER_EXTRAS_FOLDER"/guppy_config_helper.py
ln -sf "$GUPPY_SCREEN_FOLDER"/k1_mods/tmcstatus.py "$KLIPPER_EXTRAS_FOLDER"/tmcstatus.py
ln -sf "$GUPPY_SCREEN_FOLDER"/k1_mods/respawn/libeinfo.so.1 /lib/libeinfo.so.1
ln -sf "$GUPPY_SCREEN_FOLDER"/k1_mods/respawn/librc.so.1 /lib/librc.so.1
mkdir -p "$KLIPPER_CONFIG_FOLDER"/GuppyScreen/scripts
cp "$GUPPY_SCREEN_FOLDER"/scripts/*.cfg "$KLIPPER_CONFIG_FOLDER"/GuppyScreen
cp "$GUPPY_SCREEN_FOLDER"/scripts/*.py "$KLIPPER_CONFIG_FOLDER"/GuppyScreen/scripts
ln -sf "$GUPPY_SCREEN_URL1" "$KLIPPER_CONFIG_FOLDER"/GuppyScreen/guppy_update.cfg
chmod 775 "$GUPPY_SCREEN_URL2"
if grep -q "include GuppyScreen" "$PRINTER_CFG" ; then
echo -e "Info: Guppy Screen configurations are already enabled in printer.cfg file."
else
echo -e "Info: Adding Guppy Screen configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include GuppyScreen/*\.cfg\]' "$PRINTER_CFG"
fi
if grep -q 'variable_autotune_shapers:' "$MACROS_CFG" ; then
echo -e "Info: Disabling stock configuration in gcode_macro.cfg file..."
sed -i 's/variable_autotune_shapers:/#&/' "$MACROS_CFG"
else
echo -e "Info: Stock configuration is already disabled in gcode_macro.cfg file..."
fi
if grep -q '\[gcode_macro INPUTSHAPER\]' "$MACROS_CFG" ; then
echo -e "Info: Replacing stock configuration in gcode_macro.cfg file..."
sed -i 's/SHAPER_CALIBRATE AXIS=y/SHAPER_CALIBRATE/' "$MACROS_CFG"
else
echo -e "Info: Stock configuration is already replaced in gcode_macro.cfg file..."
fi
sync
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
echo -e "Info: Disabling services..."
if [ -f /usr/bin/Monitor ]; then
mv /usr/bin/Monitor /usr/bin/Monitor.disable
fi
if [ -f /usr/bin/display-server ]; then
mv /usr/bin/display-server /usr/bin/display-server.disable
fi
set +e
killall -q Monitor
killall -q display-server
set -e
echo -e "Info: Starting Guppy Screen service..."
/etc/init.d/S99guppyscreen restart &> /dev/null
sleep 1
ps auxw | grep guppyscreen | grep -v sh | grep -v grep
ok_msg "Guppy Screen has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_guppy_screen(){
guppy_screen_message
local yn
while true; do
remove_msg "Guppy Screen" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoring backup files..."
if [ -d "$HS_BACKUP_FOLDER"/guppyscreen ]; then
cp "$HS_BACKUP_FOLDER"/guppyscreen/S12boot_display "$INITD_FOLDER"/S12boot_display
cp "$HS_BACKUP_FOLDER"/guppyscreen/S50dropbear "$INITD_FOLDER"/S50dropbear
cp "$HS_BACKUP_FOLDER"/guppyscreen/S99start_app "$INITD_FOLDER"/S99start_app
rm -rf "$HS_BACKUP_FOLDER"/guppyscreen
fi
if [ ! -n "$(ls -A "$HS_BACKUP_FOLDER")" ]; then
rm -rf "$HS_BACKUP_FOLDER"
fi
echo -e "Info: Stopping Guppy Screen Service..."
[ -f "$INITD_FOLDER"/S99guppyscreen ] && "$INITD_FOLDER"/S99guppyscreen stop &> /dev/null
set +e
killall -q guppyscreen
set -e
echo -e "Info: Removing files..."
rm -f "$KLIPPER_EXTRAS_FOLDER"/calibrate_shaper_config.py
rm -f "$KLIPPER_EXTRAS_FOLDER"/calibrate_shaper_config.pyc
rm -f "$KLIPPER_EXTRAS_FOLDER"/guppy_module_loader.py
rm -f "$KLIPPER_EXTRAS_FOLDER"/guppy_module_loader.pyc
rm -f "$KLIPPER_EXTRAS_FOLDER"/guppy_config_helper.py
rm -f "$KLIPPER_EXTRAS_FOLDER"/guppy_config_helper.pyc
rm -f "$KLIPPER_EXTRAS_FOLDER"/tmcstatus.py
rm -f "$KLIPPER_EXTRAS_FOLDER"/tmcstatus.pyc
rm -f "$INITD_FOLDER"/S99guppyscreen
rm -f /lib/libeinfo.so.1
rm -f /lib/librc.so.1
rm -rf "$GUPPY_SCREEN_FOLDER"
rm -rf "$KLIPPER_CONFIG_FOLDER"/GuppyScreen
if grep -q "include GuppyScreen/*" "$PRINTER_CFG" ; then
echo -e "Info: Removing Guppy Screen configurations in printer.cfg file..."
sed -i '/\[include GuppyScreen\/\*\.cfg\]/d' "$PRINTER_CFG"
else
echo -e "Info: Guppy Screen configurations are already removed in printer.cfg file..."
fi
if grep -q "#variable_autotune_shapers:" "$MACROS_CFG"; then
echo -e "Info: Enabling stock configuration in gcode_macro.cfg file..."
sed -i 's/#variable_autotune_shapers:/variable_autotune_shapers:/' "$MACROS_CFG"
else
echo -e "Info: Stock configuration is already enabled in gcode_macro.cfg file..."
fi
if grep -q '\[gcode_macro INPUTSHAPER\]' "$MACROS_CFG" ; then
echo -e "Info: Restoring stock configuration in gcode_macro.cfg file..."
sed -i 's/SHAPER_CALIBRATE/SHAPER_CALIBRATE AXIS=y/' "$MACROS_CFG"
else
echo -e "Info: Stock configuration is already restored in gcode_macro.cfg file..."
fi
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
echo -e "Info: Restoring services..."
if [ -f /usr/bin/Monitor.disable ]; then
mv /usr/bin/Monitor.disable /usr/bin/Monitor
fi
if [ -f /usr/bin/display-server.disable ]; then
mv /usr/bin/display-server.disable /usr/bin/display-server
fi
echo -e "Info: Restarting Creality services..."
set +e
/usr/bin/Monitor > /dev/null 2>&1 &
/usr/bin/display-server > /dev/null 2>&1 &
/usr/bin/master-server > /dev/null 2>&1 &
/usr/bin/audio-server > /dev/null 2>&1 &
/usr/bin/wifi-server > /dev/null 2>&1 &
/usr/bin/app-server > /dev/null 2>&1 &
/usr/bin/upgrade-server > /dev/null 2>&1 &
if [ -f /usr/bin/web-server ]; then
/usr/bin/web-server > /dev/null 2>&1 &
fi
set -e
ok_msg "Guppy Screen has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

140
scripts/improved_shapers.sh Executable file
View file

@ -0,0 +1,140 @@
#!/bin/sh
set -e
function improved_shapers_message(){
top_line
title 'Improved Shapers Calibrations' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to calibrate Input Shaper, Belts Tension and ${white}"
echo -e "${cyan}generate Graphs. ${white}"
hr
bottom_line
}
function install_improved_shapers(){
improved_shapers_message
local yn
while true; do
install_msg "Improved Shapers Calibrations" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Backing up original file..."
if [ ! -d "$HS_BACKUP_FOLDER"/improved-shapers ]; then
mkdir -p "$HS_BACKUP_FOLDER"/improved-shapers
fi
if [ ! -f "$HS_BACKUP_FOLDER"/ft2font.cpython-38-mipsel-linux-gnu.so ]; then
mv /usr/lib/python3.8/site-packages/matplotlib/ft2font.cpython-38-mipsel-linux-gnu.so "$HS_BACKUP_FOLDER"/improved-shapers
fi
echo -e "Info: Linking files..."
ln -sf "$IMP_SHAPERS_URL"/calibrate_shaper_config.py "$KLIPPER_EXTRAS_FOLDER"/calibrate_shaper_config.py
if [ ! -d "/usr/lib/python3.8/site-packages/matplotlib-2.2.3-py3.8.egg-info" ]; then
echo -e "Info: mathplotlib ft2font module is not replaced. PSD graphs might not work..."
else
echo -e "Info: Replacing mathplotlib ft2font module to generate PSD graphs..."
cp "$IMP_SHAPERS_URL"/ft2font.cpython-38-mipsel-linux-gnu.so /usr/lib/python3.8/site-packages/matplotlib/ft2font.cpython-38-mipsel-linux-gnu.so
fi
if [ -f "$HS_CONFIG_FOLDER"/improved-shapers ]; then
rm -rf "$HS_CONFIG_FOLDER"/improved-shapers
fi
if [ ! -d "$HS_CONFIG_FOLDER"/improved-shapers/scripts ]; then
mkdir -p "$HS_CONFIG_FOLDER"/improved-shapers/scripts
fi
cp "$IMP_SHAPERS_URL"/scripts/*.py "$HS_CONFIG_FOLDER"/improved-shapers/scripts
ln -sf "$IMP_SHAPERS_URL"/improved-shapers.cfg "$HS_CONFIG_FOLDER"/improved-shapers/improved-shapers.cfg
if grep -q 'variable_autotune_shapers:' "$MACROS_CFG" ; then
echo -e "Info: Disabling [gcode_macro AUTOTUNE_SHAPERS] configurations in gcode_macro.cfg file..."
sed -i 's/variable_autotune_shapers:/#&/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro AUTOTUNE_SHAPERS] configurations are already disabled in gcode_macro.cfg file..."
fi
if [ $K1 -eq 1 ]; then
if grep -q '\[gcode_macro INPUTSHAPER\]' "$MACROS_CFG" ; then
echo -e "Info: Replacing [gcode_macro INPUTSHAPER] configurations in gcode_macro.cfg file..."
sed -i 's/SHAPER_CALIBRATE AXIS=y/SHAPER_CALIBRATE/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro INPUTSHAPER] configurations are already replaced in gcode_macro.cfg file..."
fi
fi
if grep -q "include Helper-Script/improved-shapers/improved-shapers" "$PRINTER_CFG" ; then
echo -e "Info: Improved Shapers Calibration configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Improved Shapers Calibration configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/improved-shapers/improved-shapers\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Improved Shapers Calibrations have been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_improved_shapers(){
improved_shapers_message
local yn
while true; do
remove_msg "Improved Shapers Calibrations" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoring original file..."
if [ -f "$HS_BACKUP_FOLDER"/ft2font.cpython-38-mipsel-linux-gnu.so ]; then
mv "$HS_BACKUP_FOLDER"/improved-shapers/ft2font.cpython-38-mipsel-linux-gnu.so /usr/lib/python3.8/site-packages/matplotlib
rm -rf "$HS_BACKUP_FOLDER"/improved-shapers
fi
if [ ! -n "$(ls -A "$HS_BACKUP_FOLDER")" ]; then
rm -rf "$HS_BACKUP_FOLDER"
fi
echo -e "Info: Removing files..."
rm -rf "$IMP_SHAPERS_FOLDER"
rm -f "$KLIPPER_EXTRAS_FOLDER"/calibrate_shaper_config.py
rm -f "$KLIPPER_EXTRAS_FOLDER"/calibrate_shaper_config.pyc
if grep -q "#variable_autotune_shapers:" "$MACROS_CFG"; then
echo -e "Info: Restoring [gcode_macro AUTOTUNE_SHAPERS] configurations in gcode_macro.cfg file..."
sed -i 's/#variable_autotune_shapers:/variable_autotune_shapers:/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro AUTOTUNE_SHAPERS] configurations are already restored in gcode_macro.cfg file..."
fi
if [ $K1 -eq 1 ]; then
if grep -q '\[gcode_macro INPUTSHAPER\]' "$MACROS_CFG" ; then
echo -e "Info: Restoring [gcode_macro INPUTSHAPER] configurations in gcode_macro.cfg file..."
sed -i 's/SHAPER_CALIBRATE/SHAPER_CALIBRATE AXIS=y/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro INPUTSHAPER] configurations are already restored in gcode_macro.cfg file..."
fi
fi
if grep -q "include Helper-Script/improved-shapers/improved-shapers" "$PRINTER_CFG" ; then
echo -e "Info: Removing Improved Shapers Calibrations in printer.cfg file..."
sed -i '/include Helper-Script\/improved-shapers\/improved-shapers\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Improved Shapers Calibrations are already removed in printer.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Improved Shapers Calibrations have been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

126
scripts/kamp.sh Executable file
View file

@ -0,0 +1,126 @@
#!/bin/sh
set -e
function kamp_message(){
top_line
title 'Klipper Adaptive Meshing & Purging' "${yellow}"
inner_line
hr
echo -e "${cyan}KAMP is an extension that allows to generate a mesh and ${white}"
echo -e "${cyan}purge line only in the area you really need it. ${white}"
hr
bottom_line
}
function install_kamp(){
kamp_message
local yn
while true; do
install_msg "Klipper Adaptive Meshing & Purging" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -d "$HS_CONFIG_FOLDER"/KAMP ]; then
rm -rf "$HS_CONFIG_FOLDER"/KAMP
fi
if [ -f "$HS_CONFIG_FOLDER"/KAMP_Settings.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/KAMP_Settings.cfg
fi
echo -e "Info: Creating directories..."
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
mkdir -p "$HS_CONFIG_FOLDER"/KAMP
echo -e "Info: Linking files..."
ln -sf "$KAMP_URL"/Adaptive_Meshing.cfg "$KAMP_FOLDER"/Adaptive_Meshing.cfg
ln -sf "$KAMP_URL"/Line_Purge.cfg "$KAMP_FOLDER"/Line_Purge.cfg
ln -sf "$KAMP_URL"/Prusa_Slicer.cfg "$KAMP_FOLDER"/Prusa_Slicer.cfg
ln -sf "$KAMP_URL"/Smart_Park.cfg "$KAMP_FOLDER"/Smart_Park.cfg
ln -sf "$KAMP_URL"/Start_Print.cfg "$KAMP_FOLDER"/Start_Print.cfg
cp "$KAMP_URL"/KAMP_Settings.cfg "$KAMP_FOLDER"/KAMP_Settings.cfg
if grep -q "include Helper-Script/KAMP/KAMP_Settings" "$PRINTER_CFG" ; then
echo -e "Info: KAMP configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding KAMP configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a [include Helper-Script/KAMP/KAMP_Settings.cfg]' "$PRINTER_CFG"
fi
if grep -q "\[gcode_macro START_PRINT\]" "$MACROS_CFG" ; then
echo -e "Info: Disabling [gcode_macro START_PRINT] in gcode_macro.cfg file..."
sed -i '/\[gcode_macro START_PRINT\]/,/^\s*CX_PRINT_DRAW_ONE_LINE/ { /^\s*$/d }' "$MACROS_CFG"
sed -i '/^\[gcode_macro START_PRINT\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro START_PRINT] is already disabled in gcode_macro.cfg file..."
fi
echo
local yn_prusa
while true; do
read -p " Do you want to enable needed macros for PrusaSlicer? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn_prusa
case "${yn_prusa}" in
Y|y)
echo -e "${white}"
if grep -q "#\[include Prusa_Slicer.cfg\]" "$KAMP_FOLDER"/KAMP_Settings.cfg ; then
echo -e "Info: Enabling [include Prusa_Slicer.cfg] in KAMP_Settings.cfg file..."
sed -i 's/^#\[include Prusa_Slicer\.cfg\]/[include Prusa_Slicer.cfg]/' "$KAMP_FOLDER"/KAMP_Settings.cfg
else
echo -e "Info: [include Prusa_Slicer.cfg] is already enabled in KAMP_Settings.cfg file..."
fi
break;;
N|n)
echo -e "${white}"
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Klipper Adaptive Meshing & Purging has been installed successfully!"
echo -e " Make sure Label Objects setting is enabled in your slicer."
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_kamp(){
kamp_message
local yn
while true; do
remove_msg "Klipper Adaptive Meshing & Purging" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -rf "$HS_CONFIG_FOLDER"/KAMP
if grep -q "include Helper-Script/KAMP/KAMP_Settings" "$PRINTER_CFG" ; then
echo -e "Info: Removing KAMP configurations in printer.cfg file..."
sed -i '/include Helper-Script\/KAMP\/KAMP_Settings\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: KAMP configurations are already removed in printer.cfg file..."
fi
if grep -q "#\[gcode_macro START_PRINT\]" "$MACROS_CFG" ; then
echo -e "Info: Enabling [gcode_macro START_PRINT] in gcode_macro.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[gcode_macro START_PRINT\]/[gcode_macro START_PRINT]/' -e '/^\[gcode_macro START_PRINT\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro START_PRINT] is already enabled in gcode_macro.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Klipper Adaptive Meshing & Purging has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

117
scripts/m600_support.sh Executable file
View file

@ -0,0 +1,117 @@
#!/bin/sh
set -e
function m600_support_message(){
top_line
title 'M600 Support' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to use M600 command in your slicer to change ${white}"
echo -e "${cyan}filament. ${white}"
hr
bottom_line
}
function install_m600_support(){
m600_support_message
local yn
while true; do
install_msg "M600 Support" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/M600-support.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/M600-support.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Linking file..."
ln -sf "$M600_SUPPORT_URL" "$HS_CONFIG_FOLDER"/M600-support.cfg
if grep -q "include Helper-Script/M600-support" "$PRINTER_CFG" ; then
echo -e "Info: M600 Support configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding M600 Support configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/M600-support\.cfg\]' "$PRINTER_CFG"
fi
if grep -q "\[idle_timeout\]" "$PRINTER_CFG" ; then
echo -e "Info: Disabling [idle_timeout] configurations in printer.cfg file..."
sed -i '/^\[idle_timeout\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$PRINTER_CFG"
else
echo -e "Info: [idle_timeout] configurations are already disabled in printer.cfg file..."
fi
if grep -q "\[filament_switch_sensor filament_sensor\]" "$PRINTER_CFG" ; then
echo -e "Info: Disabling [filament_switch_sensor] configurations in printer.cfg file..."
sed -i '/^\[filament_switch_sensor filament_sensor\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$PRINTER_CFG"
else
echo -e "Info: [filament_switch_sensor] configurations are already disabled in printer.cfg file..."
fi
if grep -q "\[gcode_macro RESUME\]" "$MACROS_CFG" ; then
echo -e "Info: Disabling [gcode_macro RESUME] in gcode_macro.cfg file..."
sed -i '/^\[gcode_macro RESUME\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro RESUME] is already disabled in gcode_macro.cfg file..."
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "M600 Support has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_m600_support(){
m600_support_message
local yn
while true; do
remove_msg "M600 Support" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing file..."
rm -f "$HS_CONFIG_FOLDER"/M600-support.cfg
if grep -q "include Helper-Script/M600-support" "$PRINTER_CFG" ; then
echo -e "Info: Removing M600 Support configurations in printer.cfg file..."
sed -i '/include Helper-Script\/M600-support\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: M600 Support configurations are already removed in printer.cfg file..."
fi
if grep -q "#\[idle_timeout\]" "$PRINTER_CFG" ; then
echo -e "Info: Enabling [idle_timeout] configurations in printer.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[idle_timeout\]/[idle_timeout]/' -e '/^\[idle_timeout\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$PRINTER_CFG"
else
echo -e "Info: [idle_timeout] configurations are already enabled in printer.cfg file..."
fi
if grep -q "#\[filament_switch_sensor filament_sensor\]" "$PRINTER_CFG" ; then
echo -e "Info: Enabling [filament_switch_sensor] configurations in printer.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[filament_switch_sensor filament_sensor\]/[filament_switch_sensor filament_sensor]/' -e '/^\[filament_switch_sensor filament_sensor\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$PRINTER_CFG"
else
echo -e "Info: [filament_switch_sensor] configurations are already enabled in printer.cfg file..."
fi
if grep -q "#\[gcode_macro RESUME\]" "$MACROS_CFG" ; then
echo -e "Info: Enabling [gcode_macro RESUME] in gcode_macro.cfg file..."
sed -i -e 's/^\s*#[[:space:]]*\[gcode_macro RESUME\]/[gcode_macro RESUME]/' -e '/^\[gcode_macro RESUME\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MACROS_CFG"
else
echo -e "Info: [gcode_macro RESUME] is already enabled in gcode_macro.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "M600 Support has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

91
scripts/mainsail.sh Executable file
View file

@ -0,0 +1,91 @@
#!/bin/sh
set -e
function mainsail_message(){
top_line
title 'Mainsail' "${yellow}"
inner_line
hr
echo -e "${cyan}Mainsail makes Klipper more accessible by adding a ${white}"
echo -e "${cyan}lightweight, responsive web user interface, centred around ${white}"
echo -e "${cyan}an intuitive and consistent design philosophy. ${white}"
echo -e "${cyan}It will be accessible on port 4409. ${white}"
hr
bottom_line
}
function install_mainsail(){
mainsail_message
local yn
while true; do
install_msg "Mainsail" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Downloading Mainsail file..."
"$CURL" -L "$MAINSAIL_URL" -o "$USR_DATA"/mainsail.zip
echo -e "Info: Creating directory..."
if [ -d "$MAINSAIL_FOLDER" ]; then
rm -rf "$MAINSAIL_FOLDER"
fi
mkdir -p "$MAINSAIL_FOLDER"
mv "$USR_DATA"/mainsail.zip "$MAINSAIL_FOLDER"
echo -e "Info: Extracting files..."
unzip "$MAINSAIL_FOLDER"/mainsail.zip -d "$MAINSAIL_FOLDER"
echo -e "Info: Removing file..."
rm -f "$MAINSAIL_FOLDER"/mainsail.zip
if grep -q "#\[update_manager mainsail\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Enabling Mainsail configurations for Update Manager..."
sed -i -e 's/^\s*#[[:space:]]*\[update_manager mainsail\]/[update_manager mainsail]/' -e '/^\[update_manager mainsail\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MOONRAKER_CFG"
else
echo -e "Info: Mainsail configurations are already enabled for Update Manager..."
fi
echo -e "Info: Retarting Nginx service..."
restart_nginx
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
ok_msg "Mainsail has been installed successfully!"
echo -e " You can now connect to Mainsail Web Interface with ${yellow}http://$(check_ipaddress):4409${white}"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_mainsail(){
mainsail_message
local yn
while true; do
remove_msg "Mainsail" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -rf "$MAINSAIL_FOLDER"
if grep -q "\[update_manager mainsail\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Disabling Mainsail configurations for Update Manager..."
sed -i '/^\[update_manager mainsail\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MOONRAKER_CFG"
echo -e "Info: Retarting Nginx service..."
restart_nginx
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
else
echo -e "Info: Mainsail configurations are already disabled for Update Manager..."
fi
ok_msg "Mainsail has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

View file

@ -0,0 +1,87 @@
#!/bin/sh
set -e
function customize_menu_ui_ke() {
top_line
title '[ CUSTOMIZE MENU ]' "${yellow}"
inner_line
hr
menu_option '1' 'Remove' 'Creality Web Interface'
menu_option '2' 'Restore' 'Creality Web Interface'
hr
menu_option '3' 'Install' 'Guppy Screen'
menu_option '4' 'Remove' 'Guppy Screen'
hr
menu_option '5' 'Install' 'Creality Dynamic Logos for Fluidd'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function customize_menu_ke() {
clear
customize_menu_ui_ke
local customize_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" customize_menu_opt
case "${customize_menu_opt}" in
1)
if [ ! -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Fluidd or Mainsail is needed, please install it first!"
elif [ ! -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is already removed!"
echo -e " ${darkred}Please restore Creality Web Interface first if you want to change the default Web Interface.${white}"
echo
else
run "remove_creality_web_interface" "customize_menu_ui_ke"
fi;;
2)
if [ -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is already present!"
else
run "restore_creality_web_interface" "customize_menu_ui_ke"
fi;;
3)
if [ -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Guppy Screen is already installed!"
echo -e " ${darkred}Please remove Guppy Screen first if you want to change the theme.${white}"
echo
elif [ -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Please remove Improved Shapers Calibrations first, Guppy Screen already use it!"
elif [ ! -f /lib/ld-2.29.so ]; then
error_msg "Make sure you're running 1.3.x.x firmware version!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_guppy_screen" "customize_menu_ui_ke"
fi;;
4)
if [ ! -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Guppy Screen is not installed!"
else
run "remove_guppy_screen" "customize_menu_ui_ke"
fi;;
5)
if [ -f "$FLUIDD_LOGO_FILE" ]; then
error_msg "Creality Dynamic Logos for Fluidd are already installed!"
elif [ ! -d "$FLUIDD_FOLDER" ]; then
error_msg "Fluidd is needed, please install it first!"
else
run "install_creality_dynamic_logos" "customize_menu_ui_ke"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
customize_menu_ke
}

81
scripts/menu/KE/info_menu_KE.sh Executable file
View file

@ -0,0 +1,81 @@
#!/bin/sh
set -e
function check_folder_ke() {
local folder_path="$1"
if [ -d "$folder_path" ]; then
echo -e "${green}"
else
echo -e "${red}"
fi
}
function check_file_ke() {
local file_path="$1"
if [ -f "$file_path" ]; then
echo -e "${green}"
else
echo -e "${red}"
fi
}
function info_menu_ui_ke() {
top_line
title '[ INFORMATIONS MENU ]' "${yellow}"
inner_line
hr
subtitle '•ESSENTIALS:'
info_line "$(check_folder_ke "$MOONRAKER_FOLDER")" 'Moonraker & Nginx'
info_line "$(check_folder_ke "$FLUIDD_FOLDER")" 'Fluidd'
info_line "$(check_folder_ke "$MAINSAIL_FOLDER")" 'Mainsail'
hr
subtitle '•UTILITIES:'
info_line "$(check_file_ke "$ENTWARE_FILE")" 'Entware'
info_line "$(check_file_ke "$KLIPPER_SHELL_FILE")" 'Klipper Gcode Shell Command'
hr
subtitle '•IMPROVEMENTS:'
info_line "$(check_folder_ke "$IMP_SHAPERS_FOLDER")" 'Improved Shapers Calibrations'
info_line "$(check_file_ke "$SAVE_ZOFFSET_FILE")" 'Save Z-Offset Macros'
info_line "$(check_file_ke "$VIRTUAL_PINS_FILE")" 'Virtual Pins Support'
info_line "$(check_file_ke "$GIT_BACKUP_FILE")" 'Git Backup'
hr
subtitle '•CAMERA:'
info_line "$(check_file_ke "$TIMELAPSE_FILE")" 'Moonraker Timelapse'
hr
subtitle '•REMOTE ACCESS AND AI DETECTION:'
info_line "$(check_folder_ke "$OCTOEVERYWHERE_FOLDER")" 'OctoEverywhere'
info_line "$(check_folder_ke "$MOONRAKER_OBICO_FOLDER")" 'Obico'
info_line "$(check_folder_ke "$MOBILERAKER_COMPANION_FOLDER")" 'Mobileraker Companion'
hr
subtitle '•CUSTOMIZATION:'
info_line "$(check_file_ke "$CREALITY_WEB_FILE")" 'Creality Web Interface'
info_line "$(check_folder_ke "$GUPPY_SCREEN_FOLDER")" 'Guppy Screen'
info_line "$(check_file_ke "$FLUIDD_LOGO_FILE")" 'Creality Dynamic Logos for Fluidd'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function info_menu_ke() {
clear
info_menu_ui_ke
local info_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" info_menu_opt
case "${info_menu_opt}" in
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
info_menu_ke
}

View file

@ -0,0 +1,162 @@
#!/bin/sh
set -e
function install_menu_ui_ke() {
top_line
title '[ INSTALL MENU ]' "${yellow}"
inner_line
hr
subtitle '•ESSENTIALS:'
menu_option ' 1' 'Install' 'Moonraker and Nginx'
menu_option ' 2' 'Install' 'Fluidd (port 4408)'
menu_option ' 3' 'Install' 'Mainsail (port 4409)'
hr
subtitle '•UTILITIES:'
menu_option ' 4' 'Install' 'Entware'
menu_option ' 5' 'Install' 'Klipper Gcode Shell Command'
hr
subtitle '•IMPROVEMENTS:'
menu_option ' 6' 'Install' 'Improved Shapers Calibrations'
menu_option ' 7' 'Install' 'Save Z-Offset Macros'
menu_option ' 8' 'Install' 'Virtual Pins Support'
menu_option ' 9' 'Install' 'Git Backup'
hr
subtitle '•CAMERA:'
menu_option '10' 'Install' 'Moonraker Timelapse'
hr
subtitle '•REMOTE ACCESS AND AI DETECTION:'
menu_option '11' 'Install' 'OctoEverywhere'
menu_option '12' 'Install' 'Moonraker Obico'
menu_option '13' 'Install' 'Mobileraker Companion'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function install_menu_ke() {
clear
install_menu_ui_ke
local install_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" install_menu_opt
case "${install_menu_opt}" in
1)
if [ -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker and Nginx are already installed!"
else
run "install_moonraker_nginx" "install_menu_ui_ke"
fi;;
2)
if [ -d "$FLUIDD_FOLDER" ]; then
error_msg "Fluidd is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ] && [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
else
run "install_fluidd" "install_menu_ui_ke"
fi;;
3)
if [ -d "$MAINSAIL_FOLDER" ]; then
error_msg "Mainsail is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ] && [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
else
run "install_mainsail" "install_menu_ui_ke"
fi;;
4)
if [ -f "$ENTWARE_FILE" ]; then
error_msg "Entware is already installed!"
else
run "install_entware" "install_menu_ui_ke"
fi;;
5)
if [ -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is already installed!"
else
run "install_gcode_shell_command" "install_menu_ui_ke"
fi;;
6)
if [ -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Improved Shapers Calibrations are already installed!"
elif [ -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Guppy Screen already has these features!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_improved_shapers" "install_menu_ui_ke"
fi;;
7)
if [ -f "$SAVE_ZOFFSET_FILE" ]; then
error_msg "Save Z-Offset Macros are already installed!"
else
run "install_save_zoffset_macros" "install_menu_ui_ke"
fi;;
8)
if [ -f "$VIRTUAL_PINS_FILE" ]; then
error_msg "Virtual Pins Support is already installed!"
else
run "install_virtual_pins" "install_menu_ui_ke"
fi;;
9)
if [ -f "$GIT_BACKUP_FILE" ]; then
error_msg "Git Backup is already installed!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_git_backup" "install_menu_ui_ke"
fi;;
10)
if [ -f "$TIMELAPSE_FILE" ]; then
error_msg "Moonraker Timelapse is already installed!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
else
run "install_moonraker_timelapse" "install_menu_ui_ke"
fi;;
11)
if [ -d "$OCTOEVERYWHERE_FOLDER" ]; then
error_msg "OctoEverywhere is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
elif [ ! -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Fluidd or Mainsail is needed, please install it first!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
else
run "install_octoeverywhere" "install_menu_ui_ke"
fi;;
12)
if [ -d "$MOONRAKER_OBICO_FOLDER" ]; then
error_msg "Moonraker Obico is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
elif [ ! -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Fluidd or Mainsail is needed, please install it first!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
else
run "install_moonraker_obico" "install_menu_ui_ke"
fi;;
13)
if [ -d "$MOBILERAKER_COMPANION_FOLDER" ]; then
error_msg "Mobileraker Companion is already installed!"
else
run "install_mobileraker_companion" "install_menu_ui_ke"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
install_menu_ke
}

158
scripts/menu/KE/remove_menu_KE.sh Executable file
View file

@ -0,0 +1,158 @@
#!/bin/sh
set -e
function remove_menu_ui_ke() {
top_line
title '[ REMOVE MENU ]' "${yellow}"
inner_line
hr
subtitle '•ESSENTIALS:'
menu_option ' 1' 'Remove' 'Moonraker and Nginx'
menu_option ' 2' 'Remove' 'Fluidd (port 4408)'
menu_option ' 3' 'Remove' 'Mainsail (port 4409)'
hr
subtitle '•UTILITIES:'
menu_option ' 4' 'Remove' 'Entware'
menu_option ' 5' 'Remove' 'Klipper Gcode Shell Command'
hr
subtitle '•IMPROVEMENTS:'
menu_option ' 6' 'Remove' 'Improved Shapers Calibrations'
menu_option ' 7' 'Remove' 'Save Z-Offset Macros'
menu_option ' 8' 'Remove' 'Virtual Pins Support'
menu_option ' 9' 'Remove' 'Git Backup'
hr
subtitle '•CAMERA:'
menu_option '10' 'Remove' 'Moonraker Timelapse'
hr
subtitle '•REMOTE ACCESS AND AI DETECTION:'
menu_option '11' 'Remove' 'OctoEverywhere'
menu_option '12' 'Remove' 'Moonraker Obico'
menu_option '13' 'Remove' 'Mobileraker Companion'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function remove_menu_ke() {
clear
remove_menu_ui_ke
local remove_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" remove_menu_opt
case "${remove_menu_opt}" in
1)
if [ ! -d "$MOONRAKER_FOLDER" ] && [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Moonraker and Nginx are not installed!"
else
run "remove_moonraker_nginx" "remove_menu_ui_ke"
fi;;
2)
if [ ! -d "$FLUIDD_FOLDER" ]; then
error_msg "Fluidd is not installed!"
elif [ ! -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is removed!"
echo -e " ${darkred}Please restore Creality Web Interface first if you want to remove Fluidd.${white}"
echo
else
run "remove_fluidd" "remove_menu_ui_ke"
fi;;
3)
if [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Mainsail is not installed!"
elif [ ! -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is removed!"
echo -e " ${darkred}Please restore Creality Web Interface first if you want to remove Mainsail.${white}"
echo
else
run "remove_mainsail" "remove_menu_ui_ke"
fi;;
4)
if [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is not installed!"
elif [ -f "$TIMELAPSE_FILE" ]; then
error_msg "Entware is needed to use Moonraker Timelapse, please uninstall it first!"
elif [ -f "$GIT_BACKUP_FILE" ]; then
error_msg "Entware is needed to use Git Backup, please uninstall it first!"
elif [ -d "$OCTOEVERYWHERE_FOLDER" ]; then
error_msg "Entware is needed to use OctoEverywhere, please uninstall it first!"
elif [ -d "$MOONRAKER_OBICO_FOLDER" ]; then
error_msg "Entware is needed to use Moonraker Obico, please uninstall it first!"
else
run "remove_entware" "remove_menu_ui_ke"
fi;;
5)
if [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is not installed!"
elif [ -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Guppy Screen, please uninstall it first!"
elif [ -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Improved Shapers Calibrations, please uninstall it first!"
elif [ -d "$GIT_BACKUP_FOLDER" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Git Backup, please uninstall it first!"
else
run "remove_gcode_shell_command" "remove_menu_ui_ke"
fi;;
6)
if [ ! -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Improved Shapers Calibrations are not installed!"
else
run "remove_improved_shapers" "remove_menu_ui_ke"
fi;;
7)
if [ ! -f "$SAVE_ZOFFSET_FILE" ]; then
error_msg "Save Z-Offset Macros are not installed!"
else
run "remove_save_zoffset_macros" "remove_menu_ui_ke"
fi;;
8)
if [ ! -f "$VIRTUAL_PINS_FILE" ]; then
error_msg "Virtual Pins Support is not installed!"
else
run "remove_virtual_pins" "remove_menu_ui_ke"
fi;;
9)
if [ ! -f "$GIT_BACKUP_FILE" ]; then
error_msg "Git Backup is not installed!"
else
run "remove_git_backup" "remove_menu_ui_ke"
fi;;
10)
if [ ! -f "$TIMELAPSE_FILE" ]; then
error_msg "Moonraker Timelapse is not installed!"
else
run "remove_moonraker_timelapse" "remove_menu_ui_ke"
fi;;
11)
if [ ! -d "$OCTOEVERYWHERE_FOLDER" ]; then
error_msg "OctoEverywhere is not installed!"
else
run "remove_octoeverywhere" "remove_menu_ui_ke"
fi;;
12)
if [ ! -d "$MOONRAKER_OBICO_FOLDER" ]; then
error_msg "Moonraker Obico is not installed!"
else
run "remove_moonraker_obico" "remove_menu_ui_ke"
fi;;
13)
if [ ! -d "$MOBILERAKER_COMPANION_FOLDER" ]; then
error_msg "Mobileraker Companion is not installed!"
else
run "remove_mobileraker_companion" "remove_menu_ui_ke"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
remove_menu_ke
}

View file

@ -0,0 +1,95 @@
#!/bin/sh
set -e
function tools_menu_ui_ke() {
top_line
title '[ TOOLS MENU ]' "${yellow}"
inner_line
hr
menu_option ' 1' 'Prevent updating' 'Klipper configuration files'
menu_option ' 2' 'Allow updating' 'Klipper configuration files'
hr
menu_option ' 3' 'Restart' 'Nginx service'
menu_option ' 4' 'Restart' 'Moonraker service'
menu_option ' 5' 'Restart' 'Klipper service'
hr
menu_option ' 6' 'Update' 'Entware packages'
hr
menu_option ' 7' 'Clear' 'cache'
menu_option ' 8' 'Clear' 'logs files'
hr
menu_option ' 9' 'Restore' 'a previous firmware'
hr
menu_option '10' 'Reset' 'factory settings'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function tools_menu_ke() {
clear
tools_menu_ui_ke
local tools_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" tools_menu_opt
case "${tools_menu_opt}" in
1)
if [ -f "$INITD_FOLDER"/disabled.S55klipper_service ]; then
error_msg "Updating Klipper configuration files is already prevented!"
else
run "prevent_updating_klipper_files" "tools_menu_ui_ke"
fi;;
2)
if [ ! -f "$INITD_FOLDER"/disabled.S55klipper_service ]; then
error_msg "Updating Klipper configuration files is already allowed!"
else
run "allow_updating_klipper_files" "tools_menu_ui_ke"
fi;;
3)
if [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Nginx is not installed!"
else
run "restart_nginx_action" "tools_menu_ui_ke"
fi;;
4)
if [ ! -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker is not installed!"
else
run "restart_moonraker_action" "tools_menu_ui_ke"
fi;;
5)
if [ ! -f "$INITD_FOLDER"/S55klipper_service ]; then
error_msg "Klipper service is not present!"
else
run "restart_klipper_action" "tools_menu_ui_ke"
fi;;
6)
if [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is not installed!"
else
run "update_entware_packages" "tools_menu_ui_ke"
fi;;
7)
run "clear_cache" "tools_menu_ui_ke";;
8)
run "clear_logs" "tools_menu_ui_ke";;
9)
run "restore_previous_firmware" "tools_menu_ui_ke";;
10)
run "reset_factory_settings" "tools_menu_ui_ke";;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
tools_menu_ke
}

View file

@ -0,0 +1,57 @@
#!/bin/sh
set -e
function backup_restore_menu_ui() {
top_line
title '[ BACKUP & RESTORE MENU ]' "${yellow}"
inner_line
hr
menu_option '1' 'Backup' 'Klipper configuration files'
menu_option '2' 'Restore' 'Klipper configuration files'
hr
menu_option '3' 'Backup' 'Moonraker database'
menu_option '4' 'Restore' 'Moonraker database'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function backup_restore_menu() {
clear
backup_restore_menu_ui
local backup_restore_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" backup_restore_menu_opt
case "${backup_restore_menu_opt}" in
1)
run "backup_klipper_config_files" "backup_restore_menu_ui";;
2)
if [ ! -f "$KLIPPER_CONFIG_FOLDER"/backup_config.tar.gz ]; then
error_msg "Please backup Klipper configuration files before restore!"
else
run "restore_klipper_config_files" "backup_restore_menu_ui"
fi;;
3)
run "backup_moonraker_database" "backup_restore_menu_ui";;
4)
if [ ! -f "$KLIPPER_CONFIG_FOLDER"/backup_database.tar.gz ]; then
error_msg "Please backup Moonraker database before restore!"
else
run "restore_moonraker_database" "backup_restore_menu_ui"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
backup_restore_menu
}

106
scripts/menu/customize_menu.sh Executable file
View file

@ -0,0 +1,106 @@
#!/bin/sh
set -e
function customize_menu_ui() {
top_line
title '[ CUSTOMIZE MENU ]' "${yellow}"
inner_line
hr
menu_option '1' 'Install' 'Custom Boot Display'
menu_option '2' 'Remove' 'Custom Boot Display'
hr
menu_option '3' 'Remove' 'Creality Web Interface'
menu_option '4' 'Restore' 'Creality Web Interface'
hr
menu_option '5' 'Install' 'Guppy Screen'
menu_option '6' 'Remove' 'Guppy Screen'
hr
menu_option '7' 'Install' 'Creality Dynamic Logos for Fluidd'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function customize_menu() {
clear
customize_menu_ui
local customize_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" customize_menu_opt
case "${customize_menu_opt}" in
1)
if [ -f "$BOOT_DISPLAY_FILE" ]; then
error_msg "Custom Boot Display is already installed!"
elif [ ! -d "$BOOT_DISPLAY_FOLDER" ]; then
error_msg "Please use latest firmware to install Custom Boot Display!"
else
run "install_custom_boot_display" "customize_menu_ui"
fi;;
2)
if [ ! -f "$BOOT_DISPLAY_FILE" ]; then
error_msg "Custom Boot Display is not installed!"
elif [ ! -d "$BOOT_DISPLAY_FOLDER" ]; then
error_msg "Please use latest firmware to restore Stock Boot Display!"
else
run "remove_custom_boot_display" "customize_menu_ui"
fi;;
3)
if [ ! -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Fluidd or Mainsail is needed, please install it first!"
elif [ ! -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is already removed!"
echo -e " ${darkred}Please restore Creality Web Interface first if you want to change the default Web Interface.${white}"
echo
else
run "remove_creality_web_interface" "customize_menu_ui"
fi;;
4)
if [ -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is already present!"
else
run "restore_creality_web_interface" "customize_menu_ui"
fi;;
5)
if [ -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Guppy Screen is already installed!"
echo -e " ${darkred}Please remove Guppy Screen first if you want to change the theme.${white}"
echo
elif [ -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Please remove Improved Shapers Calibrations first, Guppy Screen already use it!"
elif [ ! -f /lib/ld-2.29.so ]; then
error_msg "Make sure you're running 1.3.x.x firmware version!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_guppy_screen" "customize_menu_ui"
fi;;
6)
if [ ! -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Guppy Screen is not installed!"
else
run "remove_guppy_screen" "customize_menu_ui"
fi;;
7)
if [ -f "$FLUIDD_LOGO_FILE" ]; then
error_msg "Creality Dynamic Logos for Fluidd are already installed!"
elif [ ! -d "$FLUIDD_FOLDER" ]; then
error_msg "Fluidd is needed, please install it first!"
else
run "install_creality_dynamic_logos" "customize_menu_ui"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
customize_menu
}

197
scripts/menu/functions.sh Executable file
View file

@ -0,0 +1,197 @@
#!/bin/sh
set -e
function top_line() {
echo -e "${white}"
echo -e " ┌──────────────────────────────────────────────────────────────┐"
}
function hr() {
echo -e " │ │"
}
function inner_line() {
echo -e " ├──────────────────────────────────────────────────────────────┤"
}
function bottom_line() {
echo -e " └──────────────────────────────────────────────────────────────┘"
echo -e "${white}"
}
function blank_line() {
echo -e " "
}
function title() {
local text=$1
local color=$2
local max_length=62
local text_length=${#text}
local padding_left=$(((max_length - text_length) / 2))
local padding_right=$((max_length - text_length - padding_left))
printf " │%*s${color}%s${white}%*s│\n" $padding_left '' "$text" $padding_right ''
}
function subtitle() {
local menu_text1=$1
local max_length=61
local padding=$((max_length - ${#menu_text1}))
printf "${blue}${menu_text1}%-${padding}s${white}│\n" ''
}
function main_menu_option() {
local menu_number=$1
local menu_text1=$2
local menu_text2=$3
local max_length=56
local total_text_length=$(( ${#menu_text1} + ${#menu_text2} ))
local padding=$((max_length - total_text_length))
printf "${yellow}${menu_number}${white}) ${green}${menu_text1} ${white}${menu_text2}%-${padding}s${white}│\n" ''
}
function menu_option() {
local menu_number=$1
local menu_text1=$2
local menu_text2=$3
local max_length=60
local total_text_length=$(( ${#menu_text1} + ${#menu_text2} + ${#menu_number} + 4 ))
local padding=$((max_length - total_text_length))
printf "${yellow}${menu_number}${white}) ${white}${menu_text1} ${green}${menu_text2}%-${padding}s${white}│\n" ''
}
function bottom_menu_option() {
local menu_number=$1
local menu_text=$2
local color=$3
local max_length=57
local padding=$((max_length - ${#menu_text}))
printf "$color${menu_number}${white}) ${white}${menu_text}%-${padding}s${white}│\n" ''
}
function info_line() {
local status=$1
local text=$2
local color=$3
local max_length=66
local total_text_length=$(( ${#status} + ${#text} ))
local padding=$((max_length - total_text_length))
printf "$color${status} ${white}${text}%-${padding}s${white}│\n" ''
}
function system_line() {
local title="$1"
local value="$2"
local max_length=61
local title_length=${#title}
local separator=": "
local value_length=${#value}
local value_padding=$((max_length - title_length - ${#separator} - value_length))
printf "${green}%s${white}%s${white}\e[97m%s%-*s%s${white}│\n" "$title" "$separator" "$value" $value_padding ''
}
function install_msg() {
read -p "${white} Are you sure you want to install ${green}${1} ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" $2
}
function remove_msg() {
read -p "${white} Are you sure you want to remove ${green}${1} ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" $2
}
function restore_msg() {
read -p "${white} Are you sure you want to restore ${green}${1} ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" $2
}
function backup_msg() {
read -p "${white} Are you sure you want to backup ${green}${1} ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" $2
}
function restart_msg() {
read -p "${white} Are you sure you want to restart ${green}${1} ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" $2
}
function ok_msg() {
echo
echo -e "${white}${green}${1}${white}"
echo
}
function error_msg() {
echo
echo -e "${white}${darkred}${1}${white}"
echo
}
function run() {
clear
# $1 - Action performed
$1
# $2 - Menu launched after action is completed
$2
}
function check_ipaddress() {
eth0_ip=$(ip -4 addr show eth0 2>/dev/null | grep -o -E '(inet\s)([0-9]+\.){3}[0-9]+' | cut -d ' ' -f 2 | head -n 1)
wlan0_ip=$(ip -4 addr show wlan0 | grep -o -E '(inet\s)([0-9]+\.){3}[0-9]+' | cut -d ' ' -f 2 | head -n 1)
if [ -n "$eth0_ip" ]; then
echo -e "$eth0_ip"
elif [ -n "$wlan0_ip" ]; then
echo -e "$wlan0_ip"
else
echo -e "xxx.xxx.xxx.xxx"
fi
}
function start_moonraker() {
set +e
/etc/init.d/S56moonraker_service start
sleep 1
set -e
}
function stop_moonraker() {
set +e
/etc/init.d/S56moonraker_service stop
sleep 1
set -e
}
function start_nginx() {
set +e
/etc/init.d/S50nginx start
sleep 1
set -e
}
function stop_nginx() {
set +e
/etc/init.d/S50nginx stop
sleep 1
set -e
}
function restart_nginx() {
set +e
/etc/init.d/S50nginx restart
sleep 1
set -e
}
function start_klipper() {
set +e
/etc/init.d/S55klipper_service start
set -e
}
function stop_klipper() {
set +e
/etc/init.d/S55klipper_service stop
set -e
}
function restart_klipper() {
set +e
/etc/init.d/S55klipper_service restart
set -e
}

90
scripts/menu/info_menu.sh Executable file
View file

@ -0,0 +1,90 @@
#!/bin/sh
set -e
function check_folder() {
local folder_path="$1"
if [ -d "$folder_path" ]; then
echo -e "${green}"
else
echo -e "${red}"
fi
}
function check_file() {
local file_path="$1"
if [ -f "$file_path" ]; then
echo -e "${green}"
else
echo -e "${red}"
fi
}
function info_menu_ui() {
top_line
title '[ INFORMATIONS MENU ]' "${yellow}"
inner_line
hr
subtitle '•ESSENTIALS:'
info_line "$(check_folder "$MOONRAKER_FOLDER")" 'Moonraker & Nginx'
info_line "$(check_folder "$FLUIDD_FOLDER")" 'Fluidd'
info_line "$(check_folder "$MAINSAIL_FOLDER")" 'Mainsail'
hr
subtitle '•UTILITIES:'
info_line "$(check_file "$ENTWARE_FILE")" 'Entware'
info_line "$(check_file "$KLIPPER_SHELL_FILE")" 'Klipper Gcode Shell Command'
hr
subtitle '•IMPROVEMENTS:'
info_line "$(check_folder "$KAMP_FOLDER")" 'Klipper Adaptive Meshing & Purging'
info_line "$(check_file "$BUZZER_FILE")" 'Buzzer Support'
info_line "$(check_folder "$NOZZLE_CLEANING_FOLDER")" 'Nozzle Cleaning Fan Control'
info_line "$(check_file "$FAN_CONTROLS_FILE")" 'Fans Control Macros'
info_line "$(check_folder "$IMP_SHAPERS_FOLDER")" 'Improved Shapers Calibrations'
info_line "$(check_file "$USEFUL_MACROS_FILE")" 'Useful Macros'
info_line "$(check_file "$SAVE_ZOFFSET_FILE")" 'Save Z-Offset Macros'
info_line "$(check_file "$SCREWS_ADJUST_FILE")" 'Screws Tilt Adjust Support'
info_line "$(check_file "$VIRTUAL_PINS_FILE")" 'Virtual Pins Support'
info_line "$(check_file "$M600_SUPPORT_FILE")" 'M600 Support'
info_line "$(check_file "$GIT_BACKUP_FILE")" 'Git Backup'
hr
subtitle '•CAMERA:'
info_line "$(check_file "$TIMELAPSE_FILE")" 'Moonraker Timelapse'
info_line "$(check_file "$CAMERA_SETTINGS_FILE")" 'Camera Settings Control'
hr
subtitle '•REMOTE ACCESS AND AI DETECTION:'
info_line "$(check_folder "$OCTOEVERYWHERE_FOLDER")" 'OctoEverywhere'
info_line "$(check_folder "$MOONRAKER_OBICO_FOLDER")" 'Obico'
info_line "$(check_folder "$MOBILERAKER_COMPANION_FOLDER")" 'Mobileraker Companion'
hr
subtitle '•CUSTOMIZATION:'
info_line "$(check_file "$BOOT_DISPLAY_FILE")" 'Custom Boot Display'
info_line "$(check_file "$CREALITY_WEB_FILE")" 'Creality Web Interface'
info_line "$(check_folder "$GUPPY_SCREEN_FOLDER")" 'Guppy Screen'
info_line "$(check_file "$FLUIDD_LOGO_FILE")" 'Creality Dynamic Logos for Fluidd'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function info_menu() {
clear
info_menu_ui
local info_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" info_menu_opt
case "${info_menu_opt}" in
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
info_menu
}

228
scripts/menu/install_menu.sh Executable file
View file

@ -0,0 +1,228 @@
#!/bin/sh
set -e
function install_menu_ui() {
top_line
title '[ INSTALL MENU ]' "${yellow}"
inner_line
hr
subtitle '•ESSENTIALS:'
menu_option ' 1' 'Install' 'Moonraker and Nginx'
menu_option ' 2' 'Install' 'Fluidd (port 4408)'
menu_option ' 3' 'Install' 'Mainsail (port 4409)'
hr
subtitle '•UTILITIES:'
menu_option ' 4' 'Install' 'Entware'
menu_option ' 5' 'Install' 'Klipper Gcode Shell Command'
hr
subtitle '•IMPROVEMENTS:'
menu_option ' 6' 'Install' 'Klipper Adaptive Meshing & Purging'
menu_option ' 7' 'Install' 'Buzzer Support'
menu_option ' 8' 'Install' 'Nozzle Cleaning Fan Control'
menu_option ' 9' 'Install' 'Fans Control Macros'
menu_option '10' 'Install' 'Improved Shapers Calibrations'
menu_option '11' 'Install' 'Useful Macros'
menu_option '12' 'Install' 'Save Z-Offset Macros'
menu_option '13' 'Install' 'Screws Tilt Adjust Support'
menu_option '14' 'Install' 'Virtual Pins Support'
menu_option '15' 'Install' 'M600 Support'
menu_option '16' 'Install' 'Git Backup'
hr
subtitle '•CAMERA:'
menu_option '17' 'Install' 'Moonraker Timelapse'
menu_option '18' 'Install' 'Camera Settings Control'
hr
subtitle '•REMOTE ACCESS AND AI DETECTION:'
menu_option '19' 'Install' 'OctoEverywhere'
menu_option '20' 'Install' 'Moonraker Obico'
menu_option '21' 'Install' 'Mobileraker Companion'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function install_menu() {
clear
install_menu_ui
local install_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" install_menu_opt
case "${install_menu_opt}" in
1)
if [ -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker and Nginx are already installed!"
else
run "install_moonraker_nginx" "install_menu_ui"
fi;;
2)
if [ -d "$FLUIDD_FOLDER" ]; then
error_msg "Fluidd is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ] && [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
else
run "install_fluidd" "install_menu_ui"
fi;;
3)
if [ -d "$MAINSAIL_FOLDER" ]; then
error_msg "Mainsail is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ] && [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
else
run "install_mainsail" "install_menu_ui"
fi;;
4)
if [ -f "$ENTWARE_FILE" ]; then
error_msg "Entware is already installed!"
else
run "install_entware" "install_menu_ui"
fi;;
5)
if [ -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is already installed!"
else
run "install_gcode_shell_command" "install_menu_ui"
fi;;
6)
if [ -d "$KAMP_FOLDER" ]; then
error_msg "Klipper Adaptive Meshing & Purging is already installed!"
elif [ ! -f "$VIRTUAL_PINS_FILE" ]; then
error_msg "Virtual Pins Support is needed, please install it first!"
else
run "install_kamp" "install_menu_ui"
fi;;
7)
if [ -f "$BUZZER_FILE" ]; then
error_msg "Buzzer Support is already installed!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_buzzer_support" "install_menu_ui"
fi;;
8)
if [ -d "$NOZZLE_CLEANING_FOLDER" ]; then
error_msg "Nozzle Cleaning Fan Control is already installed!"
else
run "install_nozzle_cleaning_fan_control" "install_menu_ui"
fi;;
9)
if [ -f "$FAN_CONTROLS_FILE" ]; then
error_msg "Fans Control Macros are already installed!"
else
run "install_fans_control_macros" "install_menu_ui"
fi;;
10)
if [ -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Improved Shapers Calibrations are already installed!"
elif [ -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Guppy Screen already has these features!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_improved_shapers" "install_menu_ui"
fi;;
11)
if [ -f "$USEFUL_MACROS_FILE" ]; then
error_msg "Useful Macros are already installed!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_useful_macros" "install_menu_ui"
fi;;
12)
if [ -f "$SAVE_ZOFFSET_FILE" ]; then
error_msg "Save Z-Offset Macros are already installed!"
else
run "install_save_zoffset_macros" "install_menu_ui"
fi;;
13)
if [ -f "$SCREWS_ADJUST_FILE" ]; then
error_msg "Screws Tilt Adjust Support is already installed!"
else
run "install_screws_tilt_adjust" "install_menu_ui"
fi;;
14)
if [ -f "$VIRTUAL_PINS_FILE" ]; then
error_msg "Virtual Pins Support is already installed!"
else
run "install_virtual_pins" "install_menu_ui"
fi;;
15)
if [ -f "$M600_SUPPORT_FILE" ]; then
error_msg "M600 Support is already installed!"
else
run "install_m600_support" "install_menu_ui"
fi;;
16)
if [ -f "$GIT_BACKUP_FILE" ]; then
error_msg "Git Backup is already installed!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_git_backup" "install_menu_ui"
fi;;
17)
if [ -f "$TIMELAPSE_FILE" ]; then
error_msg "Moonraker Timelapse is already installed!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
else
run "install_moonraker_timelapse" "install_menu_ui"
fi;;
18)
if [ -f "$CAMERA_SETTINGS_FILE" ]; then
error_msg "Camera Settings Control is already installed!"
elif v4l2-ctl --list-devices | grep -q 'CCX2F3299'; then
error_msg "You have the new hardware version of the camera and it's not compatible!"
elif [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed, please install it first!"
else
run "install_camera_settings_control" "install_menu_ui"
fi;;
19)
if [ -d "$OCTOEVERYWHERE_FOLDER" ]; then
error_msg "OctoEverywhere is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
elif [ ! -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Fluidd or Mainsail is needed, please install it first!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
else
run "install_octoeverywhere" "install_menu_ui"
fi;;
20)
if [ -d "$MOONRAKER_OBICO_FOLDER" ]; then
error_msg "Moonraker Obico is already installed!"
elif [ ! -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker and Nginx are needed, please install them first!"
elif [ ! -d "$FLUIDD_FOLDER" ] && [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Fluidd or Mainsail is needed, please install it first!"
elif [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is needed, please install it first!"
else
run "install_moonraker_obico" "install_menu_ui"
fi;;
21)
if [ -d "$MOBILERAKER_COMPANION_FOLDER" ]; then
error_msg "Mobileraker Companion is already installed!"
else
run "install_mobileraker_companion" "install_menu_ui"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
install_menu
}

121
scripts/menu/main_menu.sh Executable file
View file

@ -0,0 +1,121 @@
#!/bin/sh
set -e
if /usr/bin/get_sn_mac.sh model 2>&1 | grep -iq "K1"; then K1=1; else K1=0; fi
function get_script_version() {
local version
cd "${HELPER_SCRIPT_FOLDER}"
version="$(git describe HEAD --always --tags | sed 's/-.*//')"
echo "${cyan}${version}${white}"
}
function version_line() {
local content="$1"
local content_length="${#content}"
local width=$((73))
local padding_length=$((width - content_length - 3))
printf " │ %*s%s%s\n" $padding_length '' "$content" " │"
}
function script_title() {
local title
if [ $K1 -eq 0 ]; then
title="KE"
else
title="K1"
fi
echo "${title}"
}
function fw_version() {
local firmware
if [ $K1 -eq 0 ]; then
firmware="1.1.0.12"
else
firmware="1.3.3.5"
fi
echo "${firmware}"
}
function main_menu_ui() {
top_line
title "• HELPER SCRIPT FOR CREALITY $(script_title) SERIES •" "${blue}"
title "Copyright © Cyril Guislain (Guilouz)" "${white}"
inner_line
title "/!\\ ONLY USE IT WITH FIRMWARE $(fw_version) AND ABOVE /!\\" "${darkred}"
inner_line
hr
main_menu_option '1' '[Install]' 'Menu'
main_menu_option '2' '[Remove]' 'Menu'
main_menu_option '3' '[Customize]' 'Menu'
main_menu_option '4' '[Backup & Restore]' 'Menu'
main_menu_option '5' '[Tools]' 'Menu'
main_menu_option '6' '[Informations]' 'Menu'
main_menu_option '7' '[System]' 'Menu'
hr
inner_line
hr
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function main_menu() {
clear
main_menu_ui
local main_menu_opt
while true; do
read -p "${white} Type your choice and validate with Enter: ${yellow}" main_menu_opt
case "${main_menu_opt}" in
1) clear
if [ $K1 -eq 0 ]; then
install_menu_ke
else
install_menu
fi
break;;
2) clear
if [ $K1 -eq 0 ]; then
remove_menu_ke
else
remove_menu
fi
break;;
3) clear
if [ $K1 -eq 0 ]; then
customize_menu_ke
else
customize_menu
fi
break;;
4) clear
backup_restore_menu
break;;
5) clear
if [ $K1 -eq 0 ]; then
tools_menu_ke
else
tools_menu
fi
main_ui;;
6) clear
if [ $K1 -eq 0 ]; then
info_menu_ke
else
info_menu
fi
break;;
7) clear
system_menu
break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
main_menu
}

220
scripts/menu/remove_menu.sh Executable file
View file

@ -0,0 +1,220 @@
#!/bin/sh
set -e
function remove_menu_ui() {
top_line
title '[ REMOVE MENU ]' "${yellow}"
inner_line
hr
subtitle '•ESSENTIALS:'
menu_option ' 1' 'Remove' 'Moonraker and Nginx'
menu_option ' 2' 'Remove' 'Fluidd (port 4408)'
menu_option ' 3' 'Remove' 'Mainsail (port 4409)'
hr
subtitle '•UTILITIES:'
menu_option ' 4' 'Remove' 'Entware'
menu_option ' 5' 'Remove' 'Klipper Gcode Shell Command'
hr
subtitle '•IMPROVEMENTS:'
menu_option ' 6' 'Remove' 'Klipper Adaptive Meshing & Purging'
menu_option ' 7' 'Remove' 'Buzzer Support'
menu_option ' 8' 'Remove' 'Nozzle Cleaning Fan Control'
menu_option ' 9' 'Remove' 'Fans Control Macros'
menu_option '10' 'Remove' 'Improved Shapers Calibrations'
menu_option '11' 'Remove' 'Useful Macros'
menu_option '12' 'Remove' 'Save Z-Offset Macros'
menu_option '13' 'Remove' 'Screws Tilt Adjust Support'
menu_option '14' 'Remove' 'Virtual Pins Support'
menu_option '15' 'Remove' 'M600 Support'
menu_option '16' 'Remove' 'Git Backup'
hr
subtitle '•CAMERA:'
menu_option '17' 'Remove' 'Moonraker Timelapse'
menu_option '18' 'Remove' 'Camera Settings Control'
hr
subtitle '•REMOTE ACCESS AND AI DETECTION:'
menu_option '19' 'Remove' 'OctoEverywhere'
menu_option '20' 'Remove' 'Moonraker Obico'
menu_option '21' 'Remove' 'Mobileraker Companion'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function remove_menu() {
clear
remove_menu_ui
local remove_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" remove_menu_opt
case "${remove_menu_opt}" in
1)
if [ ! -d "$MOONRAKER_FOLDER" ] && [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Moonraker and Nginx are not installed!"
else
run "remove_moonraker_nginx" "remove_menu_ui"
fi;;
2)
if [ ! -d "$FLUIDD_FOLDER" ]; then
error_msg "Fluidd is not installed!"
elif [ ! -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is removed!"
echo -e " ${darkred}Please restore Creality Web Interface first if you want to remove Fluidd.${white}"
echo
else
run "remove_fluidd" "remove_menu_ui"
fi;;
3)
if [ ! -d "$MAINSAIL_FOLDER" ]; then
error_msg "Mainsail is not installed!"
elif [ ! -f "$CREALITY_WEB_FILE" ]; then
error_msg "Creality Web Interface is removed!"
echo -e " ${darkred}Please restore Creality Web Interface first if you want to remove Mainsail.${white}"
echo
else
run "remove_mainsail" "remove_menu_ui"
fi;;
4)
if [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is not installed!"
elif [ -f "$TIMELAPSE_FILE" ]; then
error_msg "Entware is needed to use Moonraker Timelapse, please uninstall it first!"
elif [ -f "$GIT_BACKUP_FILE" ]; then
error_msg "Entware is needed to use Git Backup, please uninstall it first!"
elif [ -d "$OCTOEVERYWHERE_FOLDER" ]; then
error_msg "Entware is needed to use OctoEverywhere, please uninstall it first!"
elif [ -d "$MOONRAKER_OBICO_FOLDER" ]; then
error_msg "Entware is needed to use Moonraker Obico, please uninstall it first!"
else
run "remove_entware" "remove_menu_ui"
fi;;
5)
if [ ! -f "$KLIPPER_SHELL_FILE" ]; then
error_msg "Klipper Gcode Shell Command is not installed!"
elif [ -f "$BUZZER_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Buzzer Support, please uninstall it first!"
elif [ -f "$CAMERA_SETTINGS_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Camera Settings Control, please uninstall it first!"
elif [ -d "$GUPPY_SCREEN_FOLDER" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Guppy Screen, please uninstall it first!"
elif [ -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Improved Shapers Calibrations, please uninstall it first!"
elif [ -d "$GIT_BACKUP_FOLDER" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Git Backup, please uninstall it first!"
elif [ -f "$USEFUL_MACROS_FILE" ]; then
error_msg "Klipper Gcode Shell Command is needed to use Useful Macros, please uninstall it first!"
else
run "remove_gcode_shell_command" "remove_menu_ui"
fi;;
6)
if [ ! -d "$KAMP_FOLDER" ]; then
error_msg "Klipper Adaptive Meshing & Purging is not installed!"
else
run "remove_kamp" "remove_menu_ui"
fi;;
7)
if [ ! -f "$BUZZER_FILE" ]; then
error_msg "Buzzer Support is not installed!"
else
run "remove_buzzer_support" "remove_menu_ui"
fi;;
8)
if [ ! -d "$NOZZLE_CLEANING_FOLDER" ]; then
error_msg "Nozzle Cleaning Fan Control is not installed!"
else
run "remove_nozzle_cleaning_fan_control" "remove_menu_ui"
fi;;
9)
if [ ! -f "$FAN_CONTROLS_FILE" ]; then
error_msg "Fans Control Macros are not installed!"
else
run "remove_fans_control_macros" "remove_menu_ui"
fi;;
10)
if [ ! -d "$IMP_SHAPERS_FOLDER" ]; then
error_msg "Improved Shapers Calibrations are not installed!"
else
run "remove_improved_shapers" "remove_menu_ui"
fi;;
11)
if [ ! -f "$USEFUL_MACROS_FILE" ]; then
error_msg "Useful Macros are not installed!"
else
run "remove_useful_macros" "remove_menu_ui"
fi;;
12)
if [ ! -f "$SAVE_ZOFFSET_FILE" ]; then
error_msg "Save Z-Offset Macros are not installed!"
else
run "remove_save_zoffset_macros" "remove_menu_ui"
fi;;
13)
if [ ! -f "$SCREWS_ADJUST_FILE" ]; then
error_msg "Screws Tilt Adjust Support is not installed!"
else
run "remove_screws_tilt_adjust" "remove_menu_ui"
fi;;
14)
if [ ! -f "$VIRTUAL_PINS_FILE" ]; then
error_msg "Virtual Pins Support is not installed!"
else
run "remove_virtual_pins" "remove_menu_ui"
fi;;
15)
if [ ! -f "$M600_SUPPORT_FILE" ]; then
error_msg "M600 Support is not installed!"
else
run "remove_m600_support" "remove_menu_ui"
fi;;
16)
if [ ! -f "$GIT_BACKUP_FILE" ]; then
error_msg "Git Backup is not installed!"
else
run "remove_git_backup" "remove_menu_ui"
fi;;
17)
if [ ! -f "$TIMELAPSE_FILE" ]; then
error_msg "Moonraker Timelapse is not installed!"
else
run "remove_moonraker_timelapse" "remove_menu_ui"
fi;;
18)
if [ ! -f "$CAMERA_SETTINGS_FILE" ]; then
error_msg "Camera Settings Control is not installed!"
else
run "remove_camera_settings_control" "remove_menu_ui"
fi;;
19)
if [ ! -d "$OCTOEVERYWHERE_FOLDER" ]; then
error_msg "OctoEverywhere is not installed!"
else
run "remove_octoeverywhere" "remove_menu_ui"
fi;;
20)
if [ ! -d "$MOONRAKER_OBICO_FOLDER" ]; then
error_msg "Moonraker Obico is not installed!"
else
run "remove_moonraker_obico" "remove_menu_ui"
fi;;
21)
if [ ! -d "$MOBILERAKER_COMPANION_FOLDER" ]; then
error_msg "Mobileraker Companion is not installed!"
else
run "remove_mobileraker_companion" "remove_menu_ui"
fi;;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
remove_menu
}

80
scripts/menu/system_menu.sh Executable file
View file

@ -0,0 +1,80 @@
#!/bin/sh
set -e
function check_fw_version() {
file="/usr/data/creality/userdata/config/system_version.json"
if [ -e "$file" ]; then
cat "$file" | jq -r '.sys_version'
else
echo -e "N/A"
fi
}
function check_connection() {
eth0_ip=$(ip -4 addr show eth0 2>/dev/null | grep -o -E '(inet\s)([0-9]+\.){3}[0-9]+' | cut -d ' ' -f 2 | head -n 1)
wlan0_ip=$(ip -4 addr show wlan0 | grep -o -E '(inet\s)([0-9]+\.){3}[0-9]+' | cut -d ' ' -f 2 | head -n 1)
if [ -n "$eth0_ip" ]; then
echo -e "$eth0_ip (ETHERNET)"
elif [ -n "$wlan0_ip" ]; then
echo -e "$wlan0_ip (WLAN)"
else
echo -e "xxx.xxx.xxx.xxx"
fi
}
function system_menu_ui() {
memfree=`cat /proc/meminfo | grep MemFree | awk {'print $2'}`
memtotal=`cat /proc/meminfo | grep MemTotal | awk {'print $2'}`
pourcent=$((($memfree * 100)/$memtotal))
diskused=`df -h | grep /dev/mmcblk0p10 | awk {'print $3 " / " $2 " (" $4 " available)" '}`
process=`ps ax | wc -l | tr -d " "`
uptime=`cat /proc/uptime | cut -f1 -d.`
upDays=$((uptime/60/60/24))
upHours=$((uptime/60/60%24))
upMins=$((uptime/60%60))
load=`cat /proc/loadavg | awk {'print $1 " (1 min.) / " $2 " (5 min.) / " $3 " (15 min.)"'}`
device_sn=$(cat /usr/data/creality/userdata/config/system_config.json | grep -o '"device_sn":"[^"]*' | awk -F '"' '{print $4}')
mac_address=$(cat /usr/data/creality/userdata/config/system_config.json | grep -o '"device_mac":"[^"]*' | awk -F '"' '{print $4}' | sed 's/../&:/g; s/:$//')
top_line
title '[ SYSTEM MENU ]' "${yellow}"
inner_line
hr
system_line " System" "$(uname -s) (Kernel $(uname -r))" "${green}"
system_line " Firmware" "$(check_fw_version)"
system_line " Hostname" "$(uname -n)"
system_line " Device SN" "$device_sn"
system_line " IP Address" "$(check_connection)"
system_line "MAC Address" "$mac_address"
system_line " RAM Usage" "$(($memfree/1024)) MB / $(($memtotal/1024)) MB ($pourcent% available)"
system_line " Disk Usage" "$diskused"
system_line " Uptime" "$upDays days $upHours hours $upMins minutes"
system_line " Processes" "$process running process"
system_line "System Load" "$load"
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function system_menu() {
clear
system_menu_ui
local system_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" system_menu_opt
case "${system_menu_opt}" in
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
system_menu
}

100
scripts/menu/tools_menu.sh Executable file
View file

@ -0,0 +1,100 @@
#!/bin/sh
set -e
function tools_menu_ui() {
top_line
title '[ TOOLS MENU ]' "${yellow}"
inner_line
hr
menu_option ' 1' 'Prevent updating' 'Klipper configuration files'
menu_option ' 2' 'Allow updating' 'Klipper configuration files'
menu_option ' 3' 'Fix' 'printing Gcode files from folder'
hr
menu_option ' 4' 'Restart' 'Nginx service'
menu_option ' 5' 'Restart' 'Moonraker service'
menu_option ' 6' 'Restart' 'Klipper service'
hr
menu_option ' 7' 'Update' 'Entware packages'
hr
menu_option ' 8' 'Clear' 'cache'
menu_option ' 9' 'Clear' 'logs files'
hr
menu_option '10' 'Restore' 'a previous firmware'
hr
menu_option '11' 'Reset' 'factory settings'
hr
inner_line
hr
bottom_menu_option 'b' 'Back to [Main Menu]' "${yellow}"
bottom_menu_option 'q' 'Exit' "${darkred}"
hr
version_line "$(get_script_version)"
bottom_line
}
function tools_menu() {
clear
tools_menu_ui
local tools_menu_opt
while true; do
read -p " ${white}Type your choice and validate with Enter: ${yellow}" tools_menu_opt
case "${tools_menu_opt}" in
1)
if [ -f "$INITD_FOLDER"/disabled.S55klipper_service ]; then
error_msg "Updating Klipper configuration files is already prevented!"
else
run "prevent_updating_klipper_files" "tools_menu_ui"
fi;;
2)
if [ ! -f "$INITD_FOLDER"/disabled.S55klipper_service ]; then
error_msg "Updating Klipper configuration files is already allowed!"
else
run "allow_updating_klipper_files" "tools_menu_ui"
fi;;
3)
if [ -f "$KLIPPER_KLIPPY_FOLDER"/gcode.py ]; then
run "printing_gcode_from_folder" "tools_menu_ui"
fi;;
4)
if [ ! -d "$NGINX_FOLDER" ]; then
error_msg "Nginx is not installed!"
else
run "restart_nginx_action" "tools_menu_ui"
fi;;
5)
if [ ! -d "$MOONRAKER_FOLDER" ]; then
error_msg "Moonraker is not installed!"
else
run "restart_moonraker_action" "tools_menu_ui"
fi;;
6)
if [ ! -f "$INITD_FOLDER"/S55klipper_service ]; then
error_msg "Klipper service is not present!"
else
run "restart_klipper_action" "tools_menu_ui"
fi;;
7)
if [ ! -f "$ENTWARE_FILE" ]; then
error_msg "Entware is not installed!"
else
run "update_entware_packages" "tools_menu_ui"
fi;;
8)
run "clear_cache" "tools_menu_ui";;
9)
run "clear_logs" "tools_menu_ui";;
10)
run "restore_previous_firmware" "tools_menu_ui";;
11)
run "reset_factory_settings" "tools_menu_ui";;
B|b)
clear; main_menu; break;;
Q|q)
clear; exit 0;;
*)
error_msg "Please select a correct choice!";;
esac
done
tools_menu
}

View file

@ -0,0 +1,83 @@
#!/bin/sh
set -e
function mobileraker_companion_message(){
top_line
title 'Mobileraker Companion' "${yellow}"
inner_line
hr
echo -e "${cyan}Mobileraker Companion allows to push notification for ${white}"
echo -e "${cyan}Klipper using Moonraker for Mobileraker phone App. ${white}"
hr
bottom_line
}
function install_mobileraker_companion(){
mobileraker_companion_message
local yn
while true; do
install_msg "Mobileraker Companion" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Downloading Mobileraker Companion..."
git config --global http.sslVerify false
git clone "$MOBILERAKER_COMPANION_URL" "$MOBILERAKER_COMPANION_FOLDER"
echo -e "Info: Running Mobileraker Companion installer..."
sh "$MOBILERAKER_COMPANION_FOLDER"/scripts/install.sh
echo
if grep -q "#\[update_manager mobileraker\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Enabling Mobileraker Companion configurations for Update Manager..."
sed -i -e 's/^\s*#[[:space:]]*\[update_manager mobileraker\]/[update_manager mobileraker]/' -e '/^\[update_manager mobileraker\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MOONRAKER_CFG"
else
echo -e "Info: Mobileraker Companion configurations are already enabled for Update Manager..."
fi
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Mobileraker Companion has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_mobileraker_companion(){
mobileraker_companion_message
local yn
while true; do
remove_msg "Mobileraker Companion" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Running Mobileraker Companion uninstaller..."
sh "$MOBILERAKER_COMPANION_FOLDER"/scripts/install.sh -uninstall
echo
if grep -q "\[update_manager mobileraker\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Disabling Mobileraker Companion configurations for Update Manager..."
sed -i '/^\[update_manager mobileraker\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MOONRAKER_CFG"
else
echo -e "Info: Mobileraker Companion configurations are already disabled for Update Manager..."
fi
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Mobileraker Companion has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

107
scripts/moonraker_nginx.sh Executable file
View file

@ -0,0 +1,107 @@
#!/bin/sh
set -e
function moonraker_nginx_message(){
top_line
title 'Moonraker and Nginx' "${yellow}"
inner_line
hr
echo -e "${cyan}Moonraker is a Python 3 based web server that exposes APIs ${white}"
echo -e "${cyan}with which client applications may use to interact with ${white}"
echo -e "${cyan}Klipper firmware. ${white}"
echo -e "${cyan}Nginx is a web server that can also be used as a reverse ${white}"
echo -e "${cyan}proxy, load balancer, mail proxy and HTTP cache. ${white}"
hr
bottom_line
}
function install_moonraker_nginx(){
moonraker_nginx_message
local yn
while true; do
install_msg "Moonraker and Nginx" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Extracting files..."
tar -xvf "$MOONRAKER_URL1" -C "$USR_DATA"
echo -e "Info: Copying services files..."
if [ ! -f "$INITD_FOLDER"/S50nginx ]; then
cp "$NGINX_SERVICE_URL" "$INITD_FOLDER"/S50nginx
chmod +x "$INITD_FOLDER"/S50nginx
fi
if [ ! -f "$INITD_FOLDER"/S56moonraker_service ]; then
cp "$MOONRAKER_SERVICE_URL" "$INITD_FOLDER"/S56moonraker_service
chmod +x "$INITD_FOLDER"/S56moonraker_service
fi
echo -e "Info: Copying Moonraker configuration file..."
cp "$MOONRAKER_URL2" "$KLIPPER_CONFIG_FOLDER"/moonraker.conf
if [ -f "$PRINTER_DATA_FOLDER"/moonraker.asvc ]; then
rm -f "$PRINTER_DATA_FOLDER"/moonraker.asvc
fi
cp "$MOONRAKER_URL3" "$PRINTER_DATA_FOLDER"/moonraker.asvc
echo -e "Info: Applying changes from official repo..."
cd "$MOONRAKER_FOLDER"/moonraker
git stash; git checkout master; git pull
echo -e "Info: Installing Supervisor Lite..."
chmod 755 "$SUPERVISOR_URL"
ln -sf "$SUPERVISOR_URL" "$SUPERVISOR_FILE"
echo -e "Info: Installing Host Controls Support..."
chmod 755 "$SUDO_URL"
chmod 755 "$SYSTEMCTL_URL"
ln -sf "$SUDO_URL" "$SUDO_FILE"
ln -sf "$SYSTEMCTL_URL" "$SYSTEMCTL_FILE"
echo -e "Info: Installing necessary packages..."
cd "$MOONRAKER_FOLDER"/moonraker-env/bin
python3 -m pip install --no-cache-dir pyserial-asyncio==0.6
echo -e "Info: Starting Nginx service..."
start_nginx
echo -e "Info: Starting Moonraker service..."
start_moonraker
ok_msg "Moonraker and Nginx have been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_moonraker_nginx(){
moonraker_nginx_message
local yn
while true; do
remove_msg "Moonraker and Nginx" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Stopping Moonraker and Nginx services..."
cd /overlay/upper
stop_moonraker
stop_nginx
echo -e "Info: Removing files..."
rm -f "$INITD_FOLDER"/S50nginx
rm -f "$INITD_FOLDER"/S56moonraker_service
rm -f "$KLIPPER_CONFIG_FOLDER"/moonraker.conf
rm -f "$KLIPPER_CONFIG_FOLDER"/.moonraker.conf.bkp
rm -f "$PRINTER_DATA_FOLDER"/.moonraker.uuid
rm -f "$PRINTER_DATA_FOLDER"/moonraker.asvc
rm -rf "$PRINTER_DATA_FOLDER"/comms
rm -rf "$NGINX_FOLDER"
rm -rf "$MOONRAKER_FOLDER"
rm -f "$SUPERVISOR_FILE"
rm -f "$SUDO_FILE"
rm -f "$SYSTEMCTL_FILE"
ok_msg "Moonraker and Nginx have been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

90
scripts/moonraker_obico.sh Executable file
View file

@ -0,0 +1,90 @@
#!/bin/sh
set -e
function moonraker_obico_message(){
top_line
title 'Moonraker Obico' "${yellow}"
inner_line
hr
echo -e "${cyan}Obico is a Moonraker plugin that allows you to monitor and ${white}"
echo -e "${cyan}control your 3D printer from anywhere. ${white}"
hr
bottom_line
}
function install_moonraker_obico(){
moonraker_obico_message
local yn
while true; do
install_msg "Moonraker Obico" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -d "$MOONRAKER_OBICO_FOLDER" ]; then
echo -e "Info: Moonraker Obico is already installed. Download skipped."
else
echo -e "Info: Downloading Moonraker Obico..."
git config --global http.sslVerify false
git clone "$MOONRAKER_OBICO_URL" "$MOONRAKER_OBICO_FOLDER"
fi
echo -e "Info: Running Moonraker Obico installer..."
cd "$MOONRAKER_OBICO_FOLDER"
sh ./scripts/install_creality.sh -k
ok_msg "Moonraker Obico has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_moonraker_obico(){
moonraker_obico_message
local yn
while true; do
remove_msg "Moonraker Obico" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if grep -q "include moonraker_obico_macros" "$PRINTER_CFG" ; then
echo -e "Info: Removing Moonraker Obico configurations in printer.cfg file..."
sed -i '/include moonraker_obico_macros\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Moonraker Obico configurations are already removed in printer.cfg file..."
fi
if grep -q "\[include moonraker-obico-update.cfg\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Removing Moonraker Obico configurations in moonraker.conf file..."
sed -i '/include moonraker-obico-update\.cfg/d' "$MOONRAKER_CFG"
else
echo -e "Info: Moonraker Obico configurations are already removed in moonraker.conf file..."
fi
echo -e "Info: Removing files..."
rm -rf "$MOONRAKER_OBICO_FOLDER"
rm -rf /usr/data/moonraker-obico-env
rm -f "$KLIPPER_CONFIG_FOLDER"/moonraker-obico-update.cfg
rm -f "$KLIPPER_CONFIG_FOLDER"/config/moonraker-obico.cfg
rm -f /etc/init.d/S99moonraker_obico
if [ -f "$ENTWARE_FILE" ]; then
echo -e "Info: Removing packages..."
"$ENTWARE_FILE" --autoremove remove python3
"$ENTWARE_FILE" --autoremove remove python3-pip
fi
echo -e "Info: Restarting Moonraker service..."
stop_moonraker
start_moonraker
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Moonraker Obico has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

101
scripts/moonraker_timelapse.sh Executable file
View file

@ -0,0 +1,101 @@
#!/bin/sh
set -e
function moonraker_timelapse_message(){
top_line
title 'Moonraker Timelapse' "${yellow}"
inner_line
hr
echo -e "${cyan}Moonraker Timelapse is a 3rd party Moonraker component to ${white}"
echo -e "${cyan}create timelapse of 3D prints. ${white}"
hr
bottom_line
}
function install_moonraker_timelapse(){
moonraker_timelapse_message
local yn
while true; do
install_msg "Moonraker Timelapse" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/timelapse.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/timelapse.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Linking file..."
ln -sf "$TIMELAPSE_URL1" "$TIMELAPSE_FILE"
ln -sf "$TIMELAPSE_URL2" "$HS_CONFIG_FOLDER"/timelapse.cfg
if grep -q "include Helper-Script/timelapse" "$PRINTER_CFG" ; then
echo -e "Info: Moonraker Timelapse configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Moonraker Timelapse configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/timelapse\.cfg\]' "$PRINTER_CFG"
fi
if grep -q "#\[timelapse\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Enabling Moonraker Timelapse configurations in moonraker.conf file..."
sed -i -e 's/^\s*#[[:space:]]*\[timelapse\]/[timelapse]/' -e '/^\[timelapse\]/,/^\s*$/ s/^\(\s*\)#/\1/' "$MOONRAKER_CFG"
else
echo -e "Info: Moonraker Timelapse configurations are already enabled in moonraker.conf file..."
fi
echo -e "Info: Updating ffmpeg..."
"$ENTWARE_FILE" update && "$ENTWARE_FILE" upgrade ffmpeg
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Moonraker Timelapse has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_moonraker_timelapse(){
moonraker_timelapse_message
local yn
while true; do
remove_msg "Moonraker Timelapse" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -f "$HS_CONFIG_FOLDER"/timelapse.cfg
rm -f /usr/data/moonraker/moonraker/moonraker/components/timelapse.py
rm -f /usr/data/moonraker/moonraker/moonraker/components/timelapse.pyc
if [ -f /opt/bin/ffmpeg ]; then
"$ENTWARE_FILE" --autoremove remove ffmpeg
fi
if grep -q "include Helper-Script/timelapse" "$PRINTER_CFG" ; then
echo -e "Info: Removing Moonraker Timelapse configurations in printer.cfg file..."
sed -i '/include Helper-Script\/timelapse\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Moonraker Timelapse configurations are already removed in printer.cfg file..."
fi
if grep -q "\[timelapse\]" "$MOONRAKER_CFG" ; then
echo -e "Info: Disabling Moonraker Timelapse configurations in moonraker.conf file..."
sed -i '/^\[timelapse\]/,/^\s*$/ s/^\(\s*\)\([^#]\)/#\1\2/' "$MOONRAKER_CFG"
else
echo -e "Info: Moonraker Timelapse configurations are already disabled in moonraker.conf file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Moonraker Timelapse has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

View file

@ -0,0 +1,85 @@
#!/bin/sh
set -e
function nozzle_cleaning_fan_control_message(){
top_line
title 'Nozzle Cleaning Fan Control' "${yellow}"
inner_line
hr
echo -e "${cyan}This is an Klipper extension to control fans during nozzle ${white}"
echo -e "${cyan}cleaning. ${white}"
hr
bottom_line
}
function install_nozzle_cleaning_fan_control(){
nozzle_cleaning_fan_control_message
local yn
while true; do
install_msg "Nozzle Cleaning Fan Control" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -d "NOZZLE_CLEANING_FOLDER" ]; then
rm -rf "NOZZLE_CLEANING_FOLDER"
fi
mkdir -p "$NOZZLE_CLEANING_FOLDER"
echo -e "Info: Linking files..."
ln -sf "$NOZZLE_CLEANING_URL1" "$NOZZLE_CLEANING_FOLDER"/__init__.py
ln -sf "$NOZZLE_CLEANING_URL2" "$NOZZLE_CLEANING_FOLDER"/prtouch_v2_fan.pyc
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
ln -sf "$NOZZLE_CLEANING_URL3" "$HS_CONFIG_FOLDER"/nozzle-cleaning-fan-control.cfg
if grep -q "include Helper-Script/nozzle-cleaning-fan-control" "$PRINTER_CFG" ; then
echo -e "Info: Nozzle Cleaning Fan Control configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Nozzle Cleaning Fan Control configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/nozzle-cleaning-fan-control\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Nozzle Cleaning Fan Control has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_nozzle_cleaning_fan_control(){
nozzle_cleaning_fan_control_message
local yn
while true; do
remove_msg "Nozzle Cleaning Fan Control" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing files..."
rm -rf "$NOZZLE_CLEANING_FOLDER"
rm -f "$HS_CONFIG_FOLDER"/nozzle-cleaning-fan-control.cfg
if grep -q "include Helper-Script/nozzle-cleaning-fan-control" "$PRINTER_CFG" ; then
echo -e "Info: Removing Nozzle Cleaning Fan Control configurations in printer.cfg file..."
sed -i '/include Helper-Script\/nozzle-cleaning-fan-control\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Nozzle Cleaning Fan Control configurations are already removed in printer.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Nozzle Cleaning Fan Control has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

66
scripts/octoeverywhere.sh Executable file
View file

@ -0,0 +1,66 @@
#!/bin/sh
set -e
function octoeverywhere_message(){
top_line
title 'OctoEverywhere' "${yellow}"
inner_line
hr
echo -e "${cyan}Cloud empower your Klipper printers with free, private, and ${white}"
echo -e "${cyan}unlimited remote access to your full web control portal from ${white}"
echo -e "${cyan}anywhere! ${white}"
hr
bottom_line
}
function install_octoeverywhere(){
octoeverywhere_message
local yn
while true; do
install_msg "OctoEverywhere" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -d "$OCTOEVERYWHERE_FOLDER" ]; then
echo -e "Info: OctoEverywhere is already installed. Download skipped."
else
echo -e "Info: Downloading OctoEverywhere..."
git config --global http.sslVerify false
git clone "$OCTOEVERYWHERE_URL" "$OCTOEVERYWHERE_FOLDER"
fi
echo -e "Info: Running OctoEverywhere installer..."
cd "$OCTOEVERYWHERE_FOLDER"
sh ./install.sh
ok_msg "OctoEverywhere has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_octoeverywhere(){
octoeverywhere_message
local yn
while true; do
remove_msg "OctoEverywhere" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Running OctoEverywhere installer..."
cd "$OCTOEVERYWHERE_FOLDER"
sh ./uninstall.sh
ok_msg "OctoEverywhere has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

173
scripts/paths.sh Executable file
View file

@ -0,0 +1,173 @@
#!/bin/sh
set -e
function set_paths() {
# Colors #
white=`echo -en "\033[m"`
blue=`echo -en "\033[36m"`
cyan=`echo -en "\033[1;36m"`
yellow=`echo -en "\033[1;33m"`
green=`echo -en "\033[01;32m"`
darkred=`echo -en "\033[31m"`
red=`echo -en "\033[01;31m"`
# System #
CURL="${HELPER_SCRIPT_FOLDER}/files/fixes/curl"
INITD_FOLDER="/etc/init.d"
USR_DATA="/usr/data"
PRINTER_DATA_FOLDER="$USR_DATA/printer_data"
# Helper Script #
HS_FILES="${HELPER_SCRIPT_FOLDER}/files"
HS_CONFIG_FOLDER="$PRINTER_DATA_FOLDER/config/Helper-Script"
HS_BACKUP_FOLDER="$USR_DATA/helper-script-backup"
# Configuration Files #
MOONRAKER_CFG="${PRINTER_DATA_FOLDER}/config/moonraker.conf"
PRINTER_CFG="${PRINTER_DATA_FOLDER}/config/printer.cfg"
MACROS_CFG="${PRINTER_DATA_FOLDER}/config/gcode_macro.cfg"
# Moonraker #
MOONRAKER_FOLDER="${USR_DATA}/moonraker"
MOONRAKER_URL1="${HS_FILES}/moonraker/moonraker.tar.gz"
MOONRAKER_URL2="${HS_FILES}/moonraker/moonraker.conf"
MOONRAKER_URL3="${HS_FILES}/moonraker/moonraker.asvc"
MOONRAKER_SERVICE_URL="${HS_FILES}/services/S56moonraker_service"
# Nginx #
NGINX_FOLDER="${USR_DATA}/nginx"
NGINX_SERVICE_URL="${HS_FILES}/services/S50nginx"
# Supervisor Lite #
SUPERVISOR_FILE="/usr/bin/supervisorctl"
SUPERVISOR_URL="${HS_FILES}/fixes/supervisorctl"
# Host Controls Support #
SYSTEMCTL_FILE="/usr/bin/systemctl"
SYSTEMCTL_URL="${HS_FILES}/fixes/systemctl"
SUDO_FILE="/usr/bin/sudo"
SUDO_URL="${HS_FILES}/fixes/sudo"
# Klipper #
KLIPPER_EXTRAS_FOLDER="/usr/share/klipper/klippy/extras"
KLIPPER_CONFIG_FOLDER="${PRINTER_DATA_FOLDER}/config"
KLIPPER_KLIPPY_FOLDER="/usr/share/klipper/klippy"
KLIPPER_SERVICE_URL="${HS_FILES}/services/S55klipper_service"
KLIPPER_GCODE_URL="${HS_FILES}/fixes/gcode.py"
# Fluidd #
FLUIDD_FOLDER="${USR_DATA}/fluidd"
FLUIDD_URL="https://github.com/fluidd-core/fluidd/releases/latest/download/fluidd.zip"
# Mainsail #
MAINSAIL_FOLDER="${USR_DATA}/mainsail"
MAINSAIL_URL="https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
# Entware #
ENTWARE_FILE="/opt/bin/opkg"
ENTWARE_URL="${HS_FILES}/entware/generic.sh"
# Klipper Gcode Shell Command #
KLIPPER_SHELL_FILE="${KLIPPER_EXTRAS_FOLDER}/gcode_shell_command.py"
KLIPPER_SHELL_URL="${HS_FILES}/gcode-shell-command/gcode_shell_command.py"
# Klipper Adaptive Meshing & Purging #
KAMP_FOLDER="${HS_CONFIG_FOLDER}/KAMP"
KAMP_URL="${HS_FILES}/kamp"
# Buzzer Support #
BUZZER_FILE="${HS_CONFIG_FOLDER}/buzzer-support.cfg"
BUZZER_URL="${HS_FILES}/buzzer-support/buzzer-support.cfg"
# Nozzle Cleaning Fan Control #
NOZZLE_CLEANING_FOLDER="${KLIPPER_EXTRAS_FOLDER}/prtouch_v2_fan"
NOZZLE_CLEANING_URL1="${HS_FILES}/nozzle-cleaning-fan-control/__init__.py"
NOZZLE_CLEANING_URL2="${HS_FILES}/nozzle-cleaning-fan-control/prtouch_v2_fan.pyc"
NOZZLE_CLEANING_URL3="${HS_FILES}/nozzle-cleaning-fan-control/nozzle-cleaning-fan-control.cfg"
# Fans Control Macros #
FAN_CONTROLS_FILE="${HS_CONFIG_FOLDER}/fans-control.cfg"
FAN_CONTROLS_URL="${HS_FILES}/macros/fans-control.cfg"
# Improved Shapers Calibrations #
IMP_SHAPERS_FOLDER="${HS_CONFIG_FOLDER}/improved-shapers"
IMP_SHAPERS_URL="${HS_FILES}/improved-shapers/"
# Useful Macros #
USEFUL_MACROS_FILE="${HS_CONFIG_FOLDER}/useful-macros.cfg"
USEFUL_MACROS_URL="${HS_FILES}/macros/useful-macros.cfg"
# Save Z-Offset Macros #
SAVE_ZOFFSET_FILE="${HS_CONFIG_FOLDER}/save-zoffset.cfg"
SAVE_ZOFFSET_URL="${HS_FILES}/macros/save-zoffset.cfg"
# Screws Tilt Adjust Support #
SCREWS_ADJUST_FILE="${HS_CONFIG_FOLDER}/screws-tilt-adjust.cfg"
SCREWS_ADJUST_URL="${HS_FILES}/screws-tilt-adjust/screws_tilt_adjust.py"
SCREWS_ADJUST_K1_URL="${HS_FILES}/screws-tilt-adjust/screws-tilt-adjust-k1.cfg"
SCREWS_ADJUST_K1M_URL="${HS_FILES}/screws-tilt-adjust/screws-tilt-adjust-k1max.cfg"
# Virtual Pins Support #
VIRTUAL_PINS_FILE="${KLIPPER_EXTRAS_FOLDER}/virtual_pins.py"
VIRTUAL_PINS_URL="${HS_FILES}/klipper-virtual-pins/virtual_pins.py"
# M600 Support #
M600_SUPPORT_FILE="${HS_CONFIG_FOLDER}/M600-support.cfg"
M600_SUPPORT_URL="${HS_FILES}/macros/M600-support.cfg"
# Git Backup #
GIT_BACKUP_INSTALLER="${HS_FILES}/git-backup/git-backup.sh"
GIT_BACKUP_FILE="${HS_CONFIG_FOLDER}/git-backup.cfg"
GIT_BACKUP_URL="${HS_FILES}/git-backup/git-backup.cfg"
# Moonraker Timelapse #
TIMELAPSE_FILE="${USR_DATA}/moonraker/moonraker/moonraker/components/timelapse.py"
TIMELAPSE_URL1="${HS_FILES}/moonraker-timelapse/timelapse.py"
TIMELAPSE_URL2="${HS_FILES}/moonraker-timelapse/timelapse.cfg"
# Camera Settings Control #
CAMERA_SETTINGS_FILE="${HS_CONFIG_FOLDER}/camera-settings.cfg"
CAMERA_SETTINGS_URL="${HS_FILES}/camera-settings/camera-settings.cfg"
# OctoEverywhere #
OCTOEVERYWHERE_FOLDER="${USR_DATA}/octoeverywhere"
OCTOEVERYWHERE_URL="https://github.com/QuinnDamerell/OctoPrint-OctoEverywhere.git"
# Moonraker Obico #
MOONRAKER_OBICO_FOLDER="${USR_DATA}/moonraker-obico"
MOONRAKER_OBICO_URL="https://github.com/TheSpaghettiDetective/moonraker-obico.git"
# Mobileraker Companion #
MOBILERAKER_COMPANION_FOLDER="${USR_DATA}/mobileraker_companion"
MOBILERAKER_COMPANION_URL="https://github.com/Clon1998/mobileraker_companion.git"
# Custom Boot Display #
BOOT_DISPLAY_FOLDER="/etc/boot-display"
BOOT_DISPLAY_FILE="${BOOT_DISPLAY_FOLDER}/part0/pic_100.jpg"
BOOT_DISPLAY_K1_URL="${HS_FILES}/boot-display/k1_boot_display.tar.gz"
BOOT_DISPLAY_K1M_URL="${HS_FILES}/boot-display/k1max_boot_display.tar.gz"
BOOT_DISPLAY_STOCK_URL="${HS_FILES}/boot-display/stock_boot_display.tar.gz"
# Creality Web Interface #
CREALITY_WEB_FILE="/usr/bin/web-server"
# Guppy Screen #
GUPPY_SCREEN_FOLDER="${USR_DATA}/guppyscreen"
GUPPY_SCREEN_URL1="${HS_FILES}/guppy-screen/guppy_update.cfg"
GUPPY_SCREEN_URL2="${HS_FILES}/guppy-screen/guppy-update.sh"
# Creality Dynamic Logos for Fluidd #
FLUIDD_LOGO_FILE="${USR_DATA}/fluidd/logo_creality_v2.svg"
FLUIDD_LOGO_URL1="${HS_FILES}/fluidd-logos/logo_creality_v1.svg"
FLUIDD_LOGO_URL2="${HS_FILES}/fluidd-logos/logo_creality_v2.svg"
FLUIDD_LOGO_URL3="${HS_FILES}/fluidd-logos/config.json"
}
function set_permissions() {
chmod +x "$CURL" >/dev/null 2>&1 &
}

81
scripts/save_zoffset_macros.sh Executable file
View file

@ -0,0 +1,81 @@
#!/bin/sh
set -e
function save_zoffset_macros_message(){
top_line
title 'Save Z-Offset Macros' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to save and load the the Z-Offset automatically. ${white}"
hr
bottom_line
}
function install_save_zoffset_macros(){
save_zoffset_macros_message
local yn
while true; do
install_msg "Save Z-Offset Macros" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/save-zoffset.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/save-zoffset.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Linking file..."
ln -sf "$SAVE_ZOFFSET_URL" "$HS_CONFIG_FOLDER"/save-zoffset.cfg
if grep -q "include Helper-Script/save-zoffset" "$PRINTER_CFG" ; then
echo -e "Info: Save Z-Offset Macros configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Save Z-Offset Macros configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/save-zoffset\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Save Z-Offset Macros have been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_save_zoffset_macros(){
save_zoffset_macros_message
local yn
while true; do
remove_msg "Save Z-Offset Macros" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Removing file..."
rm -f "$HS_CONFIG_FOLDER"/save-zoffset.cfg
rm -f "$HS_CONFIG_FOLDER"/variables.cfg
if grep -q "include Helper-Script/save-zoffset" "$PRINTER_CFG" ; then
echo -e "Info: Removing Save Z-Offset Macros configurations in printer.cfg file..."
sed -i '/include Helper-Script\/save-zoffset\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Save Z-Offset Macros configurations are already removed in printer.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Save Z-Offset Macros have been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

116
scripts/screws_tilt_adjust.sh Executable file
View file

@ -0,0 +1,116 @@
#!/bin/sh
set -e
function screws_tilt_adjust_message(){
top_line
title 'Screws Tilt Adjust Support' "${yellow}"
inner_line
hr
echo -e "${cyan}It allows to add support for Screws Tilt Adjust ${white}"
echo -e "${cyan}functionality. ${white}"
hr
bottom_line
}
function install_screws_tilt_adjust(){
screws_tilt_adjust_message
local yn
while true; do
install_msg "Screws Tilt Adjust Support" yn
case "${yn}" in
Y|y)
echo -e "${white}"
if [ -f "$HS_CONFIG_FOLDER"/screws-tilt-adjust.cfg ]; then
rm -f "$HS_CONFIG_FOLDER"/screws-tilt-adjust.cfg
fi
if [ ! -d "$HS_CONFIG_FOLDER" ]; then
mkdir -p "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Backing up original file..."
if [ ! -d "$HS_BACKUP_FOLDER"/screws-tilt-adjust ]; then
mkdir -p "$HS_BACKUP_FOLDER"/screws-tilt-adjust
fi
if [ -f "$KLIPPER_EXTRAS_FOLDER"/screws_tilt_adjust.py ]; then
mv "$KLIPPER_EXTRAS_FOLDER"/screws_tilt_adjust.py "$HS_BACKUP_FOLDER"/screws-tilt-adjust
mv "$KLIPPER_EXTRAS_FOLDER"/screws_tilt_adjust.pyc "$HS_BACKUP_FOLDER"/screws-tilt-adjust
fi
echo
local printer_choice
while true; do
read -p " ${white}Do you want install it for ${yellow}K1${white} or ${yellow}K1 Max${white}? (${yellow}k1${white}/${yellow}k1max${white}): ${yellow}" printer_choice
case "${printer_choice}" in
K1|k1)
echo -e "${white}"
echo -e "Info: Linking files..."
ln -sf "$SCREWS_ADJUST_K1_URL" "$HS_CONFIG_FOLDER"/screws-tilt-adjust.cfg
break;;
K1MAX|k1max)
echo -e "${white}"
echo -e "Info: Linking files..."
ln -sf "$SCREWS_ADJUST_K1M_URL" "$HS_CONFIG_FOLDER"/screws-tilt-adjust.cfg
break;;
*)
error_msg "Please select a correct choice!";;
esac
done
ln -sf "$SCREWS_ADJUST_URL" "$KLIPPER_EXTRAS_FOLDER"/screws_tilt_adjust.py
if grep -q "include Helper-Script/screws-tilt-adjust" "$PRINTER_CFG" ; then
echo -e "Info: Screws Tilt Adjust Support configurations are already enabled in printer.cfg file..."
else
echo -e "Info: Adding Screws Tilt Adjust Support configurations in printer.cfg file..."
sed -i '/\[include printer_params\.cfg\]/a \[include Helper-Script/screws-tilt-adjust\.cfg\]' "$PRINTER_CFG"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Screws Tilt Adjust Support has been installed successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function remove_screws_tilt_adjust(){
screws_tilt_adjust_message
local yn
while true; do
remove_msg "Screws Tilt Adjust Support" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoring files..."
if [ -f "$HS_BACKUP_FOLDER"/screws-tilt-adjust/screws_tilt_adjust.py ]; then
mv "$HS_BACKUP_FOLDER"/screws-tilt-adjust/screws_tilt_adjust.py "$KLIPPER_EXTRAS_FOLDER"
mv "$HS_BACKUP_FOLDER"/screws-tilt-adjust/screws_tilt_adjust.pyc "$KLIPPER_EXTRAS_FOLDER"
rm -rf "$HS_BACKUP_FOLDER"/screws-tilt-adjust
fi
if [ ! -n "$(ls -A "$HS_BACKUP_FOLDER")" ]; then
rm -rf "$HS_BACKUP_FOLDER"
fi
echo -e "Info: Removing file..."
rm -f "$HS_CONFIG_FOLDER"/screws-tilt-adjust.cfg
if grep -q "include Helper-Script/screws-tilt-adjust" "$PRINTER_CFG" ; then
echo -e "Info: Removing Screws Tilt Adjust Support configurations in printer.cfg file..."
sed -i '/include Helper-Script\/screws-tilt-adjust\.cfg/d' "$PRINTER_CFG"
else
echo -e "Info: Screws Tilt Adjust Support configurations are already removed in printer.cfg file..."
fi
if [ ! -n "$(ls -A "$HS_CONFIG_FOLDER")" ]; then
rm -rf "$HS_CONFIG_FOLDER"
fi
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Screws Tilt Adjust Support has been removed successfully!"
return;;
N|n)
error_msg "Deletion canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

330
scripts/tools.sh Executable file
View file

@ -0,0 +1,330 @@
#!/bin/sh
set -e
function prevent_updating_klipper_files_message(){
top_line
title 'Prevent updating Klipper configuration files' "${yellow}"
inner_line
hr
echo -e "${cyan}This prevents updating Klipper configuration files when ${white}"
echo -e "${cyan}Klipper restarts. ${white}"
hr
bottom_line
}
function allow_updating_klipper_files_message(){
top_line
title 'Allow updating Klipper configuration files' "${yellow}"
inner_line
hr
echo -e "${cyan}This allows updating Klipper configuration files when ${white}"
echo -e "${cyan}Klipper restarts. ${white}"
hr
bottom_line
}
function printing_gcode_from_folder_message(){
top_line
title 'Fix printing Gcode files from folder' "${yellow}"
inner_line
hr
echo -e "${cyan}From Fluidd or Mainsail it's possible to classify your Gcode ${white}"
echo -e "${cyan}files in folders but by default it's not possible to start ${white}"
echo -e "${cyan}a print from a folder. This fix allows that. ${white}"
hr
bottom_line
}
function restore_previous_firmware_message(){
top_line
title 'Restore a previous firmware' "${yellow}"
inner_line
hr
echo -e "${cyan}To restore a previous firmware, follow these steps and ${white}"
echo -e "${cyan}validate your choice: ${white}"
echo -e " │ │"
echo -e "${cyan}1. ${white}Copy the firmware (.img) you want to update to the root ${white}"
echo -e " │ of a USB drive. ${white}"
echo -e "${cyan}2. ${white}Make sure there is only this file on the USB drive. ${white}"
echo -e "${cyan}3. ${white}Insert the USB drive into the printer. ${white}"
hr
bottom_line
}
function reset_factory_settings_message(){
top_line
title 'Reset factory settings' "${yellow}"
inner_line
hr
echo -e "${cyan}This the best way to reset the printer to its factory ${white}"
echo -e "${cyan}settings. ${white}"
echo -e "${cyan}Note that the Factory Reset function in the screen menu ${white}"
echo -e "${cyan}settings only performs a partial reset. ${white}"
hr
echo -e "${cyan}Note: After factory reset all features already been ${white}"
echo -e "${cyan}installed with Creality Helper Script must be reinstalled ${white}"
echo -e "${cyan}and it's necessary to reconnect your printer to your network ${white}"
echo -e "${cyan} from screen settings in `Settings` → `Network` tab. ${white}"
hr
bottom_line
}
function prevent_updating_klipper_files(){
prevent_updating_klipper_files_message
local yn
while true; do
read -p "${white} Do you want to prevent updating ${green}Klipper configuration files ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Backup file..."
mv "$INITD_FOLDER"/S55klipper_service "$INITD_FOLDER"/disabled.S55klipper_service
echo -e "Info: Copying file..."
cp "$KLIPPER_SERVICE_URL" "$INITD_FOLDER"/S55klipper_service
echo -e "Info: Applying permissions..."
chmod 755 "$INITD_FOLDER"/S55klipper_service
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Klipper configuration files will no longer be updated when Klipper restarts!"
return;;
N|n)
error_msg "Preventing canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function allow_updating_klipper_files(){
allow_updating_klipper_files_message
local yn
while true; do
read -p "${white} Do you want to allow updating ${green}Klipper configuration files ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoring file..."
rm -f /etc/init.d/S55klipper_service
mv "$INITD_FOLDER"/disabled.S55klipper_service "$INITD_FOLDER"/S55klipper_service
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Klipper configuration files will be updated when Klipper restarts!"
return;;
N|n)
error_msg "Authorization canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function printing_gcode_from_folder(){
printing_gcode_from_folder_message
local yn
while true; do
read -p "${white} Do you want to apply fix for ${green}printing Gcode files from folder ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Deleting files..."
if [ -f "$KLIPPER_KLIPPY_FOLDER"/gcode.py ]; then
rm -f "$KLIPPER_KLIPPY_FOLDER"/gcode.py
rm -f "$KLIPPER_KLIPPY_FOLDER"/gcode.pyc
fi
echo -e "Info: Linking files..."
ln -sf "$KLIPPER_GCODE_URL" "$KLIPPER_KLIPPY_FOLDER"/gcode.py
echo -e "Info: Restarting Klipper service..."
restart_klipper
ok_msg "Fix has been applied successfully!"
return;;
N|n)
error_msg "Installation canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restart_nginx_action(){
echo
local yn
while true; do
restart_msg "Nginx service" yn
case "${yn}" in
Y|y)
echo -e "${white}"
stop_nginx
start_nginx
ok_msg "Nginx service has been restarted successfully!"
return;;
N|n)
error_msg "Restart canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restart_moonraker_action(){
echo
local yn
while true; do
restart_msg "Moonraker service" yn
case "${yn}" in
Y|y)
echo -e "${white}"
stop_moonraker
start_moonraker
ok_msg "Moonraker service has been restarted successfully!"
return;;
N|n)
error_msg "Restart canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restart_klipper_action(){
echo
local yn
while true; do
restart_msg "Klipper service" yn
case "${yn}" in
Y|y)
echo -e "${white}"
restart_klipper
ok_msg "Klipper service has been restarted successfully!"
return;;
N|n)
error_msg "Restart canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function update_entware_packages(){
echo
local yn
while true; do
read -p "${white} Are you sure you want to update ${green}Entware packages ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Updating packages list..."
"$ENTWARE_FILE" update
echo -e "Info: Updating packages..."
"$ENTWARE_FILE" upgrade
ok_msg "Entware packages have been updated!"
return;;
N|n)
error_msg "Updating canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function clear_cache(){
echo
local yn
while true; do
read -p "${white} Are you sure you want to ${green}clear cache ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Clearing root partition cache..."
rm -rf /root/.cache
echo -e "Info: Clearing git cache..."
cd "${HELPER_SCRIPT_FOLDER}"
git gc --aggressive --prune=all
ok_msg "Cache has been cleared!"
return;;
N|n)
error_msg "Clearing cache canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function clear_logs(){
echo
local yn
while true; do
read -p "${white} Are you sure you want to clear ${green}logs files ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Clearing logs files..."
rm -f "$USR_DATA"/creality/userdata/log/*.log
rm -f "$USR_DATA"/creality/userdata/log/*.gz
rm -f "$USR_DATA"/creality/userdata/fault_code/*
rm -f "$PRINTER_DATA_FOLDER"/logs/*
ok_msg "Logs files have been cleared!"
return;;
N|n)
error_msg "Clearing logs files canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function restore_previous_firmware(){
restore_previous_firmware_message
local yn
while true; do
read -p "${white} Do you want to restore a previous firmware ? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
if ls /tmp/udisk/sda1/*.img 1> /dev/null 2>&1; then
echo -e "${white}"
echo "Info: Restoring firmware..."
rm -rf /overlay/upper/*
/etc/ota_bin/local_ota_update.sh /tmp/udisk/sda1/*.img
ok_msg "Firmware has been restored! Please reboot your printer."
exit 0
else
error_msg "No .img file found on the USB drive. Restoration canceled!"
fi
return;;
N|n)
error_msg "Restoration canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}
function reset_factory_settings(){
reset_factory_settings_message
local yn
while true; do
read -p "${white} Are you sure you want to ${green}reset factory settings ${white}? (${yellow}y${white}/${yellow}n${white}): ${yellow}" yn
case "${yn}" in
Y|y)
echo -e "${white}"
echo -e "Info: Restoration..."
echo "all" | nc -U /var/run/wipe.sock
;;
N|n)
error_msg "Reset canceled!"
return;;
*)
error_msg "Please select a correct choice!";;
esac
done
}

Some files were not shown because too many files have changed in this diff Show more