First commit
This commit is contained in:
commit
7693c29676
102 changed files with 11831 additions and 0 deletions
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
ko_fi: guilouz
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.DS_Store
|
||||
desktop.ini
|
674
LICENSE
Normal file
674
LICENSE
Normal 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
9
README.md
Normal 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 />
|
BIN
files/boot-display/k1_boot_display.tar.gz
Normal file
BIN
files/boot-display/k1_boot_display.tar.gz
Normal file
Binary file not shown.
BIN
files/boot-display/k1max_boot_display.tar.gz
Normal file
BIN
files/boot-display/k1max_boot_display.tar.gz
Normal file
Binary file not shown.
BIN
files/boot-display/stock_boot_display.tar.gz
Normal file
BIN
files/boot-display/stock_boot_display.tar.gz
Normal file
Binary file not shown.
BIN
files/buzzer-support/beep.mp3
Normal file
BIN
files/buzzer-support/beep.mp3
Normal file
Binary file not shown.
14
files/buzzer-support/buzzer-support.cfg
Normal file
14
files/buzzer-support/buzzer-support.cfg
Normal 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
|
115
files/camera-settings/camera-settings.cfg
Normal file
115
files/camera-settings/camera-settings.cfg
Normal 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
58
files/entware/generic.sh
Executable 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
BIN
files/fixes/curl
Executable file
Binary file not shown.
549
files/fixes/gcode.py
Normal file
549
files/fixes/gcode.py
Normal 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
2
files/fixes/sudo
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
exec $*
|
131
files/fixes/supervisorctl
Executable file
131
files/fixes/supervisorctl
Executable 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
7
files/fixes/systemctl
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [ "$1" == "reboot" ]; then
|
||||
/sbin/reboot
|
||||
elif [ "$1" == "poweroff" ]; then
|
||||
/sbin/poweroff
|
||||
fi
|
167
files/fluidd-logos/config.json
Normal file
167
files/fluidd-logos/config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
6
files/fluidd-logos/logo_creality_v1.svg
Normal file
6
files/fluidd-logos/logo_creality_v1.svg
Normal 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 |
122
files/fluidd-logos/logo_creality_v2.svg
Normal file
122
files/fluidd-logos/logo_creality_v2.svg
Normal 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 |
87
files/gcode-shell-command/gcode_shell_command.py
Normal file
87
files/gcode-shell-command/gcode_shell_command.py
Normal 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
25
files/git-backup/S52Git-Backup
Executable 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
|
35
files/git-backup/git-backup.cfg
Normal file
35
files/git-backup/git-backup.cfg
Normal 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
310
files/git-backup/git-backup.sh
Executable 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
|
44
files/guppy-screen/guppy-update.sh
Normal file
44
files/guppy-screen/guppy-update.sh
Normal 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
|
37
files/guppy-screen/guppy_update.cfg
Normal file
37
files/guppy-screen/guppy_update.cfg
Normal 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
|
39
files/improved-shapers/calibrate_shaper_config.py
Executable file
39
files/improved-shapers/calibrate_shaper_config.py
Executable 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)
|
BIN
files/improved-shapers/ft2font.cpython-38-mipsel-linux-gnu.so
Executable file
BIN
files/improved-shapers/ft2font.cpython-38-mipsel-linux-gnu.so
Executable file
Binary file not shown.
118
files/improved-shapers/improved-shapers.cfg
Normal file
118
files/improved-shapers/improved-shapers.cfg
Normal 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
|
186
files/improved-shapers/scripts/calibrate_shaper.py
Executable file
186
files/improved-shapers/scripts/calibrate_shaper.py
Executable 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()
|
573
files/improved-shapers/scripts/graph_belts.py
Executable file
573
files/improved-shapers/scripts/graph_belts.py
Executable 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()
|
372
files/improved-shapers/scripts/shaper_calibrate.py
Executable file
372
files/improved-shapers/scripts/shaper_calibrate.py
Executable 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))
|
102
files/improved-shapers/scripts/shaper_defs.py
Executable file
102
files/improved-shapers/scripts/shaper_defs.py
Executable 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.),
|
||||
]
|
93
files/kamp/Adaptive_Meshing.cfg
Normal file
93
files/kamp/Adaptive_Meshing.cfg
Normal 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}
|
40
files/kamp/KAMP_Settings.cfg
Normal file
40
files/kamp/KAMP_Settings.cfg
Normal 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
200
files/kamp/Line_Purge.cfg
Normal 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 %}
|
32
files/kamp/Prusa_Slicer.cfg
Normal file
32
files/kamp/Prusa_Slicer.cfg
Normal 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
79
files/kamp/Smart_Park.cfg
Normal 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
|
62
files/kamp/Start_Print.cfg
Normal file
62
files/kamp/Start_Print.cfg
Normal 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}
|
246
files/klipper-virtual-pins/virtual_pins.py
Normal file
246
files/klipper-virtual-pins/virtual_pins.py
Normal 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)
|
133
files/macros/M600-support.cfg
Normal file
133
files/macros/M600-support.cfg
Normal 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}
|
142
files/macros/fans-control.cfg
Normal file
142
files/macros/fans-control.cfg
Normal 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}
|
38
files/macros/save-zoffset.cfg
Normal file
38
files/macros/save-zoffset.cfg
Normal 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 %}
|
||||
|
205
files/macros/useful-macros.cfg
Normal file
205
files/macros/useful-macros.cfg
Normal 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
|
427
files/moonraker-timelapse/timelapse.cfg
Normal file
427
files/moonraker-timelapse/timelapse.cfg
Normal 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 %}
|
842
files/moonraker-timelapse/timelapse.py
Normal file
842
files/moonraker-timelapse/timelapse.py
Normal 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)
|
13
files/moonraker/moonraker.asvc
Normal file
13
files/moonraker/moonraker.asvc
Normal file
|
@ -0,0 +1,13 @@
|
|||
klipper_mcu
|
||||
webcamd
|
||||
MoonCord
|
||||
KlipperScreen
|
||||
moonraker-telegram-bot
|
||||
moonraker-obico
|
||||
sonar
|
||||
crowsnest
|
||||
octoeverywhere
|
||||
ratos-configurator
|
||||
mobileraker
|
||||
guppyscreen
|
||||
Git-Backup
|
103
files/moonraker/moonraker.conf
Normal file
103
files/moonraker/moonraker.conf
Normal 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
|
BIN
files/moonraker/moonraker.tar.gz
Normal file
BIN
files/moonraker/moonraker.tar.gz
Normal file
Binary file not shown.
5
files/nozzle-cleaning-fan-control/__init__.py
Normal file
5
files/nozzle-cleaning-fan-control/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
from .prtouch_v2_fan import PRTouchFan
|
||||
|
||||
def load_config(config):
|
||||
return PRTouchFan(config)
|
|
@ -0,0 +1,6 @@
|
|||
########################################
|
||||
# Nozzle Cleaning Fan Control
|
||||
########################################
|
||||
|
||||
[prtouch_v2_fan]
|
||||
max_speed: 0.5
|
BIN
files/nozzle-cleaning-fan-control/prtouch_v2_fan.pyc
Normal file
BIN
files/nozzle-cleaning-fan-control/prtouch_v2_fan.pyc
Normal file
Binary file not shown.
25
files/screws-tilt-adjust/screws-tilt-adjust-k1.cfg
Normal file
25
files/screws-tilt-adjust/screws-tilt-adjust-k1.cfg
Normal 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
|
25
files/screws-tilt-adjust/screws-tilt-adjust-k1max.cfg
Normal file
25
files/screws-tilt-adjust/screws-tilt-adjust-k1max.cfg
Normal 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
|
131
files/screws-tilt-adjust/screws_tilt_adjust.py
Normal file
131
files/screws-tilt-adjust/screws_tilt_adjust.py
Normal 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
72
files/scripts/useful_macros.sh
Executable 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
32
files/services/S50nginx
Executable 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
|
54
files/services/S55klipper_service
Executable file
54
files/services/S55klipper_service
Executable 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 $?
|
51
files/services/S56moonraker_service
Executable file
51
files/services/S56moonraker_service
Executable 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
69
helper.sh
Executable 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
|
77
scripts/backup_klipper_config.sh
Executable file
77
scripts/backup_klipper_config.sh
Executable 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
|
||||
}
|
77
scripts/backup_moonraker_database.sh
Executable file
77
scripts/backup_moonraker_database.sh
Executable 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
81
scripts/buzzer_support.sh
Executable 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
|
||||
}
|
81
scripts/camera_settings_control.sh
Executable file
81
scripts/camera_settings_control.sh
Executable 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
|
||||
}
|
39
scripts/creality_dynamic_logos.sh
Executable file
39
scripts/creality_dynamic_logos.sh
Executable 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
138
scripts/creality_web_interface.sh
Executable 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
91
scripts/custom_boot_display.sh
Executable 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
72
scripts/entware.sh
Executable 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
129
scripts/fans_control_macros.sh
Executable 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
90
scripts/fluidd.sh
Executable 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
62
scripts/gcode_shell_command.sh
Executable 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
91
scripts/git_backup.sh
Executable 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
250
scripts/guppy_screen.sh
Executable 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
140
scripts/improved_shapers.sh
Executable 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
126
scripts/kamp.sh
Executable 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
117
scripts/m600_support.sh
Executable 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
91
scripts/mainsail.sh
Executable 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
|
||||
}
|
87
scripts/menu/KE/customize_menu_KE.sh
Executable file
87
scripts/menu/KE/customize_menu_KE.sh
Executable 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
81
scripts/menu/KE/info_menu_KE.sh
Executable 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
|
||||
}
|
162
scripts/menu/KE/install_menu_KE.sh
Executable file
162
scripts/menu/KE/install_menu_KE.sh
Executable 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
158
scripts/menu/KE/remove_menu_KE.sh
Executable 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
|
||||
}
|
95
scripts/menu/KE/tools_menu_KE.sh
Executable file
95
scripts/menu/KE/tools_menu_KE.sh
Executable 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
|
||||
}
|
57
scripts/menu/backup_restore_menu.sh
Executable file
57
scripts/menu/backup_restore_menu.sh
Executable 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
106
scripts/menu/customize_menu.sh
Executable 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
197
scripts/menu/functions.sh
Executable 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
90
scripts/menu/info_menu.sh
Executable 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
228
scripts/menu/install_menu.sh
Executable 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
121
scripts/menu/main_menu.sh
Executable 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
220
scripts/menu/remove_menu.sh
Executable 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
80
scripts/menu/system_menu.sh
Executable 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
100
scripts/menu/tools_menu.sh
Executable 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
|
||||
}
|
83
scripts/mobileraker_companion.sh
Executable file
83
scripts/mobileraker_companion.sh
Executable 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
107
scripts/moonraker_nginx.sh
Executable 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
90
scripts/moonraker_obico.sh
Executable 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
101
scripts/moonraker_timelapse.sh
Executable 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
|
||||
}
|
85
scripts/nozzle_cleaning_fan_control.sh
Executable file
85
scripts/nozzle_cleaning_fan_control.sh
Executable 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
66
scripts/octoeverywhere.sh
Executable 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
173
scripts/paths.sh
Executable 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
81
scripts/save_zoffset_macros.sh
Executable 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
116
scripts/screws_tilt_adjust.sh
Executable 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
330
scripts/tools.sh
Executable 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
Loading…
Add table
Reference in a new issue