Merge pull request #216 from quen0n/dev

New plugin - Unitemp
This commit is contained in:
MX 2022-12-19 20:29:34 +03:00 committed by GitHub
commit 4a4c041fd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 6244 additions and 0 deletions

View file

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

View file

@ -0,0 +1,10 @@
![Flipper usage](https://user-images.githubusercontent.com/10090793/206618263-c1e212e4-58dc-432e-87a8-5c19fd835b35.png)
# Unitemp - Universal temperature sensor reader
[![GitHub release](https://img.shields.io/github/release/quen0n/unitemp-flipperzero?include_prereleases=&sort=semver&color=blue)](https://github.com/quen0n/unitemp-flipperzero/releases/)
[![GitHub all releases](https://img.shields.io/github/downloads/quen0n/unitemp-flipperzero/total)]()
[![GitHub](https://img.shields.io/github/license/quen0n/unitemp-flipperzero)](https://github.com/quen0n/unitemp-flipperzero/blob/dev/LICENSE.md)
[Flipper Zero](https://flipperzero.one/) application for reading temperature, pressure and pressure sensors using Onewire, Singlewire, I2C protocols.
## List of supported sensors (supplemented)
![image](https://user-images.githubusercontent.com/10090793/208480561-e98a6192-d44d-4ad9-8692-a91ccaae47c7.png)
## Installation
Copy the contents of the repository to the `applications/plugins/unitemp` folder and build the project. Flash FZ along with resources. [More...](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md)

View file

@ -0,0 +1,645 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "Sensors.h"
#include <furi_hal_power.h>
#include <m-string.h>
//Порты ввода/вывода, которые не были обозначены в общем списке
const GpioPin SWC_10 = {.pin = LL_GPIO_PIN_14, .port = GPIOA};
const GpioPin SIO_12 = {.pin = LL_GPIO_PIN_13, .port = GPIOA};
const GpioPin TX_13 = {.pin = LL_GPIO_PIN_6, .port = GPIOB};
const GpioPin RX_14 = {.pin = LL_GPIO_PIN_7, .port = GPIOB};
//Количество доступных портов ввода/вывода
#define GPIO_ITEMS (sizeof(GPIOList) / sizeof(GPIO))
//Количество интерфейсов
#define INTERFACES_TYPES_COUNT (int)(sizeof(interfaces) / sizeof(const Interface*))
//Количество типов датчиков
#define SENSOR_TYPES_COUNT (int)(sizeof(sensorTypes) / sizeof(const SensorType*))
//Перечень достуных портов ввода/вывода
static const GPIO GPIOList[] = {
{2, "2 (A7)", &gpio_ext_pa7},
{3, "3 (A6)", &gpio_ext_pa6},
{4, "4 (A4)", &gpio_ext_pa4},
{5, "5 (B3)", &gpio_ext_pb3},
{6, "6 (B2)", &gpio_ext_pb2},
{7, "7 (C3)", &gpio_ext_pc3},
{10, " 10(SWC) ", &SWC_10},
{12, "12 (SIO)", &SIO_12},
{13, "13 (TX)", &TX_13},
{14, "14 (RX)", &RX_14},
{15, "15 (C1)", &gpio_ext_pc1},
{16, "16 (C0)", &gpio_ext_pc0},
{17, "17 (1W)", &ibutton_gpio}};
//Список интерфейсов, которые прикреплены к GPIO (определяется индексом)
//NULL - порт свободен, указатель на интерфейс - порт занят этим интерфейсом
static const Interface* gpio_interfaces_list[GPIO_ITEMS] = {0};
const Interface SINGLE_WIRE = {
.name = "Single wire",
.allocator = unitemp_singlewire_alloc,
.mem_releaser = unitemp_singlewire_free,
.updater = unitemp_singlewire_update};
const Interface I2C = {
.name = "I2C",
.allocator = unitemp_I2C_sensor_alloc,
.mem_releaser = unitemp_I2C_sensor_free,
.updater = unitemp_I2C_sensor_update};
const Interface ONE_WIRE = {
.name = "One wire",
.allocator = unitemp_onewire_sensor_alloc,
.mem_releaser = unitemp_onewire_sensor_free,
.updater = unitemp_onewire_sensor_update};
//Перечень интерфейсов подключения
//static const Interface* interfaces[] = {&SINGLE_WIRE, &I2C, &ONE_WIRE};
//Перечень датчиков
static const SensorType* sensorTypes[] =
{&DHT11, &DHT12_SW, &DHT21, &DHT22, &AM2320_SW, &AM2320_I2C, &LM75, &BMP280, &BME280, &Dallas};
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) {
if(index > SENSOR_TYPES_COUNT) return NULL;
return sensorTypes[index];
}
const SensorType* unitemp_sensors_getTypeFromStr(char* str) {
UNUSED(str);
if(str == NULL) return NULL;
for(uint8_t i = 0; i < unitemp_sensors_getTypesCount(); i++) {
if(!strcmp(str, sensorTypes[i]->typename)) {
return sensorTypes[i];
}
}
return NULL;
}
uint8_t unitemp_sensors_getTypesCount(void) {
return SENSOR_TYPES_COUNT;
}
const SensorType** unitemp_sensors_getTypes(void) {
return sensorTypes;
}
int unitemp_getIntFromType(const SensorType* type) {
for(int i = 0; i < SENSOR_TYPES_COUNT; i++) {
if(!strcmp(type->typename, sensorTypes[i]->typename)) {
return i;
}
}
return 255;
}
const GPIO* unitemp_gpio_getFromInt(uint8_t name) {
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(GPIOList[i].num == name) {
return &GPIOList[i];
}
}
return NULL;
}
const GPIO* unitemp_gpio_getFromIndex(uint8_t index) {
return &GPIOList[index];
}
uint8_t unitemp_gpio_toInt(const GPIO* gpio) {
if(gpio == NULL) return 255;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(GPIOList[i].pin->pin == gpio->pin->pin && GPIOList[i].pin->port == gpio->pin->port) {
return GPIOList[i].num;
}
}
return 255;
}
uint8_t unitemp_gpio_to_index(const GpioPin* gpio) {
if(gpio == NULL) return 255;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
if(GPIOList[i].pin->pin == gpio->pin && GPIOList[i].pin->port == gpio->port) {
return i;
}
}
return 255;
}
uint8_t unitemp_gpio_getAviablePortsCount(const Interface* interface, const GPIO* extraport) {
uint8_t aviable_ports_count = 0;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
//Проверка для one wire
if(interface == &ONE_WIRE) {
if(((gpio_interfaces_list[i] == NULL || gpio_interfaces_list[i] == &ONE_WIRE) &&
(i != 12)) || //Почему-то не работает на 17 порте
(unitemp_gpio_getFromIndex(i) == extraport)) {
aviable_ports_count++;
}
}
//Проверка для single wire
if(interface == &SINGLE_WIRE) {
if(gpio_interfaces_list[i] == NULL || (unitemp_gpio_getFromIndex(i) == extraport)) {
aviable_ports_count++;
}
}
if(interface == &I2C) {
//У I2C два фиксированых порта
return 0;
}
}
return aviable_ports_count;
}
void unitemp_gpio_lock(const GPIO* gpio, const Interface* interface) {
uint8_t i = unitemp_gpio_to_index(gpio->pin);
if(i == 255) return;
gpio_interfaces_list[i] = interface;
}
void unitemp_gpio_unlock(const GPIO* gpio) {
uint8_t i = unitemp_gpio_to_index(gpio->pin);
if(i == 255) return;
gpio_interfaces_list[i] = NULL;
}
const GPIO*
unitemp_gpio_getAviablePort(const Interface* interface, uint8_t index, const GPIO* extraport) {
//Проверка для I2C
if(interface == &I2C) {
if((gpio_interfaces_list[10] == NULL || gpio_interfaces_list[10] == &I2C) &&
(gpio_interfaces_list[11] == NULL || gpio_interfaces_list[11] == &I2C)) {
//Возврат истины
return unitemp_gpio_getFromIndex(0);
} else {
//Возврат лжи
return NULL;
}
}
uint8_t aviable_index = 0;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
//Проверка для one wire
if(interface == &ONE_WIRE) {
//Почему-то не работает на 17 порте
if(((gpio_interfaces_list[i] == NULL || gpio_interfaces_list[i] == &ONE_WIRE) &&
(i != 12)) || //Почему-то не работает на 17 порте
(unitemp_gpio_getFromIndex(i) == extraport)) {
if(aviable_index == index) {
return unitemp_gpio_getFromIndex(i);
} else {
aviable_index++;
}
}
}
//Проверка для single wire
if(interface == &SINGLE_WIRE) {
if(gpio_interfaces_list[i] == NULL || unitemp_gpio_getFromIndex(i) == extraport) {
if(aviable_index == index) {
return unitemp_gpio_getFromIndex(i);
} else {
aviable_index++;
}
}
}
}
return NULL;
}
void unitemp_sensor_delete(Sensor* sensor) {
for(uint8_t i = 0; i < app->sensors_count; i++) {
if(app->sensors[i] == sensor) {
app->sensors[i]->status = UT_SENSORSTATUS_INACTIVE;
unitemp_sensors_save();
unitemp_sensors_reload();
return;
}
}
}
Sensor* unitemp_sensor_getActive(uint8_t index) {
uint8_t aviable_index = 0;
for(uint8_t i = 0; i < app->sensors_count; i++) {
if(app->sensors[i]->status != UT_SENSORSTATUS_INACTIVE) {
if(aviable_index == index) {
return app->sensors[i];
} else {
aviable_index++;
}
}
}
return NULL;
}
uint8_t unitemp_sensors_getCount(void) {
if(app->sensors == NULL) return 0;
return app->sensors_count;
}
uint8_t unitemp_sensors_getActiveCount(void) {
if(app->sensors == NULL) return 0;
uint8_t counter = 0;
for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) {
if(app->sensors[i]->status != UT_SENSORSTATUS_INACTIVE) counter++;
}
return counter;
}
void unitemp_sensors_add(Sensor* sensor) {
app->sensors =
(Sensor**)realloc(app->sensors, (unitemp_sensors_getCount() + 1) * sizeof(Sensor*));
app->sensors[unitemp_sensors_getCount()] = sensor;
app->sensors_count++;
}
bool unitemp_sensors_load(void) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Loading sensors...");
#endif
//Выделение памяти на поток
app->file_stream = file_stream_alloc(app->storage);
//Переменная пути к файлу
FuriString* filepath = furi_string_alloc();
//Составление пути к файлу
furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SENSORS);
//Открытие потока к файлу с датчиками
if(!file_stream_open(
app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
if(file_stream_get_error(app->file_stream) == FSE_NOT_EXIST) {
FURI_LOG_W(APP_NAME, "Missing sensors file");
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
return false;
} else {
FURI_LOG_E(
APP_NAME,
"An error occurred while loading the sensors file: %d",
file_stream_get_error(app->file_stream));
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
return false;
}
}
//Вычисление размера файла
uint16_t file_size = stream_size(app->file_stream);
//Если файл пустой, то:
if(file_size == (uint8_t)0) {
FURI_LOG_W(APP_NAME, "Sensors file is empty");
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
return false;
}
//Выделение памяти под загрузку файла
uint8_t* file_buf = malloc(file_size);
//Опустошение буфера файла
memset(file_buf, 0, file_size);
//Загрузка файла
if(stream_read(app->file_stream, file_buf, file_size) != file_size) {
//Выход при ошибке чтения
FURI_LOG_E(APP_NAME, "Error reading sensors file");
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
free(file_buf);
return false;
}
//Указатель на начало строки
FuriString* file = furi_string_alloc_set_str((char*)file_buf);
//Сколько байт до конца строки
size_t line_end = 0;
while(line_end != STRING_FAILURE && line_end != (size_t)(file_size - 1)) {
//Имя датчика
char name[11] = {0};
//Тип датчика
char type[11] = {0};
//Смещение по температуре
int temp_offset = 0;
//Смещение по строке для отделения аргументов
int offset = 0;
//Чтение из строки
sscanf(((char*)(file_buf + line_end)), "%s %s %d %n", name, type, &temp_offset, &offset);
//Ограничение длины имени
name[10] = '\0';
//Замена ? на пробел
for(uint8_t i = 0; i < 10; i++) {
if(name[i] == '?') name[i] = ' ';
}
char* args = ((char*)(file_buf + line_end + offset));
const SensorType* stype = unitemp_sensors_getTypeFromStr(type);
//Проверка типа датчика
if(stype != NULL && sizeof(name) > 0 && sizeof(name) <= 11) {
Sensor* sensor =
unitemp_sensor_alloc(name, unitemp_sensors_getTypeFromStr(type), args);
if(sensor != NULL) {
sensor->temp_offset = temp_offset;
unitemp_sensors_add(sensor);
} else {
FURI_LOG_E(APP_NAME, "Failed sensor (%s:%s) mem allocation", name, type);
}
} else {
FURI_LOG_E(APP_NAME, "Unsupported sensor name (%s) or sensor type (%s)", name, type);
}
//Вычисление конца строки
line_end = furi_string_search_char(file, '\n', line_end + 1);
}
free(file_buf);
file_stream_close(app->file_stream);
stream_free(app->file_stream);
FURI_LOG_I(APP_NAME, "Sensors have been successfully loaded");
return true;
}
bool unitemp_sensors_save(void) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Saving sensors...");
#endif
//Выделение памяти для потока
app->file_stream = file_stream_alloc(app->storage);
//Переменная пути к файлу
FuriString* filepath = furi_string_alloc();
//Составление пути к файлу
furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SENSORS);
//Создание папки плагина
storage_common_mkdir(app->storage, APP_PATH_FOLDER);
//Открытие потока
if(!file_stream_open(
app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
FURI_LOG_E(
APP_NAME,
"An error occurred while saving the sensors file: %d",
file_stream_get_error(app->file_stream));
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
return false;
}
//Сохранение датчиков
for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) {
Sensor* sensor = unitemp_sensor_getActive(i);
//Замена пробела на ?
for(uint8_t i = 0; i < 10; i++) {
if(sensor->name[i] == ' ') sensor->name[i] = '?';
}
stream_write_format(
app->file_stream,
"%s %s %d ",
sensor->name,
sensor->type->typename,
sensor->temp_offset);
if(sensor->type->interface == &SINGLE_WIRE) {
stream_write_format(
app->file_stream, "%d\n", unitemp_singlewire_sensorGetGPIO(sensor)->num);
}
if(sensor->type->interface == &I2C) {
stream_write_format(
app->file_stream, "%X\n", ((I2CSensor*)sensor->instance)->currentI2CAdr);
}
if(sensor->type->interface == &ONE_WIRE) {
stream_write_format(
app->file_stream,
"%d %02X%02X%02X%02X%02X%02X%02X%02X\n",
((OneWireSensor*)sensor->instance)->bus->gpio->num,
((OneWireSensor*)sensor->instance)->deviceID[0],
((OneWireSensor*)sensor->instance)->deviceID[1],
((OneWireSensor*)sensor->instance)->deviceID[2],
((OneWireSensor*)sensor->instance)->deviceID[3],
((OneWireSensor*)sensor->instance)->deviceID[4],
((OneWireSensor*)sensor->instance)->deviceID[5],
((OneWireSensor*)sensor->instance)->deviceID[6],
((OneWireSensor*)sensor->instance)->deviceID[7]);
}
}
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
FURI_LOG_I(APP_NAME, "Sensors have been successfully saved");
return true;
}
void unitemp_sensors_reload(void) {
unitemp_sensors_deInit();
unitemp_sensors_free();
unitemp_sensors_load();
unitemp_sensors_init();
}
bool unitemp_sensor_isContains(Sensor* sensor) {
for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) {
if(app->sensors[i] == sensor) return true;
}
return false;
}
Sensor* unitemp_sensor_alloc(char* name, const SensorType* type, char* args) {
if(name == NULL || type == NULL) return NULL;
bool status = false;
//Выделение памяти под датчик
Sensor* sensor = malloc(sizeof(Sensor));
if(sensor == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s allocation error", name);
return false;
}
//Выделение памяти под имя
sensor->name = malloc(11);
if(sensor->name == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s name allocation error", name);
return false;
}
//Запись имени датчка
strcpy(sensor->name, name);
//Тип датчика
sensor->type = type;
//Статус датчика по умолчанию - ошибка
sensor->status = UT_SENSORSTATUS_ERROR;
//Время последнего опроса
sensor->lastPollingTime =
furi_get_tick() - 10000; //чтобы первый опрос произошёл как можно раньше
sensor->temp = -128.0f;
sensor->hum = -128.0f;
sensor->pressure = -128.0f;
sensor->temp_offset = 0;
//Выделение памяти под инстанс датчика в зависимости от его интерфейса
status = sensor->type->interface->allocator(sensor, args);
//Выход если датчик успешно развёрнут
if(status) {
FURI_LOG_I(APP_NAME, "Sensor %s allocated", name);
return sensor;
}
//Выход с очисткой если память для датчика не была выделена
free(sensor->name);
free(sensor);
FURI_LOG_E(APP_NAME, "Sensor %s(%s) allocation error", name, type->typename);
return NULL;
}
void unitemp_sensor_free(Sensor* sensor) {
if(sensor == NULL) {
FURI_LOG_E(APP_NAME, "Null pointer sensor releasing");
return;
}
if(sensor->type == NULL) {
FURI_LOG_E(APP_NAME, "Sensor type is null");
return;
}
if(sensor->type->mem_releaser == NULL) {
FURI_LOG_E(APP_NAME, "Sensor releaser is null");
return;
}
bool status = false;
//Высвобождение памяти под инстанс
status = sensor->type->interface->mem_releaser(sensor);
UNUSED(status);
#ifdef UNITEMP_DEBUG
if(status) {
FURI_LOG_D(APP_NAME, "Sensor %s memory successfully released", sensor->name);
} else {
FURI_LOG_E(APP_NAME, "Sensor %s memory is not released", sensor->name);
}
#endif
free(sensor->name);
//free(sensor);
}
void unitemp_sensors_free(void) {
for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) {
unitemp_sensor_free(app->sensors[i]);
}
app->sensors_count = 0;
}
bool unitemp_sensors_init(void) {
bool result = true;
//Перебор датчиков из списка
for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) {
//Включение 5V если на порту 1 FZ его нет
//Может пропасть при отключении USB
if(furi_hal_power_is_otg_enabled() != true) {
furi_hal_power_enable_otg();
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "OTG enabled");
#endif
}
if(!(*app->sensors[i]->type->initializer)(app->sensors[i])) {
FURI_LOG_E(
APP_NAME,
"An error occurred during sensor initialization %s",
app->sensors[i]->name);
result = false;
}
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Sensor %s successfully initialized", app->sensors[i]->name);
#endif
}
app->sensors_ready = true;
return result;
}
bool unitemp_sensors_deInit(void) {
bool result = true;
//Выключение 5 В если до этого оно не было включено
if(app->settings.lastOTGState != true) {
furi_hal_power_disable_otg();
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "OTG disabled");
#endif
}
//Перебор датчиков из списка
for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) {
if(!(*app->sensors[i]->type->deinitializer)(app->sensors[i])) {
FURI_LOG_E(
APP_NAME,
"An error occurred during sensor deinitialization %s",
app->sensors[i]->name);
result = false;
}
}
return result;
}
UnitempStatus unitemp_sensor_updateData(Sensor* sensor) {
if(sensor == NULL) return UT_SENSORSTATUS_ERROR;
//Проверка на допустимость опроса датчика
if(furi_get_tick() - sensor->lastPollingTime < sensor->type->pollingInterval) {
//Возврат ошибки если последний опрос датчика был неудачным
if(sensor->status == UT_SENSORSTATUS_TIMEOUT) {
return UT_SENSORSTATUS_TIMEOUT;
}
return UT_SENSORSTATUS_EARLYPOOL;
}
sensor->lastPollingTime = furi_get_tick();
if(!furi_hal_power_is_otg_enabled()) {
furi_hal_power_enable_otg();
}
sensor->status = sensor->type->interface->updater(sensor);
#ifdef UNITEMP_DEBUG
if(sensor->status != UT_SENSORSTATUS_OK && sensor->status != UT_SENSORSTATUS_POLLING)
FURI_LOG_D(APP_NAME, "Sensor %s update status %d", sensor->name, sensor->status);
#endif
if(app->settings.temp_unit == UT_TEMP_FAHRENHEIT && sensor->status == UT_SENSORSTATUS_OK)
uintemp_celsiumToFarengate(sensor);
if(sensor->status == UT_SENSORSTATUS_OK) {
sensor->temp += sensor->temp_offset / 10.f;
if(app->settings.pressure_unit == UT_PRESSURE_MM_HG) {
unitemp_pascalToMmHg(sensor);
} else if(app->settings.pressure_unit == UT_PRESSURE_IN_HG) {
unitemp_pascalToInHg(sensor);
} else if(app->settings.pressure_unit == UT_PRESSURE_KPA) {
unitemp_pascalToKPa(sensor);
}
}
return sensor->status;
}
void unitemp_sensors_updateValues(void) {
for(uint8_t i = 0; i < unitemp_sensors_getCount(); i++) {
unitemp_sensor_updateData(unitemp_sensor_getActive(i));
}
}

View file

@ -0,0 +1,324 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_SENSORS
#define UNITEMP_SENSORS
#include <furi.h>
#include <input/input.h>
//Маски бит для определения типов возвращаемых значений
#define UT_TEMPERATURE 0b00000001
#define UT_HUMIDITY 0b00000010
#define UT_PRESSURE 0b00000100
//Статусы опроса датчика
typedef enum {
UT_DATA_TYPE_TEMP = UT_TEMPERATURE,
UT_DATA_TYPE_TEMP_HUM = UT_TEMPERATURE | UT_HUMIDITY,
UT_DATA_TYPE_TEMP_PRESS = UT_TEMPERATURE | UT_PRESSURE,
UT_DATA_TYPE_TEMP_HUM_PRESS = UT_TEMPERATURE | UT_HUMIDITY | UT_PRESSURE,
} SensorDataType;
//Типы возвращаемых данных
typedef enum {
UT_SENSORSTATUS_OK, //Всё хорошо, опрос успешен
UT_SENSORSTATUS_TIMEOUT, //Датчик не отозвался
UT_SENSORSTATUS_EARLYPOOL, //Опрос раньше положенной задержки
UT_SENSORSTATUS_BADCRC, //Неверная контрольная сумма
UT_SENSORSTATUS_ERROR, //Прочие ошибки
UT_SENSORSTATUS_POLLING, //В датчике происходит преобразование
UT_SENSORSTATUS_INACTIVE, //Датчик на редактировании или удалён
} UnitempStatus;
//Порт ввода/вывода Flipper Zero
typedef struct GPIO {
const uint8_t num;
const char* name;
const GpioPin* pin;
} GPIO;
typedef struct Sensor Sensor;
/**
* @brief Указатель функции выделения памяти и подготовки экземпляра датчика
*/
typedef bool(SensorAllocator)(Sensor* sensor, char* args);
/**
* @brief Указатель на функцию высвобождении памяти датчика
*/
typedef bool(SensorFree)(Sensor* sensor);
/**
* @brief Указатель функции инициализации датчика
*/
typedef bool(SensorInitializer)(Sensor* sensor);
/**
* @brief Указатель функции деинициализации датчика
*/
typedef bool(SensorDeinitializer)(Sensor* sensor);
/**
* @brief Указатель функции обновления значения датчика
*/
typedef UnitempStatus(SensorUpdater)(Sensor* sensor);
//Типы подключения датчиков
typedef struct Interface {
//Имя интерфейса
const char* name;
//Функция выделения памяти интерфейса
SensorAllocator* allocator;
//Функция высвыбождения памяти интерфейса
SensorFree* mem_releaser;
//Функция обновления значения датчика по интерфейсу
SensorUpdater* updater;
} Interface;
//Типы датчиков
typedef struct {
//Модель датчика
const char* typename;
//Полное имя с аналогами
const char* altname;
//Тип возвращаемых данных
SensorDataType datatype;
//Интерфейс подключения
const Interface* interface;
//Интервал опроса датчика
uint16_t pollingInterval;
//Функция выделения памяти для датчика
SensorAllocator* allocator;
//Функция высвыбождения памяти для датчика
SensorFree* mem_releaser;
//Функция инициализации датчика
SensorInitializer* initializer;
//Функция деинициализация датчика
SensorDeinitializer* deinitializer;
//Функция обновления значения датчка
SensorUpdater* updater;
} SensorType;
//Датчик
typedef struct Sensor {
//Имя датчика
char* name;
//Температура
float temp;
//Относительная влажность
float hum;
//Атмосферное давление
float pressure;
//Тип датчика
const SensorType* type;
//Статус последнего опроса датчика
UnitempStatus status;
//Время последнего опроса датчика
uint32_t lastPollingTime;
//Смещение по температуре (x10)
int8_t temp_offset;
//Экземпляр датчика
void* instance;
} Sensor;
extern const Interface SINGLE_WIRE; //Собственный однопроводной протокол датчиков DHTXX и AM23XX
extern const Interface ONE_WIRE; //Однопроводной протокол Dallas
extern const Interface I2C; //I2C_2 (PC0, PC1)
//extern const Interface SPI;
/* ============================= Датчик(и) ============================= */
/**
* @brief Выделение памяти под датчик
*
* @param name Имя датчика
* @param type Тип датчика
* @param args Указатель на строку с парамерами датчика
* @return Указатель на датчик в случае успешного выделения памяти, NULL при ошибке
*/
Sensor* unitemp_sensor_alloc(char* name, const SensorType* type, char* args);
/**
* @brief Высвыбождение памяти конкретного датчка
* @param sensor Указатель на датчик
*/
void unitemp_sensor_free(Sensor* sensor);
/**
* @brief Обновление данных указанного датчика
* @param sensor Указатель на датчик
* @return Статус опроса датчика
*/
UnitempStatus unitemp_sensor_updateData(Sensor* sensor);
/**
* @brief Проверка наличия датчика в памяти
*
* @param sensor Указатель на датчик
* @return Истина если этот датчик уже загружен, ложь если это новый датчик
*/
bool unitemp_sensor_isContains(Sensor* sensor);
/**
* @brief Получить датчик из списка по индексу
*
* @param index Индекс датчика (0 - unitemp_sensors_getCount())
* @return Указатель на датчик при успехе, NULL при неудаче
*/
Sensor* unitemp_sensor_getActive(uint8_t index);
/**
* @brief Загрузка датчиков с SD-карты
* @return Истина если загрузка прошла успешно
*/
bool unitemp_sensors_load();
/**
* @brief Функция перезагрузки датчиков с SD-карты
*/
void unitemp_sensors_reload(void);
/**
* @brief Сохранение датчиков на SD-карту
* @return Истина если сохранение прошло успешно
*/
bool unitemp_sensors_save(void);
/**
* @brief Удаление датчика
*
* @param sensor Указатель на датчик
*/
void unitemp_sensor_delete(Sensor* sensor);
/**
* @brief Инициализация загруженных датчиков
* @return Истина если всё прошло успешно
*/
bool unitemp_sensors_init(void);
/**
* @brief Деинициализация загруженных датчиков
* @return Истина если всё прошло успешно
*/
bool unitemp_sensors_deInit(void);
/**
* @brief Высвыбождение памяти всех датчиков
*/
void unitemp_sensors_free(void);
/**
* @brief Обновить данные всех датчиков
*/
void unitemp_sensors_updateValues(void);
/**
* @brief Получить количество загруженных датчиков
* @return Количество датчиков
*/
uint8_t unitemp_sensors_getCount(void);
/**
* @brief Добавить датчик в общий список
* @param sensor Указатель на датчик
*/
void unitemp_sensors_add(Sensor* sensor);
/**
* @brief Получить списк доступных типов датчиков
* @return Указатель на список датчиков
*/
const SensorType** unitemp_sensors_getTypes(void);
/**
* @brief Получить количество доступных типов датчиков
* @return Количество доступных типов датчиков
*/
uint8_t unitemp_sensors_getTypesCount(void);
/**
* @brief Получить тип сенсора по его индексу
* @param index Индекс типа датчика (от 0 до SENSOR_TYPES_COUNT)
* @return const SensorType*
*/
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index);
/**
* @brief Преобразовать строчное название датчка в указатель
*
* @param str Имя датчика в виде строки
* @return Указатель на тип датчика при успехе, иначе NULL
*/
const SensorType* unitemp_sensors_getTypeFromStr(char* str);
/**
* @brief Получить количество активных датчиков
*
* @return Количество активных датчиков
*/
uint8_t unitemp_sensors_getActiveCount(void);
/* ============================= GPIO ============================= */
/**
* @brief Конвертация номера порта на корпусе FZ в GPIO
* @param name Номер порта на корпусе FZ
* @return Указатель на GPIO при успехе, NULL при ошибке
*/
const GPIO* unitemp_gpio_getFromInt(uint8_t name);
/**
* @brief Конвертация GPIO в номер на корпусе FZ
* @param gpio Указатель на порт
* @return Номер порта на корпусе FZ
*/
uint8_t unitemp_gpio_toInt(const GPIO* gpio);
/**
* @brief Блокировка GPIO указанным интерфейсом
* @param gpio Указатель на порт
* @param interface Указатель на интерфейс, которым порт будет занят
*/
void unitemp_gpio_lock(const GPIO* gpio, const Interface* interface);
/**
* @brief Разблокировка порта
* @param gpio Указатель на порт
*/
void unitemp_gpio_unlock(const GPIO* gpio);
/**
* @brief Получить количество доступных портов для указанного интерфейса
* @param interface Указатель на интерфейс
* @return Количество доступных портов
*/
uint8_t unitemp_gpio_getAviablePortsCount(const Interface* interface, const GPIO* extraport);
/**
* @brief Получить указатель на доступный для интерфейса порт по индексу
* @param interface Указатель на интерфейс
* @param index Номер порта (от 0 до unitemp_gpio_getAviablePortsCount())
* @param extraport Указатель на дополнительный порт, который будет принудительно считаться доступным. Можно указать NULL если не требуется
* @return Указатель на доступный порт
*/
const GPIO*
unitemp_gpio_getAviablePort(const Interface* interface, uint8_t index, const GPIO* extraport);
/* Датчики */
//DHTxx и их производные
#include "./interfaces/SingleWireSensor.h"
//DS18x2x
#include "./interfaces/OneWireSensor.h"
#include "./sensors/LM75.h"
//BMP280, BME280
#include "./sensors/BMx280.h"
#include "./sensors/AM2320.h"
#endif

View file

@ -0,0 +1,19 @@
App(
appid="unitemp",
name="Temp sensors reader",
apptype=FlipperAppType.EXTERNAL,
entry_point="unitemp_app",
cdefines=["UNITEMP_APP"],
requires=[
"gui",
],
stack_size=2 * 1024,
order=100,
fap_description = "Universal temperature sensors reader",
fap_author = "Quenon",
fap_weburl = "https://github.com/quen0n/Unitemp-Flipper-Zero-Plugin",
fap_category="GPIO",
fap_icon="icon.png",
fap_icon_assets="assets",
fap_libs=["assets"],
)

View file

@ -0,0 +1,3 @@
# Unitemp assets
Created by [@Svaarich](https://github.com/Svaarich)

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,131 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "I2CSensor.h"
static uint8_t sensors_count = 0;
void unitemp_i2c_acquire(FuriHalI2cBusHandle* handle) {
furi_hal_i2c_acquire(handle);
LL_GPIO_SetPinPull(gpio_ext_pc1.port, gpio_ext_pc1.pin, LL_GPIO_PULL_UP);
LL_GPIO_SetPinPull(gpio_ext_pc0.port, gpio_ext_pc0.pin, LL_GPIO_PULL_UP);
}
bool unitemp_i2c_isDeviceReady(I2CSensor* i2c_sensor) {
unitemp_i2c_acquire(i2c_sensor->i2c);
bool status = furi_hal_i2c_is_device_ready(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return status;
}
uint8_t unitemp_i2c_readReg(I2CSensor* i2c_sensor, uint8_t reg) {
//Блокировка шины
unitemp_i2c_acquire(i2c_sensor->i2c);
uint8_t buff[1] = {0};
furi_hal_i2c_read_mem(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, reg, buff, 1, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return buff[0];
}
bool unitemp_i2c_readArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data) {
unitemp_i2c_acquire(i2c_sensor->i2c);
bool status = furi_hal_i2c_rx(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, data, len, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return status;
}
bool unitemp_i2c_readRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data) {
unitemp_i2c_acquire(i2c_sensor->i2c);
bool status =
furi_hal_i2c_read_mem(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, startReg, data, len, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return status;
}
bool unitemp_i2c_writeReg(I2CSensor* i2c_sensor, uint8_t reg, uint8_t value) {
//Блокировка шины
unitemp_i2c_acquire(i2c_sensor->i2c);
uint8_t buff[1] = {value};
bool status =
furi_hal_i2c_write_mem(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, reg, buff, 1, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return status;
}
bool unitemp_i2c_writeArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data) {
unitemp_i2c_acquire(i2c_sensor->i2c);
bool status = furi_hal_i2c_tx(i2c_sensor->i2c, i2c_sensor->currentI2CAdr, data, len, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return status;
}
bool unitemp_i2c_writeRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data) {
//Блокировка шины
unitemp_i2c_acquire(i2c_sensor->i2c);
bool status = furi_hal_i2c_write_mem(
i2c_sensor->i2c, i2c_sensor->currentI2CAdr, startReg, data, len, 10);
furi_hal_i2c_release(i2c_sensor->i2c);
return status;
}
bool unitemp_I2C_sensor_alloc(Sensor* sensor, char* args) {
bool status = false;
I2CSensor* instance = malloc(sizeof(I2CSensor));
if(instance == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name);
return false;
}
instance->i2c = &furi_hal_i2c_handle_external;
sensor->instance = instance;
//Указание функций инициализации, деинициализации и обновления данных, а так же адреса на шине I2C
status = sensor->type->allocator(sensor, args);
int i2c_addr;
sscanf(args, "%X", &i2c_addr);
//Установка адреса шины I2C
if(i2c_addr >= instance->minI2CAdr && i2c_addr <= instance->maxI2CAdr) {
instance->currentI2CAdr = i2c_addr;
} else {
instance->currentI2CAdr = instance->minI2CAdr;
}
//Блокировка портов GPIO
sensors_count++;
unitemp_gpio_lock(unitemp_gpio_getFromInt(15), &I2C);
unitemp_gpio_lock(unitemp_gpio_getFromInt(16), &I2C);
return status;
}
bool unitemp_I2C_sensor_free(Sensor* sensor) {
bool status = sensor->type->mem_releaser(sensor);
free(sensor->instance);
if(--sensors_count == 0) {
unitemp_gpio_unlock(unitemp_gpio_getFromInt(15));
unitemp_gpio_unlock(unitemp_gpio_getFromInt(16));
}
return status;
}
UnitempStatus unitemp_I2C_sensor_update(Sensor* sensor) {
if(sensor->status != UT_SENSORSTATUS_OK) {
sensor->type->initializer(sensor);
}
return sensor->type->updater(sensor);
}

View file

@ -0,0 +1,128 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_I2C
#define UNITEMP_I2C
#include "../unitemp.h"
#include <furi_hal_i2c.h>
//Структура I2C датчика
typedef struct I2CSensor {
//Указатель на интерфейс I2C
FuriHalI2cBusHandle* i2c;
//Минимальный адрес устройства на шине I2C
uint8_t minI2CAdr;
//Максимальный адрес устройства на шине I2C
uint8_t maxI2CAdr;
//Текущий адрес устройства на шине I2C
uint8_t currentI2CAdr;
//Указатель на собственный экземпляр датчика
void* sensorInstance;
} I2CSensor;
/**
* @brief Заблокировать шину I2C
*
* @param handle Указатель на шину
*/
void unitemp_i2c_acquire(FuriHalI2cBusHandle* handle);
/**
* @brief Проверить наличие датчика на шине
*
* @param i2c_sensor Указатель на датчик
* @return Истина если устройство отозвалось
*/
bool unitemp_i2c_isDeviceReady(I2CSensor* i2c_sensor);
/**
* @brief Выделение памяти для датчика на шине I2C
* @param sensor Указатель на датчик
* @param st Тип датчика
* @return Истина если всё ок
*/
bool unitemp_I2C_sensor_alloc(Sensor* sensor, char* args);
/**
* @brief Высвобождение памяти инстанса датчика
* @param sensor Указатель на датчик
*/
bool unitemp_I2C_sensor_free(Sensor* sensor);
/**
* @brief Обновить значение с датчка
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_I2C_sensor_update(Sensor* sensor);
/**
* @brief Прочитать значение регистра reg
* @param i2c_sensor Указатель на инстанс датчика
* @param reg Номер регистра
* @return Значение регистра
*/
uint8_t unitemp_i2c_readReg(I2CSensor* i2c_sensor, uint8_t reg);
/**
* @brief Прочитать масссив значений из памяти
* @param i2c_sensor Указатель на инстанс датчика
* @param startReg Адрес регистра с которого начнётся чтение
* @param len Количество байт для считывания из регистра
* @param data Указатель на массив куда будут считаны данные
* @return Истина если устройство вернуло данные
*/
bool unitemp_i2c_readRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data);
/**
* @brief Записать значение в регистр
* @param i2c_sensor Указатель на инстанс датчика
* @param reg Номер регистра
* @param value Значение для записи
* @return Истина если значение записано
*/
bool unitemp_i2c_writeReg(I2CSensor* i2c_sensor, uint8_t reg, uint8_t value);
/**
* @brief Записать масссив значений в память
* @param i2c_sensor Указатель на инстанс датчика
* @param startReg Адрес регистра с которого начнётся запись
* @param len Количество байт для считывания из регистра
* @param data Указатель на массив откуда будут записаны данные
* @return Истина если устройство вернуло данные
*/
bool unitemp_i2c_writeRegArray(I2CSensor* i2c_sensor, uint8_t startReg, uint8_t len, uint8_t* data);
/**
* @brief Прочитать массив данных по шине I2C
* @param i2c_sensor Указатель на инстанс датчика
* @param startReg Адрес регистра с которого начнётся чтение
* @param data Указатель на массив куда будут считаны данные
* @return Истина если устройство вернуло данные
*/
bool unitemp_i2c_readArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data);
/**
* @brief Записать масссив данных по шине I2C
* @param i2c_sensor Указатель на инстанс датчика
* @param len Количество байт для считывания из регистра
* @param data Указатель на массив откуда будут записаны данные
* @return Истина если устройство вернуло данные
*/
bool unitemp_i2c_writeArray(I2CSensor* i2c_sensor, uint8_t len, uint8_t* data);
#endif

View file

@ -0,0 +1,490 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
//Использован код Дмитрия Погребняка: https://aterlux.ru/article/1wire
#include "OneWireSensor.h"
#include <furi.h>
#include <furi_hal.h>
#include <one_wire/one_wire_host.h>
const SensorType Dallas = {
.typename = "Dallas",
.altname = "Dallas (DS18x2x)",
.interface = &ONE_WIRE,
.datatype = UT_DATA_TYPE_TEMP,
.pollingInterval = 1000,
.allocator = unitemp_onewire_sensor_alloc,
.mem_releaser = unitemp_onewire_sensor_free,
.initializer = unitemp_onewire_sensor_init,
.deinitializer = unitemp_onewire_sensor_deinit,
.updater = unitemp_onewire_sensor_update};
// Переменные для хранения промежуточного результата сканирования шины
// найденный восьмибайтовый адрес
static uint8_t onewire_enum[8] = {0};
// последний нулевой бит, где была неоднозначность (нумеруя с единицы)
static uint8_t onewire_enum_fork_bit = 65;
OneWireBus* uintemp_onewire_bus_alloc(const GPIO* gpio) {
if(gpio == NULL) {
return NULL;
}
//Проверка на наличие шины на этом порте
for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) {
if(unitemp_sensor_getActive(i)->type->interface == &ONE_WIRE &&
((OneWireSensor*)unitemp_sensor_getActive(i)->instance)->bus->gpio->num == gpio->num) {
//Если шина на этом порту уже есть, то возврат указателя на шину
return ((OneWireSensor*)unitemp_sensor_getActive(i)->instance)->bus;
}
}
OneWireBus* bus = malloc(sizeof(OneWireBus));
bus->device_count = 0;
bus->gpio = gpio;
bus->powerMode = PWR_PASSIVE;
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "one wire bus (port %d) allocated", gpio->num);
#endif
return bus;
}
bool unitemp_onewire_bus_init(OneWireBus* bus) {
if(bus == NULL) return false;
bus->device_count++;
//Выход если шина уже была инициализирована
if(bus->device_count > 1) return true;
unitemp_gpio_lock(bus->gpio, &ONE_WIRE);
//Высокий уровень по умолчанию
furi_hal_gpio_write(bus->gpio->pin, true);
//Режим работы - OpenDrain, подтяжка включается на всякий случай
furi_hal_gpio_init(
bus->gpio->pin, //Порт FZ
GpioModeOutputOpenDrain, //Режим работы - открытый сток
GpioPullUp, //Принудительная подтяжка линии данных к питанию
GpioSpeedVeryHigh); //Скорость работы - максимальная
return true;
}
bool unitemp_onewire_bus_deinit(OneWireBus* bus) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "devices on wire %d: %d", bus->gpio->num, bus->device_count);
#endif
bus->device_count--;
if(bus->device_count <= 0) {
bus->device_count = 0;
unitemp_gpio_unlock(bus->gpio);
//Режим работы - аналог, подтяжка выключена
furi_hal_gpio_init(
bus->gpio->pin, //Порт FZ
GpioModeAnalog, //Режим работы - аналог
GpioPullNo, //Подтяжка выключена
GpioSpeedLow); //Скорость работы - минимальная
//Низкий уровень по умолчанию
furi_hal_gpio_write(bus->gpio->pin, false);
return true;
} else {
return false;
}
}
bool unitemp_onewire_bus_start(OneWireBus* bus) {
furi_hal_gpio_write(bus->gpio->pin, false);
furi_delay_us(500);
furi_hal_gpio_write(bus->gpio->pin, true);
//Ожидание подъёма шины
uint32_t t = furi_get_tick();
while(!furi_hal_gpio_read(bus->gpio->pin)) {
//Выход если шина не поднялась
if(furi_get_tick() - t > 10) return false;
}
furi_delay_us(100);
bool status = !furi_hal_gpio_read(bus->gpio->pin);
furi_delay_us(400);
return status;
}
void unitemp_onewire_bus_send_bit(OneWireBus* bus, bool state) {
//Необходимо для стабильной работы при пассивном питании
if(bus->powerMode == PWR_PASSIVE) furi_delay_us(100);
if(state) {
// write 1
furi_hal_gpio_write(bus->gpio->pin, false);
furi_delay_us(1);
furi_hal_gpio_write(bus->gpio->pin, true);
furi_delay_us(90);
} else {
furi_hal_gpio_write(bus->gpio->pin, false);
furi_delay_us(90);
furi_hal_gpio_write(bus->gpio->pin, true);
//Ожидание подъёма шины
uint32_t t = furi_get_tick();
while(!furi_hal_gpio_read(bus->gpio->pin)) {
//Выход если шина не поднялась
if(furi_get_tick() - t > 10) return;
}
}
}
void unitemp_onewire_bus_send_byte(OneWireBus* bus, uint8_t data) {
for(int i = 0; i < 8; i++) {
unitemp_onewire_bus_send_bit(bus, (data & (1 << i)) != 0);
}
}
void unitemp_onewire_bus_send_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len) {
for(uint8_t i = 0; i < len; i++) {
unitemp_onewire_bus_send_byte(bus, data[i]);
}
}
bool unitemp_onewire_bus_read_bit(OneWireBus* bus) {
furi_delay_ms(1);
furi_hal_gpio_write(bus->gpio->pin, false);
furi_delay_us(2); // Длительность низкого уровня, минимум 1 мкс
furi_hal_gpio_write(bus->gpio->pin, true);
furi_delay_us(8); // Пауза до момента сэмплирования, всего не более 15 мкс
bool r = furi_hal_gpio_read(bus->gpio->pin);
furi_delay_us(80); // Ожидание до следующего тайм-слота, минимум 60 мкс с начала низкого уровня
return r;
}
uint8_t unitemp_onewire_bus_read_byte(OneWireBus* bus) {
uint8_t r = 0;
for(uint8_t p = 8; p; p--) {
r >>= 1;
if(unitemp_onewire_bus_read_bit(bus)) r |= 0x80;
}
return r;
}
void unitemp_onewire_bus_read_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len) {
for(uint8_t i = 0; i < len; i++) {
data[i] = unitemp_onewire_bus_read_byte(bus);
}
}
static uint8_t onewire_CRC_update(uint8_t crc, uint8_t b) {
for(uint8_t p = 8; p; p--) {
crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0b10001100 : (crc >> 1);
b >>= 1;
}
return crc;
}
bool unitemp_onewire_CRC_check(uint8_t* data, uint8_t len) {
uint8_t crc = 0;
for(uint8_t i = 0; i < len; i++) {
crc = onewire_CRC_update(crc, data[i]);
}
return !crc;
}
char* unitemp_onewire_sensor_getModel(Sensor* sensor) {
OneWireSensor* ow_sensor = sensor->instance;
switch(ow_sensor->deviceID[0]) {
case FC_DS18B20:
return "DS18B20";
case FC_DS18S20:
return "DS18S20";
case FC_DS1822:
return "DS1822";
default:
return "unknown";
}
}
bool unitemp_onewire_sensor_readID(OneWireSensor* instance) {
if(!unitemp_onewire_bus_start(instance->bus)) return false;
unitemp_onewire_bus_send_byte(instance->bus, 0x33); // Чтение ПЗУ
unitemp_onewire_bus_read_byteArray(instance->bus, instance->deviceID, 8);
if(!unitemp_onewire_CRC_check(instance->deviceID, 8)) {
memset(instance->deviceID, 0, 8);
return false;
}
instance->familyCode = instance->deviceID[0];
return true;
}
void unitemp_onewire_bus_enum_init(void) {
for(uint8_t p = 0; p < 8; p++) {
onewire_enum[p] = 0;
}
onewire_enum_fork_bit = 65; // правее правого
}
uint8_t* unitemp_onewire_bus_enum_next(OneWireBus* bus) {
furi_delay_ms(10);
if(!onewire_enum_fork_bit) { // Если на предыдущем шаге уже не было разногласий
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "All devices on wire %d is found", unitemp_gpio_toInt(bus->gpio));
#endif
return 0; // то просто выходим ничего не возвращая
}
if(!unitemp_onewire_bus_start(bus)) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Wire %d is empty", unitemp_gpio_toInt(bus->gpio));
#endif
return 0;
}
uint8_t bp = 8;
uint8_t* pprev = &onewire_enum[0];
uint8_t prev = *pprev;
uint8_t next = 0;
uint8_t p = 1;
unitemp_onewire_bus_send_byte(bus, 0xF0);
uint8_t newfork = 0;
for(;;) {
uint8_t not0 = unitemp_onewire_bus_read_bit(bus);
uint8_t not1 = unitemp_onewire_bus_read_bit(bus);
if(!not0) { // Если присутствует в адресах бит ноль
if(!not1) { // Но также присустствует бит 1 (вилка)
if(p <
onewire_enum_fork_bit) { // Если мы левее прошлого правого конфликтного бита,
if(prev & 1) {
next |= 0x80; // то копируем значение бита из прошлого прохода
} else {
newfork = p; // если ноль, то запомним конфликтное место
}
} else if(p == onewire_enum_fork_bit) {
next |=
0x80; // если на этом месте в прошлый раз был правый конфликт с нулём, выведем 1
} else {
newfork = p; // правее - передаём ноль и запоминаем конфликтное место
}
} // в противном случае идём, выбирая ноль в адресе
} else {
if(!not1) { // Присутствует единица
next |= 0x80;
} else { // Нет ни нулей ни единиц - ошибочная ситуация
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Wrong wire %d situation", unitemp_gpio_toInt(bus->gpio));
#endif
return 0;
}
}
unitemp_onewire_bus_send_bit(bus, next & 0x80);
bp--;
if(!bp) {
*pprev = next;
if(p >= 64) break;
next = 0;
pprev++;
prev = *pprev;
bp = 8;
} else {
if(p >= 64) break;
prev >>= 1;
next >>= 1;
}
p++;
}
onewire_enum_fork_bit = newfork;
return &onewire_enum[0];
}
void unitemp_onewire_bus_select_sensor(OneWireSensor* instance) {
unitemp_onewire_bus_send_byte(instance->bus, 0x55);
unitemp_onewire_bus_send_byteArray(instance->bus, instance->deviceID, 8);
}
bool unitemp_onewire_sensor_alloc(Sensor* sensor, char* args) {
OneWireSensor* instance = malloc(sizeof(OneWireSensor));
if(instance == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name);
return false;
}
sensor->instance = instance;
//Очистка адреса
memset(instance->deviceID, 0, 8);
int gpio, addr_0, addr_1, addr_2, addr_3, addr_4, addr_5, addr_6, addr_7;
sscanf(
args,
"%d %2X%2X%2X%2X%2X%2X%2X%2X",
&gpio,
&addr_0,
&addr_1,
&addr_2,
&addr_3,
&addr_4,
&addr_5,
&addr_6,
&addr_7);
instance->deviceID[0] = addr_0;
instance->deviceID[1] = addr_1;
instance->deviceID[2] = addr_2;
instance->deviceID[3] = addr_3;
instance->deviceID[4] = addr_4;
instance->deviceID[5] = addr_5;
instance->deviceID[6] = addr_6;
instance->deviceID[7] = addr_7;
instance->familyCode = instance->deviceID[0];
instance->bus = uintemp_onewire_bus_alloc(unitemp_gpio_getFromInt(gpio));
if(instance != NULL) {
return true;
}
FURI_LOG_E(APP_NAME, "Sensor %s bus allocation error", sensor->name);
free(instance);
return false;
}
bool unitemp_onewire_sensor_free(Sensor* sensor) {
if(((OneWireSensor*)sensor->instance)->bus != NULL) {
if(((OneWireSensor*)sensor->instance)->bus->device_count == 0) {
free(((OneWireSensor*)sensor->instance)->bus);
}
}
free(sensor->instance);
return true;
}
bool unitemp_onewire_sensor_init(Sensor* sensor) {
OneWireSensor* instance = sensor->instance;
if(instance == NULL || instance->bus == NULL) {
FURI_LOG_E(APP_NAME, "Sensor pointer is null!");
return false;
}
unitemp_onewire_bus_init(instance->bus);
furi_delay_ms(1);
if(instance->familyCode == FC_DS18B20 || instance->familyCode == FC_DS1822) {
//Установка разрядности в 10 бит
if(!unitemp_onewire_bus_start(instance->bus)) return false;
unitemp_onewire_bus_select_sensor(instance);
unitemp_onewire_bus_send_byte(instance->bus, 0x4E); // Запись в память
uint8_t buff[3];
//Значения тревоги
buff[0] = 0x4B; //Значение нижнего предела температуры
buff[1] = 0x46; //Значение верхнего предела температуры
//Конфигурация
buff[2] = 0b01111111; //12 бит разрядность преобразования
unitemp_onewire_bus_send_byteArray(instance->bus, buff, 3);
//Сохранение значений в EEPROM для автоматического восстановления после сбоев питания
if(!unitemp_onewire_bus_start(instance->bus)) return false;
unitemp_onewire_bus_select_sensor(instance);
unitemp_onewire_bus_send_byte(instance->bus, 0x48); // Запись в EEPROM
}
return true;
}
bool unitemp_onewire_sensor_deinit(Sensor* sensor) {
OneWireSensor* instance = sensor->instance;
if(instance == NULL || instance->bus == NULL) return false;
unitemp_onewire_bus_deinit(instance->bus);
return true;
}
UnitempStatus unitemp_onewire_sensor_update(Sensor* sensor) {
//Снятие особого статуса с датчика при пассивном режиме питания
if(sensor->status == UT_SENSORSTATUS_EARLYPOOL) {
return UT_SENSORSTATUS_POLLING;
}
OneWireSensor* instance = sensor->instance;
uint8_t buff[9] = {0};
if(sensor->status != UT_SENSORSTATUS_POLLING) {
//Если датчик в прошлый раз не отозвался, проверка его наличия на шине
if(sensor->status == UT_SENSORSTATUS_TIMEOUT || sensor->status == UT_SENSORSTATUS_BADCRC) {
if(!unitemp_onewire_bus_start(instance->bus)) return UT_SENSORSTATUS_TIMEOUT;
unitemp_onewire_bus_select_sensor(instance);
unitemp_onewire_bus_send_byte(instance->bus, 0xBE); // Read Scratch-pad
unitemp_onewire_bus_read_byteArray(instance->bus, buff, 9);
if(!unitemp_onewire_CRC_check(buff, 9)) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Sensor %s is not found", sensor->name);
#endif
return UT_SENSORSTATUS_TIMEOUT;
}
}
if(!unitemp_onewire_bus_start(instance->bus)) return UT_SENSORSTATUS_TIMEOUT;
//Запуск преобразования на всех датчиках в режиме пассивного питания
if(instance->bus->powerMode == PWR_PASSIVE) {
unitemp_onewire_bus_send_byte(instance->bus, 0xCC); // skip addr
//Установка на всех датчиках этой шины особого статуса, чтобы не запускать преобразование ещё раз
for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) {
if(unitemp_sensor_getActive(i)->type->interface == &ONE_WIRE &&
((OneWireSensor*)unitemp_sensor_getActive(i)->instance)->bus == instance->bus) {
unitemp_sensor_getActive(i)->status = UT_SENSORSTATUS_EARLYPOOL;
}
}
} else {
unitemp_onewire_bus_select_sensor(instance);
}
unitemp_onewire_bus_send_byte(instance->bus, 0x44); // convert t
if(instance->bus->powerMode == PWR_PASSIVE) {
furi_hal_gpio_write(instance->bus->gpio->pin, true);
furi_hal_gpio_init(
instance->bus->gpio->pin, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
}
return UT_SENSORSTATUS_POLLING;
} else {
if(instance->bus->powerMode == PWR_PASSIVE) {
furi_hal_gpio_write(instance->bus->gpio->pin, true);
furi_hal_gpio_init(
instance->bus->gpio->pin, GpioModeOutputOpenDrain, GpioPullUp, GpioSpeedVeryHigh);
}
if(!unitemp_onewire_bus_start(instance->bus)) return UT_SENSORSTATUS_TIMEOUT;
unitemp_onewire_bus_select_sensor(instance);
unitemp_onewire_bus_send_byte(instance->bus, 0xBE); // Read Scratch-pad
unitemp_onewire_bus_read_byteArray(instance->bus, buff, 9);
if(!unitemp_onewire_CRC_check(buff, 9)) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Failed CRC check: %s", sensor->name);
#endif
return UT_SENSORSTATUS_BADCRC;
}
int16_t raw = buff[0] | ((int16_t)buff[1] << 8);
if(instance->familyCode == FC_DS18S20) {
//Песевдо-12-бит. Отключено из-за неестественности и нестабильности показаний по сравнению с DS18B20
//sensor->temp = ((float)raw / 2.0f) - 0.25f + (16.0f - buff[6]) / 16.0f;
//Честные 9 бит
sensor->temp = ((float)raw / 2.0f);
} else {
sensor->temp = (float)raw / 16.0f;
}
}
return UT_SENSORSTATUS_OK;
}
bool unitemp_onewire_id_compare(uint8_t* id1, uint8_t* id2) {
if(id1 == NULL || id2 == NULL) return false;
for(uint8_t i = 0; i < 8; i++) {
if(id1[i] != id2[i]) return false;
}
return true;
}

View file

@ -0,0 +1,223 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_OneWire
#define UNITEMP_OneWire
#include "../unitemp.h"
//Коды семейства устройств
typedef enum DallasFamilyCode {
FC_DS18S20 = 0x10,
FC_DS1822 = 0x22,
FC_DS18B20 = 0x28,
} DallasFamilyCode;
//Режим питания датчка
typedef enum PowerMode {
PWR_PASSIVE, //Питание от линии данных
PWR_ACTIVE //Питание от источника питания
} PowerMode;
//Инстанс шины one wire
typedef struct {
//Порт подключения датчика
const GPIO* gpio;
//Количество устройств на шине
//Обновляется при ручном добавлении датчика на эту шину
int8_t device_count;
//Режим питания датчиков на шине
PowerMode powerMode;
} OneWireBus;
//Инстанс датчика one wire
typedef struct OneWireSensor {
//Указатель на шину OneWire
OneWireBus* bus;
//Текущий адрес устройства на шине OneWire
uint8_t deviceID[8];
//Код семейства устройств
DallasFamilyCode familyCode;
} OneWireSensor;
/**
* @brief Выделение памяти для датчика на шине OneWire
* @param sensor Указатель на датчик
* @param args Указатель на массив аргументов с параметрами датчика
* @return Истина если всё ок
*/
bool unitemp_onewire_sensor_alloc(Sensor* sensor, char* args);
/**
* @brief Высвобождение памяти инстанса датчика
* @param sensor Указатель на датчик
*/
bool unitemp_onewire_sensor_free(Sensor* sensor);
/**
* @brief Инициализации датчика на шине one wire
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_onewire_sensor_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
* @param sensor Указатель на датчик
*/
bool unitemp_onewire_sensor_deinit(Sensor* sensor);
/**
* @brief Обновить значение с датчка
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_onewire_sensor_update(Sensor* sensor);
/**
* @brief Выделение памяти для шины one wire и её инициализация
* @param gpio Порт на котором необходимо создать шину
* @return При успехе возвращает указатель на шину one wire
*/
OneWireBus* uintemp_onewire_bus_alloc(const GPIO* gpio);
/**
* @brief Инициализация шины one wire
*
* @param bus Указатель на шину
* @return Истина если инициализация успешна
*/
bool unitemp_onewire_bus_init(OneWireBus* bus);
/**
* @brief Деинициализация шины one wire
*
* @param bus Указатель на шину
* @return Истина если шина была деинициализирована, ложь если на шине остались устройства
*/
bool unitemp_onewire_bus_deinit(OneWireBus* bus);
/**
* @brief Запуск общения с датчиками на шине one wire
* @param bus Указатель на шину
* @return Истина если хотя бы одно устройство отозвалось
*/
bool unitemp_onewire_bus_start(OneWireBus* bus);
/**
* @brief Отправить 1 бит данных на шину one wire
* @param bus Указатель на шину
* @param state Логический уровень
*/
void unitemp_onewire_bus_send_bit(OneWireBus* bus, bool state);
/**
* @brief Запись байта на шину one wire
*
* @param bus Указатель на шину one wire
* @param data Записываемый байт
*/
void unitemp_onewire_bus_send_byte(OneWireBus* bus, uint8_t data);
/**
* @brief Запись массива байт на шину one wire
*
* @param bus Указатель на шину one wire
* @param data Указатель на массив, откуда будут записаны данные
* @param len Количество байт
*/
void unitemp_onewire_bus_send_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len);
/**
* @brief Чтение бита на шине one wire
*
* @param bus Указатель на шину one wire
* @return Логический уровень бита
*/
bool unitemp_onewire_bus_read_bit(OneWireBus* bus);
/**
* @brief Чтение байта с шины One Wire
*
* @param bus Указатель на шину one wire
* @return Байт информации
*/
uint8_t unitemp_onewire_bus_read_byte(OneWireBus* bus);
/**
* @brief Чтение массива байт с шины One Wire
*
* @param bus Указатель на шину one wire
* @param data Указатель на массив, куда будут записаны данные
* @param len Количество байт
*/
void unitemp_onewire_bus_read_byteArray(OneWireBus* bus, uint8_t* data, uint8_t len);
/**
* @brief Проверить контрольную сумму массива данных
*
* @param data Указатель на массив данных
* @param len Длина массива (включая байт CRC)
* @return Истина если контрольная сумма корректная
*/
bool unitemp_onewire_CRC_check(uint8_t* data, uint8_t len);
/**
* @brief Получить имя модели датчика на шине One Wire
*
* @param sensor Указатель на датчик
* @return Указатель на строку с названием
*/
char* unitemp_onewire_sensor_getModel(Sensor* sensor);
/**
* @brief Чтение индификатора единственного датчика. ID запишется в инстанс датчика
*
* @param instance Указатель на инстанс датчика
* @return Истина, если код успешно прочитан, ложь если устройство отсутствует или устройств на шине больше одного
*/
bool unitemp_oneWire_sensor_readID(OneWireSensor* instance);
/**
* @brief Команда выбора определённого датчка по его ID
* @param instance Указатель на датчик one wire
*/
void unitemp_onewire_bus_select_sensor(OneWireSensor* instance);
/**
* @brief Инициализация процесса поиска адресов на шине one wire
*/
void unitemp_onewire_bus_enum_init(void);
/**
* @brief Перечисляет устройства на шине one wire и получает очередной адрес
* @param bus Указатель на шину one wire
* @return Возвращает указатель на буфер, содержащий восьмибайтовое значение адреса, либо NULL, если поиск завешён
*/
uint8_t* unitemp_onewire_bus_enum_next(OneWireBus* bus);
/**
* @brief Сравнить ID датчиков
*
* @param id1 Указатель на адрес первого датчика
* @param id2 Указатель на адрес второго датчика
* @return Истина если ID индентичны
*/
bool unitemp_onewire_id_compare(uint8_t* id1, uint8_t* id2);
extern const SensorType Dallas;
#endif

View file

@ -0,0 +1,279 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "SingleWireSensor.h"
//Максимальное количество попугаев ожидания датчика
#define POLLING_TIMEOUT_TICKS 500
/* Типы датчиков и их параметры */
const SensorType DHT11 = {
.typename = "DHT11",
.interface = &SINGLE_WIRE,
.datatype = UT_DATA_TYPE_TEMP_HUM,
.pollingInterval = 2000,
.allocator = unitemp_singlewire_alloc,
.mem_releaser = unitemp_singlewire_free,
.initializer = unitemp_singlewire_init,
.deinitializer = unitemp_singlewire_deinit,
.updater = unitemp_singlewire_update};
const SensorType DHT12_SW = {
.typename = "DHT12",
.interface = &SINGLE_WIRE,
.datatype = UT_DATA_TYPE_TEMP_HUM,
.pollingInterval = 2000,
.allocator = unitemp_singlewire_alloc,
.mem_releaser = unitemp_singlewire_free,
.initializer = unitemp_singlewire_init,
.deinitializer = unitemp_singlewire_deinit,
.updater = unitemp_singlewire_update};
const SensorType DHT21 = {
.typename = "DHT21",
.altname = "DHT21 (AM2301)",
.interface = &SINGLE_WIRE,
.datatype = UT_DATA_TYPE_TEMP_HUM,
.pollingInterval = 1000,
.allocator = unitemp_singlewire_alloc,
.mem_releaser = unitemp_singlewire_free,
.initializer = unitemp_singlewire_init,
.deinitializer = unitemp_singlewire_deinit,
.updater = unitemp_singlewire_update};
const SensorType DHT22 = {
.typename = "DHT22",
.altname = "DHT22 (AM2302)",
.interface = &SINGLE_WIRE,
.datatype = UT_DATA_TYPE_TEMP_HUM,
.pollingInterval = 2000,
.allocator = unitemp_singlewire_alloc,
.mem_releaser = unitemp_singlewire_free,
.initializer = unitemp_singlewire_init,
.deinitializer = unitemp_singlewire_deinit,
.updater = unitemp_singlewire_update};
const SensorType AM2320_SW = {
.typename = "AM2320",
.altname = "AM2320 (single wire)",
.interface = &SINGLE_WIRE,
.datatype = UT_DATA_TYPE_TEMP_HUM,
.pollingInterval = 2000,
.allocator = unitemp_singlewire_alloc,
.mem_releaser = unitemp_singlewire_free,
.initializer = unitemp_singlewire_init,
.deinitializer = unitemp_singlewire_deinit,
.updater = unitemp_singlewire_update};
bool unitemp_singlewire_alloc(Sensor* sensor, char* args) {
if(args == NULL) return false;
SingleWireSensor* instance = malloc(sizeof(SingleWireSensor));
if(instance == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name);
return false;
}
sensor->instance = instance;
int gpio = 255;
sscanf(args, "%d", &gpio);
if(unitemp_singlewire_sensorSetGPIO(sensor, unitemp_gpio_getFromInt(gpio))) {
return true;
}
FURI_LOG_E(APP_NAME, "Sensor %s GPIO setting error", sensor->name);
free(instance);
return false;
}
bool unitemp_singlewire_free(Sensor* sensor) {
free(sensor->instance);
return true;
}
bool unitemp_singlewire_init(Sensor* sensor) {
SingleWireSensor* instance = ((Sensor*)sensor)->instance;
if(instance == NULL || instance->gpio == NULL) {
FURI_LOG_E(APP_NAME, "Sensor pointer is null!");
return false;
}
unitemp_gpio_lock(instance->gpio, &SINGLE_WIRE);
//Высокий уровень по умолчанию
furi_hal_gpio_write(instance->gpio->pin, true);
//Режим работы - OpenDrain, подтяжка включается на всякий случай
furi_hal_gpio_init(
instance->gpio->pin, //Порт FZ
GpioModeOutputOpenDrain, //Режим работы - открытый сток
GpioPullUp, //Принудительная подтяжка линии данных к питанию
GpioSpeedVeryHigh); //Скорость работы - максимальная
return true;
}
bool unitemp_singlewire_deinit(Sensor* sensor) {
SingleWireSensor* instance = ((Sensor*)sensor)->instance;
if(instance == NULL || instance->gpio == NULL) return false;
unitemp_gpio_unlock(instance->gpio);
//Низкий уровень по умолчанию
furi_hal_gpio_write(instance->gpio->pin, false);
//Режим работы - аналог, подтяжка выключена
furi_hal_gpio_init(
instance->gpio->pin, //Порт FZ
GpioModeAnalog, //Режим работы - аналог
GpioPullNo, //Подтяжка выключена
GpioSpeedLow); //Скорость работы - минимальная
return true;
}
bool unitemp_singlewire_sensorSetGPIO(Sensor* sensor, const GPIO* gpio) {
if(sensor == NULL || gpio == NULL) return false;
SingleWireSensor* instance = sensor->instance;
instance->gpio = gpio;
return true;
}
const GPIO* unitemp_singlewire_sensorGetGPIO(Sensor* sensor) {
if(sensor == NULL) return NULL;
SingleWireSensor* instance = sensor->instance;
return instance->gpio;
}
UnitempStatus unitemp_singlewire_update(Sensor* sensor) {
SingleWireSensor* instance = sensor->instance;
//Массив для приёма данных
uint8_t data[5] = {0};
/* Запрос */
//Опускание линии
furi_hal_gpio_write(instance->gpio->pin, false);
//Ожидание более 18 мс
furi_delay_ms(19);
//Выключение прерываний, чтобы ничто не мешало обработке данных
__disable_irq();
//Подъём линии
furi_hal_gpio_write(instance->gpio->pin, true);
/* Ответ датчика */
//Переменная-счётчик
uint16_t timeout = 0;
//Ожидание подъёма линии
while(!furi_hal_gpio_read(instance->gpio->pin)) {
timeout++;
if(timeout > POLLING_TIMEOUT_TICKS) {
//Включение прерываний
__enable_irq();
//Возврат признака отсутствующего датчика
return UT_SENSORSTATUS_TIMEOUT;
}
}
timeout = 0;
//Ожидание спада линии
while(furi_hal_gpio_read(instance->gpio->pin)) {
timeout++;
if(timeout > POLLING_TIMEOUT_TICKS) {
//Включение прерываний
__enable_irq();
//Возврат признака отсутствующего датчика
return UT_SENSORSTATUS_TIMEOUT;
}
}
//Ожидание подъёма линии
while(!furi_hal_gpio_read(instance->gpio->pin)) {
timeout++;
if(timeout > POLLING_TIMEOUT_TICKS) {
//Включение прерываний
__enable_irq();
//Возврат признака отсутствующего датчика
return UT_SENSORSTATUS_TIMEOUT;
}
}
timeout = 0;
//Ожидание спада линии
while(furi_hal_gpio_read(instance->gpio->pin)) {
timeout++;
if(timeout > POLLING_TIMEOUT_TICKS) {
//Включение прерываний
__enable_irq();
//Возврат признака отсутствующего датчика
return UT_SENSORSTATUS_TIMEOUT;
}
}
/* Чтение данных с датчика*/
//Приём 5 байт
for(uint8_t a = 0; a < 5; a++) {
for(uint8_t b = 7; b != 255; b--) {
uint16_t hT = 0, lT = 0;
//Пока линия в низком уровне, инкремент переменной lT
while(!furi_hal_gpio_read(instance->gpio->pin) && lT != 65535) lT++;
//Пока линия в высоком уровне, инкремент переменной hT
while(furi_hal_gpio_read(instance->gpio->pin) && hT != 65535) hT++;
//Если hT больше lT, то пришла единица
if(hT > lT) data[a] |= (1 << b);
}
}
//Включение прерываний
__enable_irq();
//Проверка контрольной суммы
if((uint8_t)(data[0] + data[1] + data[2] + data[3]) != data[4]) {
//Если контрольная сумма не совпала, возврат ошибки
return UT_SENSORSTATUS_BADCRC;
}
/* Преобразование данных в явный вид */
//DHT11 и DHT12
if(sensor->type == &DHT11 || sensor->type == &DHT12_SW) {
sensor->hum = (float)data[0];
sensor->temp = (float)data[2];
//Проверка на отрицательность температуры
if(data[3] != 0) {
//Проверка знака
if(!(data[3] & (1 << 7))) {
//Добавление положительной дробной части
sensor->temp += data[3] * 0.1f;
} else {
//А тут делаем отрицательное значение
data[3] &= ~(1 << 7);
sensor->temp += data[3] * 0.1f;
sensor->temp *= -1;
}
}
}
//DHT21, DHT22, AM2320
if(sensor->type == &DHT21 || sensor->type == &DHT22 || sensor->type == &AM2320_SW) {
sensor->hum = (float)(((uint16_t)data[0] << 8) | data[1]) / 10;
uint16_t raw = (((uint16_t)data[2] << 8) | data[3]);
//Проверка на отрицательность температуры
if(READ_BIT(raw, 1 << 15)) {
//Проверка на способ кодирования данных
if(READ_BIT(raw, 0x60)) {
//Не оригинал
sensor->temp = (float)((int16_t)raw) / 10;
} else {
//Оригинальный датчик
CLEAR_BIT(raw, 1 << 15);
sensor->temp = (float)(raw) / -10;
}
} else {
sensor->temp = (float)(raw) / 10;
}
}
//Возврат признака успешного опроса
return UT_SENSORSTATUS_OK;
}

View file

@ -0,0 +1,92 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_SINGLE_WIRE
#define UNITEMP_SINGLE_WIRE
#include "../unitemp.h"
#include "../Sensors.h"
//Интерфейс Single Wire
typedef struct {
//Порт подключения датчика
const GPIO* gpio;
} SingleWireSensor;
/* Датчики */
extern const SensorType DHT11;
extern const SensorType DHT12_SW;
extern const SensorType DHT21;
extern const SensorType DHT22;
extern const SensorType AM2320_SW;
/**
* @brief Инициализация датчика
*
* @param sensor Указатель на инициализируемый датчик
* @return Истина если всё прошло успешно
*/
bool unitemp_singlewire_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
*
* @param sensor Указатель на инициализируемый датчик
* @return Истина если всё прошло успешно
*/
bool unitemp_singlewire_deinit(Sensor* sensor);
/**
* @brief Получение данных с датчика по однопроводному интерфейсу DHTxx и AM2xxx
*
* @param sensor Указатель на датчик
* @return Статус опроса
*/
UnitempStatus unitemp_singlewire_update(Sensor* sensor);
/**
* @brief Установить порт датчика
*
* @param sensor Указатель на датчик
* @param gpio Устанавливаемый порт
* @return Истина если всё ок
*/
bool unitemp_singlewire_sensorSetGPIO(Sensor* sensor, const GPIO* gpio);
/**
* @brief Получить порт датчика
*
* @param sensor Указатель на датчик
* @return Указатель на GPIO
*/
const GPIO* unitemp_singlewire_sensorGetGPIO(Sensor* sensor);
/**
* @brief Выделение памяти под датчик на линии One Wire
*
* @param sensor Указатель на датчик
* @param args Указатель на массив с аргументами параметров датчка
*/
bool unitemp_singlewire_alloc(Sensor* sensor, char* args);
/**
* @brief Высвобождение памяти инстанса датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_singlewire_free(Sensor* sensor);
#endif

View file

@ -0,0 +1,106 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "AM2320.h"
#include "../interfaces/I2CSensor.h"
const SensorType AM2320_I2C = {
.typename = "AM2320_I2C",
.altname = "AM2320 (I2C)",
.interface = &I2C,
.datatype = UT_TEMPERATURE | UT_HUMIDITY,
.pollingInterval = 2000,
.allocator = unitemp_AM2320_I2C_alloc,
.mem_releaser = unitemp_AM2320_I2C_free,
.initializer = unitemp_AM2320_init,
.deinitializer = unitemp_AM2320_I2C_deinit,
.updater = unitemp_AM2320_I2C_update};
static uint16_t AM2320_calc_CRC(uint8_t* ptr, uint8_t len) {
uint16_t crc = 0xFFFF;
uint8_t i;
while(len--) {
crc ^= *ptr++;
for(i = 0; i < 8; i++) {
if(crc & 0x01) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
bool unitemp_AM2320_I2C_alloc(Sensor* sensor, char* args) {
UNUSED(args);
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
//Адреса на шине I2C (7 бит)
i2c_sensor->minI2CAdr = 0x5C << 1;
i2c_sensor->maxI2CAdr = 0x5C << 1;
return true;
}
bool unitemp_AM2320_I2C_free(Sensor* sensor) {
//Нечего высвобождать, так как ничего не было выделено
UNUSED(sensor);
return true;
}
bool unitemp_AM2320_init(Sensor* sensor) {
//Нечего инициализировать
UNUSED(sensor);
return true;
}
bool unitemp_AM2320_I2C_deinit(Sensor* sensor) {
//Нечего деинициализировать
UNUSED(sensor);
return true;
}
UnitempStatus unitemp_AM2320_I2C_update(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
uint8_t data[8] = {0x03, 0x00, 0x04};
//Wake up
unitemp_i2c_isDeviceReady(i2c_sensor);
furi_delay_ms(1);
//Запрос
if(!unitemp_i2c_writeArray(i2c_sensor, 3, data)) return UT_SENSORSTATUS_TIMEOUT;
furi_delay_ms(2);
//Ответ
if(!unitemp_i2c_readArray(i2c_sensor, 8, data)) return UT_SENSORSTATUS_TIMEOUT;
if(AM2320_calc_CRC(data, 6) != ((data[7] << 8) | data[6])) {
return UT_SENSORSTATUS_BADCRC;
}
sensor->hum = (float)(((uint16_t)data[2] << 8) | data[3]) / 10;
//Проверка на отрицательность температуры
if(!(data[4] & (1 << 7))) {
sensor->temp = (float)(((uint16_t)data[4] << 8) | data[5]) / 10;
} else {
data[4] &= ~(1 << 7);
sensor->temp = (float)(((uint16_t)data[4] << 8) | data[5]) / -10;
}
return UT_SENSORSTATUS_OK;
}

View file

@ -0,0 +1,62 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_AM2320
#define UNITEMP_AM2320
#include "../unitemp.h"
#include "../Sensors.h"
extern const SensorType AM2320_I2C;
/**
* @brief Выделение памяти и установка начальных значений датчика AM2320
*
* @param sensor Указатель на создаваемый датчик
* @return Истина при успехе
*/
bool unitemp_AM2320_I2C_alloc(Sensor* sensor, char* args);
/**
* @brief Инициализации датчика AM2320
*
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_AM2320_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_AM2320_I2C_deinit(Sensor* sensor);
/**
* @brief Обновление значений из датчика
*
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_AM2320_I2C_update(Sensor* sensor);
/**
* @brief Высвободить память датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_AM2320_I2C_free(Sensor* sensor);
#endif

View file

@ -0,0 +1,352 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "BMx280.h"
const SensorType BMP280 = {
.typename = "BMP280",
.interface = &I2C,
.datatype = UT_TEMPERATURE | UT_PRESSURE,
.pollingInterval = 500,
.allocator = unitemp_BMx280_alloc,
.mem_releaser = unitemp_BMx280_free,
.initializer = unitemp_BMx280_init,
.deinitializer = unitemp_BMx280_deinit,
.updater = unitemp_BMx280_update};
const SensorType BME280 = {
.typename = "BME280",
.interface = &I2C,
.datatype = UT_TEMPERATURE | UT_HUMIDITY | UT_PRESSURE,
.pollingInterval = 500,
.allocator = unitemp_BMx280_alloc,
.mem_releaser = unitemp_BMx280_free,
.initializer = unitemp_BMx280_init,
.deinitializer = unitemp_BMx280_deinit,
.updater = unitemp_BMx280_update};
//Интервал обновления калибровочных значений
#define BOSCH_CAL_UPDATE_INTERVAL 60000
#define TEMP_CAL_START_ADDR 0x88
#define PRESS_CAL_START_ADDR 0x8E
#define HUM_CAL_H1_ADDR 0xA1
#define HUM_CAL_H2_ADDR 0xE1
#define BMP280_ID 0x58
#define BME280_ID 0x60
#define BMx280_I2C_ADDR_MIN (0x76 << 1)
#define BMx280_I2C_ADDR_MAX (0x77 << 1)
#define BMx280_REG_STATUS 0xF3
#define BMx280_REG_CTRL_MEAS 0xF4
#define BMx280_REG_CONFIG 0xF5
#define BME280_REG_CTRL_HUM 0xF2
//Преддескретизация температуры
#define BMx280_TEMP_OVERSAMPLING_SKIP 0b00000000
#define BMx280_TEMP_OVERSAMPLING_1 0b00100000
#define BMx280_TEMP_OVERSAMPLING_2 0b01000000
#define BMx280_TEMP_OVERSAMPLING_4 0b01100000
#define BMx280_TEMP_OVERSAMPLING_8 0b10000000
#define BMx280_TEMP_OVERSAMPLING_16 0b10100000
//Преддескретизация давления
#define BMx280_PRESS_OVERSAMPLING_SKIP 0b00000000
#define BMx280_PRESS_OVERSAMPLING_1 0b00000100
#define BMx280_PRESS_OVERSAMPLING_2 0b00001000
#define BMx280_PRESS_OVERSAMPLING_4 0b00001100
#define BMx280_PRESS_OVERSAMPLING_8 0b00010000
#define BMx280_PRESS_OVERSAMPLING_16 0b00010100
//Преддескретизация влажности
#define BME280_HUM_OVERSAMPLING_SKIP 0b00000000
#define BME280_HUM_OVERSAMPLING_1 0b00000001
#define BME280_HUM_OVERSAMPLING_2 0b00000010
#define BME280_HUM_OVERSAMPLING_4 0b00000011
#define BME280_HUM_OVERSAMPLING_8 0b00000100
#define BME280_HUM_OVERSAMPLING_16 0b00000101u
//Режимы работы датчика
#define BMx280_MODE_SLEEP 0b00000000 //Наелся и спит
#define BMx280_MODE_FORCED 0b00000001 //Обновляет значения 1 раз, после чего уходит в сон
#define BMx280_MODE_NORMAL 0b00000011 //Регулярно обновляет значения
//Период обновления в нормальном режиме
#define BMx280_STANDBY_TIME_0_5 0b00000000
#define BMx280_STANDBY_TIME_62_5 0b00100000
#define BMx280_STANDBY_TIME_125 0b01000000
#define BMx280_STANDBY_TIME_250 0b01100000
#define BMx280_STANDBY_TIME_500 0b10000000
#define BMx280_STANDBY_TIME_1000 0b10100000
#define BMx280_STANDBY_TIME_2000 0b11000000
#define BMx280_STANDBY_TIME_4000 0b11100000
//Коэффициент фильтрации значений
#define BMx280_FILTER_COEFF_1 0b00000000
#define BMx280_FILTER_COEFF_2 0b00000100
#define BMx280_FILTER_COEFF_4 0b00001000
#define BMx280_FILTER_COEFF_8 0b00001100
#define BMx280_FILTER_COEFF_16 0b00010000
//Разрешить работу по SPI
#define BMx280_SPI_3W_ENABLE 0b00000001
#define BMx280_SPI_3W_DISABLE 0b00000000
static float BMx280_compensate_temperature(I2CSensor* i2c_sensor, int32_t adc_T) {
BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance;
int32_t var1, var2;
var1 = ((((adc_T >> 3) - ((int32_t)bmx280_instance->temp_cal.dig_T1 << 1))) *
((int32_t)bmx280_instance->temp_cal.dig_T2)) >>
11;
var2 = (((((adc_T >> 4) - ((int32_t)bmx280_instance->temp_cal.dig_T1)) *
((adc_T >> 4) - ((int32_t)bmx280_instance->temp_cal.dig_T1))) >>
12) *
((int32_t)bmx280_instance->temp_cal.dig_T3)) >>
14;
bmx280_instance->t_fine = var1 + var2;
return ((bmx280_instance->t_fine * 5 + 128) >> 8) / 100.0f;
}
static float BMx280_compensate_pressure(I2CSensor* i2c_sensor, int32_t adc_P) {
BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance;
int32_t var1, var2;
uint32_t p;
var1 = (((int32_t)bmx280_instance->t_fine) >> 1) - (int32_t)64000;
var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((int32_t)bmx280_instance->press_cal.dig_P6);
var2 = var2 + ((var1 * ((int32_t)bmx280_instance->press_cal.dig_P5)) << 1);
var2 = (var2 >> 2) + (((int32_t)bmx280_instance->press_cal.dig_P4) << 16);
var1 = (((bmx280_instance->press_cal.dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) +
((((int32_t)bmx280_instance->press_cal.dig_P2) * var1) >> 1)) >>
18;
var1 = ((((32768 + var1)) * ((int32_t)bmx280_instance->press_cal.dig_P1)) >> 15);
if(var1 == 0) {
return 0; // avoid exception caused by division by zero
}
p = (((uint32_t)(((int32_t)1048576) - adc_P) - (var2 >> 12))) * 3125;
if(p < 0x80000000) {
p = (p << 1) / ((uint32_t)var1);
} else {
p = (p / (uint32_t)var1) * 2;
}
var1 = (((int32_t)bmx280_instance->press_cal.dig_P9) *
((int32_t)(((p >> 3) * (p >> 3)) >> 13))) >>
12;
var2 = (((int32_t)(p >> 2)) * ((int32_t)bmx280_instance->press_cal.dig_P8)) >> 13;
p = (uint32_t)((int32_t)p + ((var1 + var2 + bmx280_instance->press_cal.dig_P7) >> 4));
return p;
}
static float BMx280_compensate_humidity(I2CSensor* i2c_sensor, int32_t adc_H) {
BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance;
int32_t v_x1_u32r;
v_x1_u32r = (bmx280_instance->t_fine - ((int32_t)76800));
v_x1_u32r =
(((((adc_H << 14) - (((int32_t)bmx280_instance->hum_cal.dig_H4) << 20) -
(((int32_t)bmx280_instance->hum_cal.dig_H5) * v_x1_u32r)) +
((int32_t)16384)) >>
15) *
(((((((v_x1_u32r * ((int32_t)bmx280_instance->hum_cal.dig_H6)) >> 10) *
(((v_x1_u32r * ((int32_t)bmx280_instance->hum_cal.dig_H3)) >> 11) +
((int32_t)32768))) >>
10) +
((int32_t)2097152)) *
((int32_t)bmx280_instance->hum_cal.dig_H2) +
8192) >>
14));
v_x1_u32r =
(v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
((int32_t)bmx280_instance->hum_cal.dig_H1)) >>
4));
v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
return ((uint32_t)(v_x1_u32r >> 12)) / 1024.0f;
}
static bool bmx280_readCalValues(I2CSensor* i2c_sensor) {
BMx280_instance* bmx280_instance = (BMx280_instance*)i2c_sensor->sensorInstance;
if(!unitemp_i2c_readRegArray(
i2c_sensor, TEMP_CAL_START_ADDR, 6, (uint8_t*)&bmx280_instance->temp_cal))
return false;
#ifdef UNITEMP_DEBUG
FURI_LOG_D(
APP_NAME,
"Sensor BMx280 (0x%02X) T1-T3: %d, %d, %d",
i2c_sensor->currentI2CAdr,
bmx280_instance->temp_cal.dig_T1,
bmx280_instance->temp_cal.dig_T2,
bmx280_instance->temp_cal.dig_T3);
#endif
if(!unitemp_i2c_readRegArray(
i2c_sensor, PRESS_CAL_START_ADDR, 18, (uint8_t*)&bmx280_instance->press_cal))
return false;
#ifdef UNITEMP_DEBUG
FURI_LOG_D(
APP_NAME,
"Sensor BMx280 (0x%02X): P1-P9: %d, %d, %d, %d, %d, %d, %d, %d, %d",
i2c_sensor->currentI2CAdr,
bmx280_instance->press_cal.dig_P1,
bmx280_instance->press_cal.dig_P2,
bmx280_instance->press_cal.dig_P3,
bmx280_instance->press_cal.dig_P4,
bmx280_instance->press_cal.dig_P5,
bmx280_instance->press_cal.dig_P6,
bmx280_instance->press_cal.dig_P7,
bmx280_instance->press_cal.dig_P8,
bmx280_instance->press_cal.dig_P9);
#endif
if(bmx280_instance->chip_id == BME280_ID) {
uint8_t buff[7] = {0};
if(!unitemp_i2c_readRegArray(i2c_sensor, HUM_CAL_H1_ADDR, 1, buff)) return false;
bmx280_instance->hum_cal.dig_H1 = buff[0];
if(!unitemp_i2c_readRegArray(i2c_sensor, HUM_CAL_H2_ADDR, 7, buff)) return false;
bmx280_instance->hum_cal.dig_H2 = (uint16_t)(buff[0] | ((uint16_t)buff[1] << 8));
bmx280_instance->hum_cal.dig_H3 = buff[2];
bmx280_instance->hum_cal.dig_H4 = ((int16_t)buff[3] << 4) | (buff[4] & 0x0F);
bmx280_instance->hum_cal.dig_H5 = (buff[4] & 0x0F) | ((int16_t)buff[5] << 4);
bmx280_instance->hum_cal.dig_H6 = buff[6];
#ifdef UNITEMP_DEBUG
FURI_LOG_D(
APP_NAME,
"Sensor BMx280 (0x%02X): H1-H6: %d, %d, %d, %d, %d, %d",
i2c_sensor->currentI2CAdr,
bmx280_instance->hum_cal.dig_H1,
bmx280_instance->hum_cal.dig_H2,
bmx280_instance->hum_cal.dig_H3,
bmx280_instance->hum_cal.dig_H4,
bmx280_instance->hum_cal.dig_H5,
bmx280_instance->hum_cal.dig_H6);
#endif
}
bmx280_instance->last_cal_update_time = furi_get_tick();
return true;
}
static bool bmp280_isMeasuring(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
return (bool)((unitemp_i2c_readReg(i2c_sensor, BMx280_REG_STATUS) & 0x08) >> 3);
}
bool unitemp_BMx280_alloc(Sensor* sensor, char* args) {
UNUSED(args);
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
BMx280_instance* bmx280_instance = malloc(sizeof(BMx280_instance));
if(bmx280_instance == NULL) {
FURI_LOG_E(APP_NAME, "Failed to allocation sensor %s instance", sensor->name);
return false;
}
if(sensor->type == &BMP280) bmx280_instance->chip_id = BMP280_ID;
if(sensor->type == &BME280) bmx280_instance->chip_id = BME280_ID;
i2c_sensor->sensorInstance = bmx280_instance;
i2c_sensor->minI2CAdr = BMx280_I2C_ADDR_MIN;
i2c_sensor->maxI2CAdr = BMx280_I2C_ADDR_MAX;
return true;
}
bool unitemp_BMx280_init(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
//Перезагрузка
unitemp_i2c_writeReg(i2c_sensor, 0xE0, 0xB6);
//Чтение ID датчика
uint8_t id = unitemp_i2c_readReg(i2c_sensor, 0xD0);
if(id != BMP280_ID && id != BME280_ID) {
FURI_LOG_E(
APP_NAME,
"Sensor %s returned wrong ID 0x%02X, expected 0x%02X or 0x%02X",
sensor->name,
id,
BMP280_ID,
BME280_ID);
return false;
}
//Настройка режимов работы
if(id == BME280_ID) {
unitemp_i2c_writeReg(i2c_sensor, BME280_REG_CTRL_HUM, BME280_HUM_OVERSAMPLING_1);
unitemp_i2c_writeReg(
i2c_sensor, BME280_REG_CTRL_HUM, unitemp_i2c_readReg(i2c_sensor, BME280_REG_CTRL_HUM));
}
unitemp_i2c_writeReg(
i2c_sensor,
BMx280_REG_CTRL_MEAS,
BMx280_TEMP_OVERSAMPLING_2 | BMx280_PRESS_OVERSAMPLING_4 | BMx280_MODE_NORMAL);
//Настройка периода опроса и фильтрации значений
unitemp_i2c_writeReg(
i2c_sensor,
BMx280_REG_CONFIG,
BMx280_STANDBY_TIME_500 | BMx280_FILTER_COEFF_16 | BMx280_SPI_3W_DISABLE);
//Чтение калибровочных значений
if(!bmx280_readCalValues(i2c_sensor)) {
FURI_LOG_E(APP_NAME, "Failed to read calibration values sensor %s", sensor->name);
return false;
}
return true;
}
bool unitemp_BMx280_deinit(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
//Перевод в сон
unitemp_i2c_writeReg(i2c_sensor, BMx280_REG_CTRL_MEAS, BMx280_MODE_SLEEP);
return true;
}
UnitempStatus unitemp_BMx280_update(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
BMx280_instance* instance = i2c_sensor->sensorInstance;
uint32_t t = furi_get_tick();
uint8_t buff[3];
//Проверка инициализированности датчика
unitemp_i2c_readRegArray(i2c_sensor, 0xF4, 2, buff);
if(buff[0] == 0) {
FURI_LOG_W(APP_NAME, "Sensor %s is not initialized!", sensor->name);
return UT_SENSORSTATUS_ERROR;
}
while(bmp280_isMeasuring(sensor)) {
if(furi_get_tick() - t > 100) {
return UT_SENSORSTATUS_TIMEOUT;
}
}
if(furi_get_tick() - instance->last_cal_update_time > BOSCH_CAL_UPDATE_INTERVAL) {
bmx280_readCalValues(i2c_sensor);
}
if(!unitemp_i2c_readRegArray(i2c_sensor, 0xFA, 3, buff)) return UT_SENSORSTATUS_TIMEOUT;
int32_t adc_T = ((int32_t)buff[0] << 12) | ((int32_t)buff[1] << 4) | ((int32_t)buff[2] >> 4);
if(!unitemp_i2c_readRegArray(i2c_sensor, 0xF7, 3, buff)) return UT_SENSORSTATUS_TIMEOUT;
int32_t adc_P = ((int32_t)buff[0] << 12) | ((int32_t)buff[1] << 4) | ((int32_t)buff[2] >> 4);
if(!unitemp_i2c_readRegArray(i2c_sensor, 0xFD, 2, buff)) return UT_SENSORSTATUS_TIMEOUT;
int32_t adc_H = ((uint16_t)buff[0] << 8) | buff[1];
sensor->temp = BMx280_compensate_temperature(i2c_sensor, adc_T);
sensor->pressure = BMx280_compensate_pressure(i2c_sensor, adc_P);
sensor->hum = BMx280_compensate_humidity(i2c_sensor, adc_H);
return UT_SENSORSTATUS_OK;
}
bool unitemp_BMx280_free(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
free(i2c_sensor->sensorInstance);
return true;
}

View file

@ -0,0 +1,102 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_BMx280
#define UNITEMP_BMx280
#include "../unitemp.h"
#include "../Sensors.h"
#include "../interfaces/I2CSensor.h"
typedef struct {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
} BMx280_temp_cal;
typedef struct {
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
} BMx280_press_cal;
typedef struct {
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
} BMx280_hum_cal;
typedef struct {
//Калибровочные значения температуры
BMx280_temp_cal temp_cal;
//Калибровочные значения давления
BMx280_press_cal press_cal;
//Калибровочные значения влажности воздуха
BMx280_hum_cal hum_cal;
//Время последнего обновления калибровочных значений
uint32_t last_cal_update_time;
//Индификатор датчика
uint8_t chip_id;
//Корректировочное значение температуры
int32_t t_fine;
} BMx280_instance;
extern const SensorType BMP280;
extern const SensorType BME280;
/**
* @brief Выделение памяти и установка начальных значений датчика BMP280
* @param sensor Указатель на создаваемый датчик
* @return Истина при успехе
*/
bool unitemp_BMx280_alloc(Sensor* sensor, char* args);
/**
* @brief Инициализации датчика BMP280
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_BMx280_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
* @param sensor Указатель на датчик
*/
bool unitemp_BMx280_deinit(Sensor* sensor);
/**
* @brief Обновление значений из датчика
* @param sensor Указатель на датчик
* @return Статус опроса датчика
*/
UnitempStatus unitemp_BMx280_update(Sensor* sensor);
/**
* @brief Высвободить память датчика
* @param sensor Указатель на датчик
*/
bool unitemp_BMx280_free(Sensor* sensor);
#endif

View file

@ -0,0 +1,65 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "DHT20.h"
#include "../interfaces/I2CSensor.h"
const SensorType DHT20 = {
.typename = "DHT20",
.altname = "DHT20 (AM2108)",
.interface = &I2C,
.datatype = UT_TEMPERATURE | UT_HUMIDITY,
.pollingInterval = 2000,
.allocator = unitemp_DHT20_I2C_alloc,
.mem_releaser = unitemp_DHT20_I2C_free,
.initializer = unitemp_DHT20_init,
.deinitializer = unitemp_DHT20_I2C_deinit,
.updater = unitemp_DHT20_I2C_update};
bool unitemp_DHT20_I2C_alloc(Sensor* sensor, char* args) {
UNUSED(args);
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
//Адреса на шине I2C (7 бит)
i2c_sensor->minI2CAdr = 0x38 << 1;
i2c_sensor->maxI2CAdr = 0x38 << 1;
return true;
}
bool unitemp_DHT20_I2C_free(Sensor* sensor) {
//Нечего высвобождать, так как ничего не было выделено
UNUSED(sensor);
return true;
}
bool unitemp_DHT20_init(Sensor* sensor) {
//Нечего инициализировать
UNUSED(sensor);
return true;
}
bool unitemp_DHT20_I2C_deinit(Sensor* sensor) {
//Нечего деинициализировать
UNUSED(sensor);
return true;
}
UnitempStatus unitemp_DHT20_I2C_update(Sensor* sensor) {
//I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
UNUSED(sensor);
return UT_SENSORSTATUS_OK;
}

View file

@ -0,0 +1,62 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_DHT20
#define UNITEMP_DHT20
#include "../unitemp.h"
#include "../Sensors.h"
extern const SensorType DHT20;
/**
* @brief Выделение памяти и установка начальных значений датчика DHT20
*
* @param sensor Указатель на создаваемый датчик
* @return Истина при успехе
*/
bool unitemp_DHT20_I2C_alloc(Sensor* sensor, char* args);
/**
* @brief Инициализации датчика DHT20
*
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_DHT20_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_DHT20_I2C_deinit(Sensor* sensor);
/**
* @brief Обновление значений из датчика
*
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_DHT20_I2C_update(Sensor* sensor);
/**
* @brief Высвободить память датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_DHT20_I2C_free(Sensor* sensor);
#endif

View file

@ -0,0 +1,87 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "LM75.h"
#include "../interfaces/I2CSensor.h"
#define LM75_REG_TEMP 0x00
#define LM75_REG_CONFIG 0x01
#define LM75_REG_THYST 0x02
#define LM75_REG_TOS 0x03
#define LM75_CONFIG_SHUTDOWN 0b00000001
#define LM75_CONFIG_INTERRUPT 0b00000010
#define LM75_CONFIG_OSPOLARITY_HIGH 0b00000100
#define LM75_CONFIG_FAULTQUEUE_1 0b00000000
#define LM75_CONFIG_FAULTQUEUE_2 0b00001000
#define LM75_CONFIG_FAULTQUEUE_4 0b00010000
#define LM75_CONFIG_FAULTQUEUE_6 0b00011000
const SensorType LM75 = {
.typename = "LM75",
.interface = &I2C,
.datatype = UT_DATA_TYPE_TEMP,
.pollingInterval = 500,
.allocator = unitemp_LM75_alloc,
.mem_releaser = unitemp_LM75_free,
.initializer = unitemp_LM75_init,
.deinitializer = unitemp_LM75_deinit,
.updater = unitemp_LM75_update};
bool unitemp_LM75_alloc(Sensor* sensor, char* args) {
UNUSED(args);
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
//Адреса на шине I2C (7 бит)
i2c_sensor->minI2CAdr = 0b1001000 << 1;
i2c_sensor->maxI2CAdr = 0b1001111 << 1;
return true;
}
bool unitemp_LM75_free(Sensor* sensor) {
//Нечего высвобождать, так как ничего не было выделено
UNUSED(sensor);
return true;
}
bool unitemp_LM75_init(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
//Выход если не удалось записать значение в датчик
if(!unitemp_i2c_writeReg(i2c_sensor, LM75_REG_CONFIG, LM75_CONFIG_FAULTQUEUE_1)) return false;
return true;
}
bool unitemp_LM75_deinit(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
if(!unitemp_i2c_writeReg(
i2c_sensor, LM75_REG_CONFIG, LM75_CONFIG_FAULTQUEUE_1 | LM75_CONFIG_SHUTDOWN))
return false;
return true;
}
UnitempStatus unitemp_LM75_update(Sensor* sensor) {
I2CSensor* i2c_sensor = (I2CSensor*)sensor->instance;
uint8_t buff[2];
if(!unitemp_i2c_readRegArray(i2c_sensor, LM75_REG_TEMP, 2, buff))
return UT_SENSORSTATUS_TIMEOUT;
int16_t raw = (((uint16_t)buff[0] << 8) | buff[1]);
sensor->temp = raw / 32 * 0.125;
return UT_SENSORSTATUS_OK;
}

View file

@ -0,0 +1,62 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_LM75
#define UNITEMP_LM75
#include "../unitemp.h"
#include "../Sensors.h"
extern const SensorType LM75;
/**
* @brief Выделение памяти и установка начальных значений датчика LM75
*
* @param sensor Указатель на создаваемый датчик
* @return Истина при успехе
*/
bool unitemp_LM75_alloc(Sensor* sensor, char* args);
/**
* @brief Инициализации датчика LM75
*
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_LM75_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_LM75_deinit(Sensor* sensor);
/**
* @brief Обновление значений из датчика
*
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_LM75_update(Sensor* sensor);
/**
* @brief Высвободить память датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_LM75_free(Sensor* sensor);
#endif

Binary file not shown.

View file

@ -0,0 +1,310 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "unitemp.h"
#include "interfaces/SingleWireSensor.h"
#include "Sensors.h"
#include "./views/UnitempViews.h"
#include <furi_hal_power.h>
#include <m-string.h>
/* Переменные */
//Данные приложения
Unitemp* app;
void uintemp_celsiumToFarengate(Sensor* sensor) {
sensor->temp = sensor->temp * (9.0 / 5.0) + 32;
}
void unitemp_pascalToMmHg(Sensor* sensor) {
sensor->pressure = sensor->pressure * 0.007500638;
}
void unitemp_pascalToKPa(Sensor* sensor) {
sensor->pressure = sensor->pressure / 1000.0f;
}
void unitemp_pascalToInHg(Sensor* sensor) {
sensor->pressure = sensor->pressure * 0.0002953007;
}
bool unitemp_saveSettings(void) {
//Выделение памяти для потока
app->file_stream = file_stream_alloc(app->storage);
//Переменная пути к файлу
FuriString* filepath = furi_string_alloc();
//Составление пути к файлу
furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SETTINGS);
//Создание папки плагина
storage_common_mkdir(app->storage, APP_PATH_FOLDER);
//Открытие потока
if(!file_stream_open(
app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
FURI_LOG_E(
APP_NAME,
"An error occurred while saving the settings file: %d",
file_stream_get_error(app->file_stream));
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
return false;
}
//Сохранение настроек
stream_write_format(
app->file_stream, "INFINITY_BACKLIGHT %d\n", app->settings.infinityBacklight);
stream_write_format(app->file_stream, "TEMP_UNIT %d\n", app->settings.temp_unit);
stream_write_format(app->file_stream, "PRESSURE_UNIT %d\n", app->settings.pressure_unit);
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
FURI_LOG_I(APP_NAME, "Settings have been successfully saved");
return true;
}
bool unitemp_loadSettings(void) {
#ifdef UNITEMP_DEBUG
FURI_LOG_D(APP_NAME, "Loading settings...");
#endif
//Выделение памяти на поток
app->file_stream = file_stream_alloc(app->storage);
//Переменная пути к файлу
FuriString* filepath = furi_string_alloc();
//Составление пути к файлу
furi_string_printf(filepath, "%s/%s", APP_PATH_FOLDER, APP_FILENAME_SETTINGS);
//Открытие потока к файлу настроек
if(!file_stream_open(
app->file_stream, furi_string_get_cstr(filepath), FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
//Сохранение настроек по умолчанию в случае отсутствия файла
if(file_stream_get_error(app->file_stream) == FSE_NOT_EXIST) {
FURI_LOG_W(APP_NAME, "Missing settings file. Setting defaults and saving...");
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
//Сохранение стандартного конфига
unitemp_saveSettings();
return false;
} else {
FURI_LOG_E(
APP_NAME,
"An error occurred while loading the settings file: %d. Standard values have been applied",
file_stream_get_error(app->file_stream));
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
return false;
}
}
//Вычисление размера файла
uint8_t file_size = stream_size(app->file_stream);
//Если файл пустой, то:
if(file_size == (uint8_t)0) {
FURI_LOG_W(APP_NAME, "Settings file is empty");
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
//Сохранение стандартного конфига
unitemp_saveSettings();
return false;
}
//Выделение памяти под загрузку файла
uint8_t* file_buf = malloc(file_size);
//Опустошение буфера файла
memset(file_buf, 0, file_size);
//Загрузка файла
if(stream_read(app->file_stream, file_buf, file_size) != file_size) {
//Выход при ошибке чтения
FURI_LOG_E(APP_NAME, "Error reading settings file");
//Закрытие потока и освобождение памяти
file_stream_close(app->file_stream);
stream_free(app->file_stream);
free(file_buf);
return false;
}
//Построчное чтение файла
//Указатель на начало строки
FuriString* file = furi_string_alloc_set_str((char*)file_buf);
//Сколько байт до конца строки
size_t line_end = 0;
while(line_end != STRING_FAILURE && line_end != (size_t)(file_size - 1)) {
char buff[20] = {0};
sscanf(((char*)(file_buf + line_end)), "%s", buff);
if(!strcmp(buff, "INFINITY_BACKLIGHT")) {
//Чтение значения параметра
int p = 0;
sscanf(((char*)(file_buf + line_end)), "INFINITY_BACKLIGHT %d", &p);
app->settings.infinityBacklight = p;
} else if(!strcmp(buff, "TEMP_UNIT")) {
//Чтение значения параметра
int p = 0;
sscanf(((char*)(file_buf + line_end)), "\nTEMP_UNIT %d", &p);
app->settings.temp_unit = p;
} else if(!strcmp(buff, "PRESSURE_UNIT")) {
//Чтение значения параметра
int p = 0;
sscanf(((char*)(file_buf + line_end)), "\nPRESSURE_UNIT %d", &p);
app->settings.pressure_unit = p;
} else {
FURI_LOG_W(APP_NAME, "Unknown settings parameter: %s", buff);
}
//Вычисление конца строки
line_end = furi_string_search_char(file, '\n', line_end + 1);
}
free(file_buf);
file_stream_close(app->file_stream);
stream_free(app->file_stream);
FURI_LOG_I(APP_NAME, "Settings have been successfully loaded");
return true;
}
/**
* @brief Выделение места под переменные плагина
*
* @return true Если всё прошло успешно
* @return false Если в процессе загрузки произошла ошибка
*/
static bool unitemp_alloc(void) {
//Выделение памяти под данные приложения
app = malloc(sizeof(Unitemp));
//Разрешение работы приложения
app->processing = true;
//Открытие хранилища (?)
app->storage = furi_record_open(RECORD_STORAGE);
//Уведомления
app->notifications = furi_record_open(RECORD_NOTIFICATION);
//Установка значений по умолчанию
app->settings.infinityBacklight = true; //Подсветка горит всегда
app->settings.temp_unit = UT_TEMP_CELSIUS; //Единица измерения температуры - градусы Цельсия
app->settings.pressure_unit = UT_PRESSURE_MM_HG; //Единица измерения давления - мм рт. ст.
app->gui = furi_record_open(RECORD_GUI);
//Диспетчер окон
app->view_dispatcher = view_dispatcher_alloc();
app->sensors = NULL;
app->buff = malloc(BUFF_SIZE);
unitemp_General_alloc();
unitemp_MainMenu_alloc();
unitemp_Settings_alloc();
unitemp_SensorsList_alloc();
unitemp_SensorEdit_alloc();
unitemp_SensorNameEdit_alloc();
unitemp_SensorActions_alloc();
unitemp_widgets_alloc();
//Всплывающее окно
app->popup = popup_alloc();
view_dispatcher_add_view(app->view_dispatcher, UnitempViewPopup, popup_get_view(app->popup));
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
return true;
}
/**
* @brief Освыбождение памяти после работы приложения
*/
static void unitemp_free(void) {
popup_free(app->popup);
unitemp_widgets_free();
unitemp_SensorActions_free();
unitemp_SensorNameEdit_free();
unitemp_SensorEdit_free();
unitemp_SensorsList_free();
unitemp_Settings_free();
unitemp_MainMenu_free();
unitemp_General_free();
free(app->buff);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
//Очистка датчиков
//Высвыбождение данных датчиков
unitemp_sensors_free();
free(app->sensors);
//Закрытие уведомлений
furi_record_close(RECORD_NOTIFICATION);
//Закрытие хранилища
furi_record_close(RECORD_STORAGE);
//Удаление в самую последнюю очередь
free(app);
}
/**
* @brief Точка входа в приложение
*
* @return Код ошибки
*/
int32_t unitemp_app() {
//Выделение памяти под переменные
//Выход если произошла ошибка
if(unitemp_alloc() == false) {
//Освобождение памяти
unitemp_free();
//Выход
return 0;
}
//Загрузка настроек из SD-карты
unitemp_loadSettings();
//Применение настроек
if(app->settings.infinityBacklight == true) {
//Постоянное свечение подсветки
notification_message(app->notifications, &sequence_display_backlight_enforce_on);
}
app->settings.lastOTGState = furi_hal_power_is_otg_enabled();
//Загрузка датчиков из SD-карты
unitemp_sensors_load();
//Инициализация датчиков
unitemp_sensors_init();
unitemp_General_switch();
while(app->processing) {
if(app->sensors_ready) unitemp_sensors_updateValues();
furi_delay_ms(100);
}
//Деинициализация датчиков
unitemp_sensors_deInit();
//Автоматическое управление подсветкой
if(app->settings.infinityBacklight == true)
notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
//Освобождение памяти
unitemp_free();
//Выход
return 0;
}

View file

@ -0,0 +1,148 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP
#define UNITEMP
/* Подключение стандартных библиотек */
/* Подключение API Flipper Zero */
//Файловый поток
#include <toolbox/stream/file_stream.h>
//Экран
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/widget.h>
#include <gui/modules/popup.h>
//Уведомления
#include <notification/notification.h>
#include <notification/notification_messages.h>
/* Внутренние библиотеки */
//Интерфейсы подключения датчиков
#include "Sensors.h"
/* Объявление макроподстановок */
//Имя приложения
#define APP_NAME "Unitemp"
//Путь хранения файлов плагина
#define APP_PATH_FOLDER "/ext/unitemp"
//Имя файла с настройками
#define APP_FILENAME_SETTINGS "settings.cfg"
//Имя файла с датчиками
#define APP_FILENAME_SENSORS "sensors.cfg"
//Версия приложения
#define UNITEMP_APP_VER "1.0"
//Размер буффера текста
#define BUFF_SIZE 32
#define UNITEMP_DEBUG
/* Объявление перечислений */
//Единицы измерения температуры
typedef enum { UT_TEMP_CELSIUS, UT_TEMP_FAHRENHEIT, UT_TEMP_COUNT } tempMeasureUnit;
//Единицы измерения давления
typedef enum {
UT_PRESSURE_MM_HG,
UT_PRESSURE_IN_HG,
UT_PRESSURE_KPA,
UT_PRESSURE_COUNT
} pressureMeasureUnit;
/* Объявление структур */
//Настройки плагина
typedef struct {
//Бесконечная работа подсветки
bool infinityBacklight;
//Единица измерения температуры
tempMeasureUnit temp_unit;
//Единица измерения давления
pressureMeasureUnit pressure_unit;
//Последнее состояние OTG
bool lastOTGState;
} UnitempSettings;
//Основная структура плагина
typedef struct {
//Система
bool processing; //Флаг работы приложения. При ложном значении приложение закрывается
bool sensors_ready; //Флаг готовности датчиков к опросу
//Основные настройки
UnitempSettings settings;
//Массив указателей на датчики
Sensor** sensors;
//Количество загруженных датчиков
uint8_t sensors_count;
//SD-карта
Storage* storage; //Хранилище
Stream* file_stream; //Файловый поток
//Экран
Gui* gui;
ViewDispatcher* view_dispatcher;
NotificationApp* notifications;
Widget* widget;
Popup* popup;
//Буффер для различного текста
char* buff;
} Unitemp;
/* Объявление прототипов функций */
/**
* @brief Перевод значения температуры датчика из Цельсия в Фаренгейты
*
* @param sensor Указатель на датчик
*/
void uintemp_celsiumToFarengate(Sensor* sensor);
/**
* @brief Конвертация давления из паскалей в мм рт.ст.
*
* @param sensor Указатель на датчик
*/
void unitemp_pascalToMmHg(Sensor* sensor);
/**
* @brief Конвертация давления из паскалей в килопаскали
*
* @param sensor Указатель на датчик
*/
void unitemp_pascalToKPa(Sensor* sensor);
/**
* @brief Конвертация давления из паскалей в дюйм рт.ст.
*
* @param sensor Указатель на датчик
*/
void unitemp_pascalToInHg(Sensor* sensor);
/**
* @brief Сохранение настроек на SD-карту
*
* @return Истина если сохранение успешное
*/
bool unitemp_saveSettings(void);
/**
* @brief Загрузка настроек с SD-карты
*
* @return Истина если загрузка успешная
*/
bool unitemp_loadSettings(void);
extern Unitemp* app;
#endif

View file

@ -0,0 +1,562 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include "unitemp_icons.h"
#include <assets_icons.h>
static View* view;
typedef enum general_views {
G_NO_SENSORS_VIEW, //Нет датчиков
G_LIST_VIEW, //Вид в ввиде списка
G_CAROUSEL_VIEW, //Карусель
} general_view;
typedef enum carousel_info {
CAROUSEL_VALUES, //Отображение значений датчиков
CAROUSEL_INFO, //Отображение информации о датчике
} carousel_info;
static general_view current_view;
carousel_info carousel_info_selector = CAROUSEL_VALUES;
uint8_t generalview_sensor_index = 0;
static void _draw_temperature(Canvas* canvas, Sensor* sensor, uint8_t x, uint8_t y, Color color) {
//Рисование рамки
canvas_draw_rframe(canvas, x, y, 54, 20, 3);
if(color == ColorBlack) {
canvas_draw_rbox(canvas, x, y, 54, 19, 3);
canvas_invert_color(canvas);
} else {
canvas_draw_rframe(canvas, x, y, 54, 19, 3);
}
int8_t temp_dec = abs((int16_t)(sensor->temp * 10) % 10);
//Рисование иконки
canvas_draw_icon(
canvas,
x + 3,
y + 3,
(app->settings.temp_unit == UT_TEMP_CELSIUS ? &I_temp_C_11x14 : &I_temp_F_11x14));
if((int16_t)sensor->temp == -128 || sensor->status == UT_SENSORSTATUS_TIMEOUT) {
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, x + 27, y + 10, AlignCenter, AlignCenter, "--");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, x + 50, y + 10 + 3, AlignRight, AlignCenter, ". -");
if(color == ColorBlack) canvas_invert_color(canvas);
return;
}
//Целая часть температуры
//Костыль для отображения знака числа меньше 0
uint8_t offset = 0;
if(sensor->temp < 0 && sensor->temp > -1) {
app->buff[0] = '-';
offset = 1;
}
snprintf((char*)(app->buff + offset), BUFF_SIZE, "%d", (int8_t)sensor->temp);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(
canvas,
x + 27 + ((sensor->temp <= -10 || sensor->temp > 99) ? 5 : 0),
y + 10,
AlignCenter,
AlignCenter,
app->buff);
//Печать дробной части температуры в диапазоне от -9 до 99 (когда два знака в числе)
if(sensor->temp > -10 && sensor->temp <= 99) {
uint8_t int_len = canvas_string_width(canvas, app->buff);
snprintf(app->buff, BUFF_SIZE, ".%d", temp_dec);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, x + 27 + int_len / 2 + 2, y + 10 + 7, app->buff);
}
if(color == ColorBlack) canvas_invert_color(canvas);
}
static void _draw_humidity(Canvas* canvas, Sensor* sensor, const uint8_t pos[2]) {
//Рисование рамки
canvas_draw_rframe(canvas, pos[0], pos[1], 54, 20, 3);
canvas_draw_rframe(canvas, pos[0], pos[1], 54, 19, 3);
//Рисование иконки
canvas_draw_icon(canvas, pos[0] + 3, pos[1] + 2, &I_hum_9x15);
//Целая часть влажности
snprintf(app->buff, BUFF_SIZE, "%d", (uint8_t)sensor->hum);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, pos[0] + 27, pos[1] + 10, AlignCenter, AlignCenter, app->buff);
uint8_t int_len = canvas_string_width(canvas, app->buff);
//Единица измерения
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, pos[0] + 27 + int_len / 2 + 4, pos[1] + 10 + 7, "%");
}
static void _draw_pressure(Canvas* canvas, Sensor* sensor) {
const uint8_t x = 29, y = 39;
//Рисование рамки
canvas_draw_rframe(canvas, x, y, 69, 20, 3);
canvas_draw_rframe(canvas, x, y, 69, 19, 3);
//Рисование иконки
canvas_draw_icon(canvas, x + 3, y + 4, &I_pressure_7x13);
int16_t press_int = sensor->pressure;
int8_t press_dec = (int16_t)(sensor->temp * 10) % 10;
//Целая часть давления
snprintf(app->buff, BUFF_SIZE, "%d", press_int);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(
canvas, x + 27 + ((press_int > 99) ? 5 : 0), y + 10, AlignCenter, AlignCenter, app->buff);
//Печать дробной части давления в диапазоне от 0 до 99 (когда два знака в числе)
if(press_int <= 99) {
uint8_t int_len = canvas_string_width(canvas, app->buff);
snprintf(app->buff, BUFF_SIZE, ".%d", press_dec);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, x + 27 + int_len / 2 + 2, y + 10 + 7, app->buff);
}
canvas_set_font(canvas, FontSecondary);
//Единица измерения
if(app->settings.pressure_unit == UT_PRESSURE_MM_HG) {
canvas_draw_icon(canvas, x + 50, y + 2, &I_mm_hg_15x15);
} else if(app->settings.pressure_unit == UT_PRESSURE_IN_HG) {
canvas_draw_icon(canvas, x + 50, y + 2, &I_in_hg_15x15);
} else if(app->settings.pressure_unit == UT_PRESSURE_KPA) {
canvas_draw_str(canvas, x + 52, y + 13, "kPa");
}
}
static void _draw_singleSensor(Canvas* canvas, Sensor* sensor, const uint8_t pos[2], Color color) {
canvas_set_font(canvas, FontPrimary);
const uint8_t max_width = 56;
char sensor_name[12] = {0};
memcpy(sensor_name, sensor->name, 10);
if(canvas_string_width(canvas, sensor_name) > max_width) {
uint8_t i = 10;
while((canvas_string_width(canvas, sensor_name) > max_width - 6) && (i != 0)) {
sensor_name[i--] = '\0';
}
sensor_name[++i] = '.';
sensor_name[++i] = '.';
}
canvas_draw_str_aligned(
canvas, pos[0] + 27, pos[1] + 3, AlignCenter, AlignCenter, sensor_name);
_draw_temperature(canvas, sensor, pos[0], pos[1] + 8, color);
}
static void _draw_view_noSensors(Canvas* canvas) {
canvas_draw_icon(canvas, 7, 17, &I_sherlok_53x45);
//Рисование рамки
canvas_draw_rframe(canvas, 0, 0, 128, 63, 7);
canvas_draw_rframe(canvas, 0, 0, 128, 64, 7);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 63, 10, AlignCenter, AlignCenter, "No sensors found");
canvas_set_font(canvas, FontSecondary);
const uint8_t x = 65, y = 32;
canvas_draw_rframe(canvas, x - 4, y - 11, 54, 33, 3);
canvas_draw_rframe(canvas, x - 4, y - 11, 54, 34, 3);
canvas_draw_str(canvas, x, y, "To add the");
canvas_draw_str(canvas, x, y + 9, "new sensor");
canvas_draw_str(canvas, x, y + 18, "press OK");
canvas_draw_icon(canvas, x + 37, y + 10, &I_Ok_btn_9x9);
}
static void _draw_view_sensorsList(Canvas* canvas) {
//Текущая страница
uint8_t page = generalview_sensor_index / 4;
//Количество датчиков, которые будут отображаться на странице
uint8_t page_sensors_count;
if((unitemp_sensors_getActiveCount() - page * 4) / 4) {
page_sensors_count = 4;
} else {
page_sensors_count = (unitemp_sensors_getActiveCount() - page * 4) % 4;
}
//Количество страниц
uint8_t pages =
unitemp_sensors_getActiveCount() / 4 + (unitemp_sensors_getActiveCount() % 4 ? 1 : 0);
//Стрелка влево
if(page > 0) {
canvas_draw_icon(canvas, 2, 32, &I_ButtonLeft_4x7);
}
//Стрелка вправо
if(pages > 0 && page < pages - 1) {
canvas_draw_icon(canvas, 122, 32, &I_ButtonRight_4x7);
}
const uint8_t value_positions[][4][2] = {
{{36, 18}}, //1 датчик
{{7, 18}, {67, 18}}, //2 датчика
{{7, 3}, {67, 3}, {37, 33}}, //3 датчика
{{7, 3}, {67, 3}, {7, 33}, {67, 33}}}; //4 датчика
//Рисование рамки
canvas_draw_rframe(canvas, 0, 0, 128, 63, 7);
canvas_draw_rframe(canvas, 0, 0, 128, 64, 7);
for(uint8_t i = 0; i < page_sensors_count; i++) {
_draw_singleSensor(
canvas,
unitemp_sensor_getActive(page * 4 + i),
value_positions[page_sensors_count - 1][i],
ColorWhite);
}
}
static void _draw_carousel_values(Canvas* canvas) {
UnitempStatus sensor_status = unitemp_sensor_getActive(generalview_sensor_index)->status;
if(sensor_status == UT_SENSORSTATUS_ERROR || sensor_status == UT_SENSORSTATUS_TIMEOUT) {
const Icon* frames[] = {
&I_flipper_happy_60x38, &I_flipper_happy_2_60x38, &I_flipper_sad_60x38};
canvas_draw_icon(canvas, 34, 23, frames[furi_get_tick() % 2250 / 750]);
canvas_set_font(canvas, FontSecondary);
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SINGLE_WIRE) {
snprintf(
app->buff,
BUFF_SIZE,
"Waiting for module on pin %d",
((SingleWireSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance)
->gpio->num);
}
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &ONE_WIRE) {
snprintf(
app->buff,
BUFF_SIZE,
"Waiting for module on pin %d",
((OneWireSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance)
->bus->gpio->num);
}
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) {
snprintf(app->buff, BUFF_SIZE, "Waiting for module on I2C pins");
}
canvas_draw_str_aligned(canvas, 64, 19, AlignCenter, AlignCenter, app->buff);
return;
}
static const uint8_t temp_positions[3][2] = {{37, 23}, {37, 16}, {9, 16}};
static const uint8_t hum_positions[2][2] = {{37, 38}, {65, 16}};
//Селектор значений для отображения
switch(unitemp_sensor_getActive(generalview_sensor_index)->type->datatype) {
case UT_DATA_TYPE_TEMP:
_draw_temperature(
canvas,
unitemp_sensor_getActive(generalview_sensor_index),
temp_positions[0][0],
temp_positions[0][1],
ColorWhite);
break;
case UT_DATA_TYPE_TEMP_HUM:
_draw_temperature(
canvas,
unitemp_sensor_getActive(generalview_sensor_index),
temp_positions[1][0],
temp_positions[1][1],
ColorWhite);
_draw_humidity(
canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[0]);
break;
case UT_DATA_TYPE_TEMP_PRESS:
_draw_temperature(
canvas,
unitemp_sensor_getActive(generalview_sensor_index),
temp_positions[1][0],
temp_positions[1][1],
ColorWhite);
_draw_pressure(canvas, unitemp_sensor_getActive(generalview_sensor_index));
break;
case UT_DATA_TYPE_TEMP_HUM_PRESS:
_draw_temperature(
canvas,
unitemp_sensor_getActive(generalview_sensor_index),
temp_positions[2][0],
temp_positions[2][1],
ColorWhite);
_draw_humidity(
canvas, unitemp_sensor_getActive(generalview_sensor_index), hum_positions[1]);
_draw_pressure(canvas, unitemp_sensor_getActive(generalview_sensor_index));
break;
}
}
static void _draw_carousel_info(Canvas* canvas) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 23, "Type:");
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &ONE_WIRE) {
OneWireSensor* s = unitemp_sensor_getActive(generalview_sensor_index)->instance;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 35, "GPIO:");
canvas_draw_str(canvas, 10, 47, "ID:");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(
canvas,
41,
23,
unitemp_onewire_sensor_getModel(unitemp_sensor_getActive(generalview_sensor_index)));
canvas_draw_str(canvas, 41, 35, s->bus->gpio->name);
snprintf(
app->buff,
BUFF_SIZE,
"%02X%02X%02X%02X%02X%02X%02X%02X",
s->deviceID[0],
s->deviceID[1],
s->deviceID[2],
s->deviceID[3],
s->deviceID[4],
s->deviceID[5],
s->deviceID[6],
s->deviceID[7]);
canvas_draw_str(canvas, 24, 47, app->buff);
}
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SINGLE_WIRE) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 35, "GPIO:");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(
canvas, 41, 23, unitemp_sensor_getActive(generalview_sensor_index)->type->typename);
canvas_draw_str(
canvas,
41,
35,
((SingleWireSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance)
->gpio->name);
}
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 35, "I2C addr:");
canvas_draw_str(canvas, 10, 46, "SDA pin:");
canvas_draw_str(canvas, 10, 58, "SCL pin:");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(
canvas, 41, 23, unitemp_sensor_getActive(generalview_sensor_index)->type->typename);
snprintf(
app->buff,
BUFF_SIZE,
"0x%02X",
((I2CSensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance)
->currentI2CAdr);
canvas_draw_str(canvas, 57, 35, app->buff);
canvas_draw_str(canvas, 54, 46, "15 (C0)");
canvas_draw_str(canvas, 54, 58, "16 (C1)");
}
}
static void _draw_view_sensorsCarousel(Canvas* canvas) {
//Рисование рамки
canvas_draw_rframe(canvas, 0, 0, 128, 63, 7);
canvas_draw_rframe(canvas, 0, 0, 128, 64, 7);
//Печать имени
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas,
64,
7,
AlignCenter,
AlignCenter,
unitemp_sensor_getActive(generalview_sensor_index)->name);
//Подчёркивание
uint8_t line_len =
canvas_string_width(canvas, unitemp_sensor_getActive(generalview_sensor_index)->name) + 2;
canvas_draw_line(canvas, 64 - line_len / 2, 12, 64 + line_len / 2, 12);
//Стрелка вправо
if(unitemp_sensors_getTypesCount() > 0 &&
generalview_sensor_index < unitemp_sensors_getActiveCount() - 1) {
canvas_draw_icon(canvas, 122, 29, &I_ButtonRight_4x7);
}
//Стрелка влево
if(generalview_sensor_index > 0) {
canvas_draw_icon(canvas, 2, 29, &I_ButtonLeft_4x7);
}
switch(carousel_info_selector) {
case CAROUSEL_VALUES:
_draw_carousel_values(canvas);
break;
case CAROUSEL_INFO:
_draw_carousel_info(canvas);
break;
}
}
static void _draw_callback(Canvas* canvas, void* _model) {
UNUSED(_model);
app->sensors_ready = true;
uint8_t sensors_count = unitemp_sensors_getActiveCount();
if(generalview_sensor_index + 1 > sensors_count) generalview_sensor_index = 0;
if(sensors_count == 0) {
current_view = G_NO_SENSORS_VIEW;
_draw_view_noSensors(canvas);
} else {
if(sensors_count == 1) current_view = G_CAROUSEL_VIEW;
if(current_view == G_NO_SENSORS_VIEW) current_view = G_CAROUSEL_VIEW;
if(current_view == G_LIST_VIEW) _draw_view_sensorsList(canvas);
if(current_view == G_CAROUSEL_VIEW) _draw_view_sensorsCarousel(canvas);
}
}
static bool _input_callback(InputEvent* event, void* context) {
UNUSED(context);
//Обработка короткого нажатия "ок"
if(event->key == InputKeyOk && event->type == InputTypeShort) {
//Меню добавления датчика при их отсутствии
if(current_view == G_NO_SENSORS_VIEW) {
app->sensors_ready = false;
unitemp_SensorsList_switch();
} else if(current_view == G_LIST_VIEW) {
//Переход в главное меню при выключенном селекторе
app->sensors_ready = false;
unitemp_MainMenu_switch();
} else if(current_view == G_CAROUSEL_VIEW) {
app->sensors_ready = false;
unitemp_SensorActions_switch(unitemp_sensor_getActive(generalview_sensor_index));
}
}
//Обработка короткого нажатия "вниз"
if(event->key == InputKeyDown && event->type == InputTypeShort) {
//Переход из значений в информацию в карусели
if(current_view == G_CAROUSEL_VIEW && carousel_info_selector == CAROUSEL_VALUES) {
carousel_info_selector = CAROUSEL_INFO;
return true;
}
//Переход в карусель из списка
if(current_view == G_LIST_VIEW) {
current_view = G_CAROUSEL_VIEW;
return true;
}
}
//Обработка короткого нажатия "вверх"
if(event->key == InputKeyUp && event->type == InputTypeShort) {
//Переход из информации в значения в карусели
if(current_view == G_CAROUSEL_VIEW && carousel_info_selector == CAROUSEL_INFO) {
carousel_info_selector = CAROUSEL_VALUES;
return true;
}
//Переход в список из карусели
if(current_view == G_CAROUSEL_VIEW && carousel_info_selector == CAROUSEL_VALUES &&
unitemp_sensors_getActiveCount() > 1) {
current_view = G_LIST_VIEW;
return true;
}
}
//Обработка короткого нажатия "вправо"
if(event->key == InputKeyRight && event->type == InputTypeShort) {
//Пролистывание карусели вперёд
if(current_view == G_CAROUSEL_VIEW) {
if(++generalview_sensor_index >= unitemp_sensors_getActiveCount()) {
generalview_sensor_index = 0;
if(carousel_info_selector == CAROUSEL_VALUES) current_view = G_LIST_VIEW;
}
return true;
}
//Пролистывание списка вперёд
if(current_view == G_LIST_VIEW) {
generalview_sensor_index += 4;
if(generalview_sensor_index >= unitemp_sensors_getActiveCount()) {
generalview_sensor_index = 0;
current_view = G_CAROUSEL_VIEW;
}
return true;
}
}
//Обработка короткого нажатия "влево"
if(event->key == InputKeyLeft && event->type == InputTypeShort) {
//Пролистывание карусели назад
if(current_view == G_CAROUSEL_VIEW) {
if(--generalview_sensor_index >= unitemp_sensors_getActiveCount()) {
generalview_sensor_index = unitemp_sensors_getActiveCount() - 1;
if(carousel_info_selector == CAROUSEL_VALUES) current_view = G_LIST_VIEW;
}
return true;
}
//Пролистывание списка назад
if(current_view == G_LIST_VIEW) {
generalview_sensor_index -= 4;
if(generalview_sensor_index >= unitemp_sensors_getActiveCount()) {
generalview_sensor_index = unitemp_sensors_getActiveCount() - 1;
current_view = G_CAROUSEL_VIEW;
}
return true;
}
}
//Обработка короткого нажатия "назад"
if(event->key == InputKeyBack && event->type == InputTypeShort) {
//Выход из приложения при карусели или отсутствии датчиков
if(current_view == G_NO_SENSORS_VIEW ||
((current_view == G_CAROUSEL_VIEW) && (carousel_info_selector == CAROUSEL_VALUES))) {
app->processing = false;
return true;
}
//Переключение селектора вида карусели
if((current_view == G_CAROUSEL_VIEW) && (carousel_info_selector != CAROUSEL_VALUES)) {
carousel_info_selector = CAROUSEL_VALUES;
return true;
}
//Переход в карусель из списка
if(current_view == G_LIST_VIEW) {
current_view = G_CAROUSEL_VIEW;
return true;
}
}
return true;
}
void unitemp_General_alloc(void) {
view = view_alloc();
view_set_context(view, app);
view_set_draw_callback(view, _draw_callback);
view_set_input_callback(view, _input_callback);
view_dispatcher_add_view(app->view_dispatcher, UnitempViewGeneral, view);
}
void unitemp_General_switch(void) {
app->sensors_ready = true;
view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewGeneral);
}
void unitemp_General_free(void) {
view_free(view);
}

View file

@ -0,0 +1,99 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/variable_item_list.h>
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
#define VIEW_ID UnitempViewMainMenu
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _exit_callback(void* context) {
UNUSED(context);
//Возврат в общий вид
return UnitempViewGeneral;
}
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void _enter_callback(void* context, uint32_t index) {
UNUSED(context);
if(index == 0) { //Add new sensor
unitemp_SensorsList_switch();
}
if(index == 1) { //Settings
unitemp_Settings_switch();
}
if(index == 2) {
unitemp_widget_help_switch();
}
if(index == 3) {
unitemp_widget_about_switch();
}
}
/**
* @brief Создание списка действий с указанным датчиком
*/
void unitemp_MainMenu_alloc(void) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
variable_item_list_add(variable_item_list, "Add new sensor", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Settings", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Help", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "About", 1, NULL, NULL);
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, _exit_callback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view);
}
void unitemp_MainMenu_switch(void) {
//Обнуление последнего выбранного пункта
variable_item_list_set_selected_item(variable_item_list, 0);
//Переключение в вид
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}
void unitemp_MainMenu_free(void) {
//Очистка списка элементов
variable_item_list_free(variable_item_list);
//Очистка вида
view_free(view);
//Удаление вида после обработки
view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID);
}

View file

@ -0,0 +1,50 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/variable_item_list.h>
#include <stdio.h>
#include <assets_icons.h>
uint32_t _prev_view_id;
#define VIEW_ID UnitempViewPopup
static void _popup_callback(void* context) {
UNUSED(context);
view_dispatcher_switch_to_view(app->view_dispatcher, _prev_view_id);
}
void unitemp_popup(const Icon* icon, char* header, char* message, uint32_t prev_view_id) {
_prev_view_id = prev_view_id;
popup_reset(app->popup);
popup_set_icon(app->popup, 0, 64 - icon_get_height(icon), icon);
popup_set_header(app->popup, header, 64, 6, AlignCenter, AlignCenter);
popup_set_text(
app->popup,
message,
(128 - icon_get_width(icon)) / 2 + icon_get_width(icon),
32,
AlignCenter,
AlignCenter);
popup_set_timeout(app->popup, 5000);
popup_set_callback(app->popup, _popup_callback);
popup_enable_timeout(app->popup);
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}

View file

@ -0,0 +1,125 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/variable_item_list.h>
#include <stdio.h>
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
//Текущий датчик
static Sensor* current_sensor;
typedef enum carousel_info {
CAROUSEL_VALUES, //Отображение значений датчиков
CAROUSEL_INFO, //Отображение информации о датчике
} carousel_info;
extern carousel_info carousel_info_selector;
#define VIEW_ID UnitempViewSensorActions
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _exit_callback(void* context) {
UNUSED(context);
//Возврат предыдущий вид
return UnitempViewGeneral;
}
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void _enter_callback(void* context, uint32_t index) {
UNUSED(context);
switch(index) {
case 0:
carousel_info_selector = CAROUSEL_INFO;
unitemp_General_switch();
return;
case 1:
unitemp_SensorEdit_switch(current_sensor);
break;
case 2:
unitemp_widget_delete_switch(current_sensor);
break;
case 3:
unitemp_SensorsList_switch();
break;
case 4:
unitemp_Settings_switch();
break;
case 5:
unitemp_widget_help_switch();
break;
case 6:
unitemp_widget_about_switch();
break;
}
}
/**
* @brief Создание меню действий с датчиком
*/
void unitemp_SensorActions_alloc(void) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
variable_item_list_add(variable_item_list, "Info", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Edit", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Delete", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Add new sensor", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Settings", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "Help", 1, NULL, NULL);
variable_item_list_add(variable_item_list, "About", 1, NULL, NULL);
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, _exit_callback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view);
}
void unitemp_SensorActions_switch(Sensor* sensor) {
current_sensor = sensor;
//Обнуление последнего выбранного пункта
variable_item_list_set_selected_item(variable_item_list, 0);
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}
void unitemp_SensorActions_free(void) {
//Очистка списка элементов
variable_item_list_free(variable_item_list);
//Очистка вида
view_free(view);
//Удаление вида после обработки
view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID);
}

View file

@ -0,0 +1,376 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/variable_item_list.h>
#include "../interfaces/SingleWireSensor.h"
#include "../interfaces/OneWireSensor.h"
#include "../interfaces/I2CSensor.h"
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
//Текущий редактируемый датчик
static Sensor* editable_sensor;
//Изначальный GPIO датчика
static const GPIO* initial_gpio = NULL;
//Элемент списка - имя датчика
static VariableItem* sensor_name_item;
//Элемент списка - адрес датчика one wire
static VariableItem* onewire_addr_item;
//Элемент списка - адрес датчика one wire
static VariableItem* onewire_type_item;
//Элемент списка - смещение температуры
VariableItem* temp_offset_item;
#define OFFSET_BUFF_SIZE 5
//Буффер для текста смещения
static char* offset_buff;
extern uint8_t generalview_sensor_index;
#define VIEW_ID UnitempViewSensorEdit
bool _onewire_id_exist(uint8_t* id) {
if(id == NULL) return false;
for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) {
if(unitemp_sensor_getActive(i)->type == &Dallas) {
if(unitemp_onewire_id_compare(
id, ((OneWireSensor*)(unitemp_sensor_getActive(i)->instance))->deviceID)) {
return true;
}
}
}
return false;
}
static void _onewire_scan(void) {
OneWireSensor* ow_sensor = editable_sensor->instance;
#ifdef UNITEMP_DEBUG
FURI_LOG_D(
APP_NAME,
"devices on wire %d: %d",
ow_sensor->bus->gpio->num,
ow_sensor->bus->device_count);
#endif
//Сканирование шины one wire
unitemp_onewire_bus_init(ow_sensor->bus);
uint8_t* id = NULL;
do {
id = unitemp_onewire_bus_enum_next(ow_sensor->bus);
} while(_onewire_id_exist(id));
if(id == NULL) {
unitemp_onewire_bus_enum_init();
id = unitemp_onewire_bus_enum_next(ow_sensor->bus);
if(_onewire_id_exist(id)) {
do {
id = unitemp_onewire_bus_enum_next(ow_sensor->bus);
} while(_onewire_id_exist(id) && id != NULL);
}
if(id == NULL) {
memset(ow_sensor->deviceID, 0, 8);
ow_sensor->familyCode = 0;
unitemp_onewire_bus_deinit(ow_sensor->bus);
variable_item_set_current_value_text(onewire_addr_item, "empty");
variable_item_set_current_value_text(
onewire_type_item, unitemp_onewire_sensor_getModel(editable_sensor));
return;
}
}
unitemp_onewire_bus_deinit(ow_sensor->bus);
memcpy(ow_sensor->deviceID, id, 8);
ow_sensor->familyCode = id[0];
#ifdef UNITEMP_DEBUG
FURI_LOG_D(
APP_NAME,
"Found sensor's ID: %02X%02X%02X%02X%02X%02X%02X%02X",
id[0],
id[1],
id[2],
id[3],
id[4],
id[5],
id[6],
id[7]);
#endif
if(ow_sensor->familyCode != 0) {
char id_buff[10];
snprintf(
id_buff,
10,
"%02X%02X%02X",
ow_sensor->deviceID[1],
ow_sensor->deviceID[2],
ow_sensor->deviceID[3]);
//А больше не лезет(
variable_item_set_current_value_text(onewire_addr_item, id_buff);
} else {
variable_item_set_current_value_text(onewire_addr_item, "empty");
}
variable_item_set_current_value_text(
onewire_type_item, unitemp_onewire_sensor_getModel(editable_sensor));
}
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _exit_callback(void* context) {
UNUSED(context);
editable_sensor->status = UT_SENSORSTATUS_TIMEOUT;
if(!unitemp_sensor_isContains(editable_sensor)) unitemp_sensor_free(editable_sensor);
unitemp_sensors_reload();
//Возврат предыдущий вид
return UnitempViewGeneral;
}
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void _enter_callback(void* context, uint32_t index) {
UNUSED(context);
//Смена имени
if(index == 0) {
unitemp_SensorNameEdit_switch(editable_sensor);
}
//Сохранение
if((index == 4 && editable_sensor->type->interface != &ONE_WIRE) ||
(index == 5 && editable_sensor->type->interface == &ONE_WIRE)) {
//Выход если датчик one wire не имеет ID
if(editable_sensor->type->interface == &ONE_WIRE &&
((OneWireSensor*)(editable_sensor->instance))->familyCode == 0) {
return;
}
if(initial_gpio != NULL) {
unitemp_gpio_unlock(initial_gpio);
initial_gpio = NULL;
}
editable_sensor->status = UT_SENSORSTATUS_TIMEOUT;
if(!unitemp_sensor_isContains(editable_sensor)) unitemp_sensors_add(editable_sensor);
unitemp_sensors_save();
unitemp_sensors_reload();
generalview_sensor_index = unitemp_sensors_getActiveCount() - 1;
unitemp_General_switch();
}
//Адрес устройства на шине one wire
if(index == 4 && editable_sensor->type->interface == &ONE_WIRE) {
_onewire_scan();
}
}
/**
* @brief Функция обработки изменения значения GPIO
*
* @param item Указатель на элемент списка
*/
static void _gpio_change_callback(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
if(editable_sensor->type->interface == &SINGLE_WIRE) {
SingleWireSensor* instance = editable_sensor->instance;
instance->gpio =
unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, initial_gpio);
variable_item_set_current_value_text(item, instance->gpio->name);
}
if(editable_sensor->type->interface == &ONE_WIRE) {
OneWireSensor* instance = editable_sensor->instance;
instance->bus->gpio =
unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, NULL);
variable_item_set_current_value_text(item, instance->bus->gpio->name);
}
}
/**
* @brief Функция обработки изменения значения GPIO
*
* @param item Указатель на элемент списка
*/
static void _i2caddr_change_callback(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
((I2CSensor*)editable_sensor->instance)->currentI2CAdr =
((I2CSensor*)editable_sensor->instance)->minI2CAdr + index;
char buff[5];
snprintf(buff, 5, "0x%2X", ((I2CSensor*)editable_sensor->instance)->currentI2CAdr);
variable_item_set_current_value_text(item, buff);
}
/**
* @brief Функция обработки изменения значения имени датчика
*
* @param item Указатель на элемент списка
*/
static void _name_change_callback(VariableItem* item) {
variable_item_set_current_value_index(item, 0);
unitemp_SensorNameEdit_switch(editable_sensor);
}
/**
* @brief Функция обработки изменения значения адреса датчика one wire
*
* @param item Указатель на элемент списка
*/
static void _onwire_addr_change_callback(VariableItem* item) {
variable_item_set_current_value_index(item, 0);
_onewire_scan();
}
/**
* @brief Функция обработки изменения значения смещения температуры
*
* @param item Указатель на элемент списка
*/
static void _offset_change_callback(VariableItem* item) {
editable_sensor->temp_offset = variable_item_get_current_value_index(item) - 20;
snprintf(
offset_buff, OFFSET_BUFF_SIZE, "%+1.1f", (double)(editable_sensor->temp_offset / 10.0));
variable_item_set_current_value_text(item, offset_buff);
}
/**
* @brief Создание меню редактирования датчка
*/
void unitemp_SensorEdit_alloc(void) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, _exit_callback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view);
offset_buff = malloc(OFFSET_BUFF_SIZE);
}
void unitemp_SensorEdit_switch(Sensor* sensor) {
editable_sensor = sensor;
editable_sensor->status = UT_SENSORSTATUS_INACTIVE;
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
//Обнуление последнего выбранного пункта
variable_item_list_set_selected_item(variable_item_list, 0);
//Имя датчика
sensor_name_item = variable_item_list_add(
variable_item_list, "Name", strlen(sensor->name) > 7 ? 1 : 2, _name_change_callback, NULL);
variable_item_set_current_value_index(sensor_name_item, 0);
variable_item_set_current_value_text(sensor_name_item, sensor->name);
//Тип датчика (не редактируется)
onewire_type_item = variable_item_list_add(variable_item_list, "Type", 1, NULL, NULL);
variable_item_set_current_value_index(onewire_type_item, 0);
variable_item_set_current_value_text(
onewire_type_item,
(sensor->type->interface == &ONE_WIRE ? unitemp_onewire_sensor_getModel(editable_sensor) :
sensor->type->typename));
//Смещение температуры
temp_offset_item = variable_item_list_add(
variable_item_list, "Temp. offset", 41, _offset_change_callback, NULL);
variable_item_set_current_value_index(temp_offset_item, sensor->temp_offset + 20);
snprintf(
offset_buff, OFFSET_BUFF_SIZE, "%+1.1f", (double)(editable_sensor->temp_offset / 10.0));
variable_item_set_current_value_text(temp_offset_item, offset_buff);
//Порт подключения датчка (для one wire и single wire)
if(sensor->type->interface == &ONE_WIRE || sensor->type->interface == &SINGLE_WIRE) {
if(sensor->type->interface == &ONE_WIRE) {
initial_gpio = ((OneWireSensor*)editable_sensor->instance)->bus->gpio;
} else {
initial_gpio = ((SingleWireSensor*)editable_sensor->instance)->gpio;
}
uint8_t aviable_gpio_count =
unitemp_gpio_getAviablePortsCount(sensor->type->interface, initial_gpio);
VariableItem* item = variable_item_list_add(
variable_item_list, "GPIO", aviable_gpio_count, _gpio_change_callback, app);
uint8_t gpio_index = 0;
if(unitemp_sensor_isContains(editable_sensor)) {
for(uint8_t i = 0; i < aviable_gpio_count; i++) {
if(unitemp_gpio_getAviablePort(sensor->type->interface, i, initial_gpio) ==
initial_gpio) {
gpio_index = i;
break;
}
}
}
variable_item_set_current_value_index(item, gpio_index);
variable_item_set_current_value_text(
item,
unitemp_gpio_getAviablePort(sensor->type->interface, gpio_index, initial_gpio)->name);
}
//Адрес устройства на шине I2C (для датчиков I2C)
if(sensor->type->interface == &I2C) {
VariableItem* item = variable_item_list_add(
variable_item_list,
"I2C address",
((I2CSensor*)sensor->instance)->maxI2CAdr - ((I2CSensor*)sensor->instance)->minI2CAdr +
1,
_i2caddr_change_callback,
app);
snprintf(app->buff, 5, "0x%2X", ((I2CSensor*)sensor->instance)->currentI2CAdr);
variable_item_set_current_value_text(item, app->buff);
}
//Адрес устройства на шине one wire (для датчиков one wire)
if(sensor->type->interface == &ONE_WIRE) {
onewire_addr_item = variable_item_list_add(
variable_item_list, "Address", 2, _onwire_addr_change_callback, NULL);
OneWireSensor* ow_sensor = sensor->instance;
if(ow_sensor->familyCode == 0) {
variable_item_set_current_value_text(onewire_addr_item, "Scan");
} else {
snprintf(
app->buff,
10,
"%02X%02X%02X",
ow_sensor->deviceID[1],
ow_sensor->deviceID[2],
ow_sensor->deviceID[3]);
variable_item_set_current_value_text(onewire_addr_item, app->buff);
}
}
variable_item_list_add(variable_item_list, "Save", 1, NULL, NULL);
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}
void unitemp_SensorEdit_free(void) {
//Очистка списка элементов
variable_item_list_free(variable_item_list);
//Очистка вида
view_free(view);
//Удаление вида после обработки
view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID);
free(offset_buff);
}

View file

@ -0,0 +1,46 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/text_input.h>
//Окно ввода текста
static TextInput* text_input;
//Текущий редактируемый датчик
static Sensor* editable_sensor;
#define VIEW_ID UnitempViewSensorNameEdit
static void _sensor_name_changed_callback(void* context) {
UNUSED(context);
unitemp_SensorEdit_switch(editable_sensor);
}
void unitemp_SensorNameEdit_alloc(void) {
text_input = text_input_alloc();
view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, text_input_get_view(text_input));
text_input_set_header_text(text_input, "Sensor name");
}
void unitemp_SensorNameEdit_switch(Sensor* sensor) {
editable_sensor = sensor;
text_input_set_result_callback(
text_input, _sensor_name_changed_callback, app, sensor->name, 11, true);
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}
void unitemp_SensorNameEdit_free(void) {
text_input_free(text_input);
}

View file

@ -0,0 +1,162 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/variable_item_list.h>
#include <stdio.h>
#include <assets_icons.h>
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
#define VIEW_ID UnitempViewSensorsList
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _exit_callback(void* context) {
UNUSED(context);
//Возврат предыдущий вид
return UnitempViewGeneral;
}
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void _enter_callback(void* context, uint32_t index) {
UNUSED(context);
if(index == unitemp_sensors_getTypesCount()) {
unitemp_widget_help_switch();
return;
}
const SensorType* type = unitemp_sensors_getTypes()[index];
uint8_t sensor_type_count = 0;
//Подсчёт имеющихся датчиков данного типа
for(uint8_t i = 0; i < unitemp_sensors_getActiveCount(); i++) {
if(unitemp_sensor_getActive(i)->type == type) {
sensor_type_count++;
}
}
//Имя датчка
char sensor_name[11];
//Добавление счётчика к имени если такой датчик имеется
if(sensor_type_count == 0)
snprintf(sensor_name, 11, "%s", type->typename);
else
snprintf(sensor_name, 11, "%s_%d", type->typename, sensor_type_count);
char args[22] = {0};
//Проверка доступности датчика
if(unitemp_gpio_getAviablePort(type->interface, 0, NULL) == NULL) {
if(type->interface == &SINGLE_WIRE || type->interface == &ONE_WIRE) {
unitemp_popup(
&I_Cry_dolph_55x52, "Sensor is unavailable", "All GPIOs\nare busy", VIEW_ID);
}
if(type->interface == &I2C) {
unitemp_popup(
&I_Cry_dolph_55x52, "Sensor is unavailable", "GPIOs 15 or 16\nare busy", VIEW_ID);
}
return;
}
//Выбор первого доступного порта для датчика single wire
if(type->interface == &SINGLE_WIRE) {
snprintf(
args,
4,
"%d",
unitemp_gpio_toInt(unitemp_gpio_getAviablePort(type->interface, 0, NULL)));
}
//Выбор первого доступного порта для датчика one wire и запись нулевого ID
if(type->interface == &ONE_WIRE) {
snprintf(
args,
21,
"%d %02X%02X%02X%02X%02X%02X%02X%02X",
unitemp_gpio_toInt(unitemp_gpio_getAviablePort(type->interface, 0, NULL)),
0,
0,
0,
0,
0,
0,
0,
0);
}
//Для I2C адрес выберется автоматически
unitemp_SensorEdit_switch(unitemp_sensor_alloc(sensor_name, type, args));
}
/**
* @brief Создание меню редактирования настроек
*/
void unitemp_SensorsList_alloc(void) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
//Добавление в список доступных датчиков
for(uint8_t i = 0; i < unitemp_sensors_getTypesCount(); i++) {
if(unitemp_sensors_getTypes()[i]->altname == NULL) {
variable_item_list_add(
variable_item_list, unitemp_sensors_getTypes()[i]->typename, 1, NULL, app);
} else {
variable_item_list_add(
variable_item_list, unitemp_sensors_getTypes()[i]->altname, 1, NULL, app);
}
}
variable_item_list_add(variable_item_list, "I don't know what to choose", 1, NULL, app);
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, _exit_callback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view);
}
void unitemp_SensorsList_switch(void) {
//Обнуление последнего выбранного пункта
variable_item_list_set_selected_item(variable_item_list, 0);
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}
void unitemp_SensorsList_free(void) {
//Очистка списка элементов
variable_item_list_free(variable_item_list);
//Очистка вида
view_free(view);
//Удаление вида после обработки
view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID);
}

View file

@ -0,0 +1,152 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include <gui/modules/variable_item_list.h>
//Текущий вид
static View* view;
//Список
static VariableItemList* variable_item_list;
static const char states[2][9] = {"Auto", "Infinity"};
static const char temp_units[UT_TEMP_COUNT][3] = {"*C", "*F"};
static const char pressure_units[UT_PRESSURE_COUNT][6] = {"mm Hg", "in Hg", "kPa"};
//Элемент списка - бесконечная подсветка
VariableItem* infinity_backlight_item;
//Единица измерения температуры
VariableItem* temperature_unit_item;
//Единица измерения давления
VariableItem* pressure_unit_item;
#define VIEW_ID UnitempViewSettings
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _exit_callback(void* context) {
UNUSED(context);
//Костыль с зависающей подсветкой
if((bool)variable_item_get_current_value_index(infinity_backlight_item) !=
app->settings.infinityBacklight) {
if((bool)variable_item_get_current_value_index(infinity_backlight_item)) {
notification_message(app->notifications, &sequence_display_backlight_enforce_on);
} else {
notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
}
}
app->settings.infinityBacklight =
(bool)variable_item_get_current_value_index(infinity_backlight_item);
app->settings.temp_unit = variable_item_get_current_value_index(temperature_unit_item);
app->settings.pressure_unit = variable_item_get_current_value_index(pressure_unit_item);
unitemp_saveSettings();
unitemp_loadSettings();
//Возврат предыдущий вид
return UnitempViewMainMenu;
}
/**
* @brief Функция обработки нажатия средней кнопки
*
* @param context Указатель на данные приложения
* @param index На каком элементе списка была нажата кнопка
*/
static void _enter_callback(void* context, uint32_t index) {
UNUSED(context);
UNUSED(index);
}
static void _setting_change_callback(VariableItem* item) {
if(item == infinity_backlight_item) {
variable_item_set_current_value_text(
infinity_backlight_item,
states[variable_item_get_current_value_index(infinity_backlight_item)]);
}
if(item == temperature_unit_item) {
variable_item_set_current_value_text(
temperature_unit_item,
temp_units[variable_item_get_current_value_index(temperature_unit_item)]);
}
if(item == pressure_unit_item) {
variable_item_set_current_value_text(
pressure_unit_item,
pressure_units[variable_item_get_current_value_index(pressure_unit_item)]);
}
}
/**
* @brief Создание меню редактирования настроек
*/
void unitemp_Settings_alloc(void) {
variable_item_list = variable_item_list_alloc();
//Сброс всех элементов меню
variable_item_list_reset(variable_item_list);
infinity_backlight_item = variable_item_list_add(
variable_item_list, "Backlight time", UT_TEMP_COUNT, _setting_change_callback, app);
temperature_unit_item =
variable_item_list_add(variable_item_list, "Temp. unit", 2, _setting_change_callback, app);
pressure_unit_item = variable_item_list_add(
variable_item_list, "Press. unit", UT_PRESSURE_COUNT, _setting_change_callback, app);
//Добавление колбека на нажатие средней кнопки
variable_item_list_set_enter_callback(variable_item_list, _enter_callback, app);
//Создание вида из списка
view = variable_item_list_get_view(variable_item_list);
//Добавление колбека на нажатие кнопки "Назад"
view_set_previous_callback(view, _exit_callback);
//Добавление вида в диспетчер
view_dispatcher_add_view(app->view_dispatcher, VIEW_ID, view);
}
void unitemp_Settings_switch(void) {
//Обнуление последнего выбранного пункта
variable_item_list_set_selected_item(variable_item_list, 0);
variable_item_set_current_value_index(
infinity_backlight_item, (uint8_t)app->settings.infinityBacklight);
variable_item_set_current_value_text(
infinity_backlight_item,
states[variable_item_get_current_value_index(infinity_backlight_item)]);
variable_item_set_current_value_index(temperature_unit_item, (uint8_t)app->settings.temp_unit);
variable_item_set_current_value_text(
temperature_unit_item,
temp_units[variable_item_get_current_value_index(temperature_unit_item)]);
variable_item_set_current_value_index(
pressure_unit_item, (uint8_t)app->settings.pressure_unit);
variable_item_set_current_value_text(
pressure_unit_item,
pressure_units[variable_item_get_current_value_index(pressure_unit_item)]);
view_dispatcher_switch_to_view(app->view_dispatcher, VIEW_ID);
}
void unitemp_Settings_free(void) {
//Очистка списка элементов
variable_item_list_free(variable_item_list);
//Очистка вида
view_free(view);
//Удаление вида после обработки
view_dispatcher_remove_view(app->view_dispatcher, VIEW_ID);
}

View file

@ -0,0 +1,94 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#ifndef UNITEMP_SCENES
#define UNITEMP_SCENES
#include "../unitemp.h"
//Виды менюшек
typedef enum UnitempViews {
UnitempViewGeneral,
UnitempViewMainMenu,
UnitempViewSettings,
UnitempViewSensorsList,
UnitempViewSensorEdit,
UnitempViewSensorNameEdit,
UnitempViewSensorActions,
UnitempViewWidget,
UnitempViewPopup,
UnitempViewsCount
} UnitempViews;
/**
* @brief Вывести всплывающее окно
*
* @param icon Указатель на иконку
* @param header Заголовок
* @param message Сообщение
* @param prev_view_id ID вида куда в который нужно вернуться
*/
void unitemp_popup(const Icon* icon, char* header, char* message, uint32_t prev_view_id);
/* Общий вид на датчики */
void unitemp_General_alloc(void);
void unitemp_General_switch(void);
void unitemp_General_free(void);
/* Главное меню */
void unitemp_MainMenu_alloc(void);
void unitemp_MainMenu_switch(void);
void unitemp_MainMenu_free(void);
/* Настройки */
void unitemp_Settings_alloc(void);
void unitemp_Settings_switch(void);
void unitemp_Settings_free(void);
/* Список датчиков */
void unitemp_SensorsList_alloc(void);
void unitemp_SensorsList_switch(void);
void unitemp_SensorsList_free(void);
/* Редактор датчка */
void unitemp_SensorEdit_alloc(void);
//sensor - указатель на редактируемый датчик
void unitemp_SensorEdit_switch(Sensor* sensor);
void unitemp_SensorEdit_free(void);
/* Редактор имени датчика */
void unitemp_SensorNameEdit_alloc(void);
void unitemp_SensorNameEdit_switch(Sensor* sensor);
void unitemp_SensorNameEdit_free(void);
/* Список действий с датчиком */
void unitemp_SensorActions_alloc(void);
void unitemp_SensorActions_switch(Sensor* sensor);
void unitemp_SensorActions_free(void);
/* Виджеты */
void unitemp_widgets_alloc(void);
void unitemp_widgets_free(void);
/* Подтверждение удаления */
void unitemp_widget_delete_switch(Sensor* sensor);
/* Помощь */
void unitemp_widget_help_switch(void);
/* О приложении */
void unitemp_widget_about_switch(void);
#endif

View file

@ -0,0 +1,204 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022 Victor Nikitchuk (https://github.com/quen0n)
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/>.
*/
#include "UnitempViews.h"
#include "unitemp_icons.h"
#include <assets_icons.h>
void unitemp_widgets_alloc(void) {
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, UnitempViewWidget, widget_get_view(app->widget));
}
void unitemp_widgets_free(void) {
widget_free(app->widget);
}
/* ================== Подтверждение удаления ================== */
Sensor* current_sensor;
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _delete_exit_callback(void* context) {
UNUSED(context);
//Возвращаем ID вида, в который нужно вернуться
return UnitempViewSensorActions;
}
/**
* @brief Обработчик нажатий на кнопку в виджете
*
* @param result Какая из кнопок была нажата
* @param type Тип нажатия
* @param context Указатель на данные плагина
*/
static void _delete_click_callback(GuiButtonType result, InputType type, void* context) {
UNUSED(context);
//Коротко нажата левая кнопка (Cancel)
if(result == GuiButtonTypeLeft && type == InputTypeShort) {
unitemp_SensorActions_switch(current_sensor);
}
//Коротко нажата правая кнопка (Delete)
if(result == GuiButtonTypeRight && type == InputTypeShort) {
//Удаление датчика
unitemp_sensor_delete(current_sensor);
//Выход из меню
unitemp_General_switch();
}
}
/**
* @brief Переключение в виджет удаления датчика
*/
void unitemp_widget_delete_switch(Sensor* sensor) {
current_sensor = sensor;
//Очистка виджета
widget_reset(app->widget);
//Добавление кнопок
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Cancel", _delete_click_callback, app);
widget_add_button_element(
app->widget, GuiButtonTypeRight, "Delete", _delete_click_callback, app);
snprintf(app->buff, BUFF_SIZE, "\e#Delete %s?\e#", current_sensor->name);
widget_add_text_box_element(
app->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, app->buff, false);
if(current_sensor->type->interface == &ONE_WIRE) {
OneWireSensor* s = current_sensor->instance;
snprintf(
app->buff,
BUFF_SIZE,
"\e#Type:\e# %s",
unitemp_onewire_sensor_getModel(current_sensor));
widget_add_text_box_element(
app->widget, 0, 16, 128, 23, AlignLeft, AlignTop, app->buff, false);
snprintf(app->buff, BUFF_SIZE, "\e#GPIO:\e# %s", s->bus->gpio->name);
widget_add_text_box_element(
app->widget, 0, 28, 128, 23, AlignLeft, AlignTop, app->buff, false);
snprintf(
app->buff,
BUFF_SIZE,
"\e#ID:\e# %02X%02X%02X%02X%02X%02X%02X%02X",
s->deviceID[0],
s->deviceID[1],
s->deviceID[2],
s->deviceID[3],
s->deviceID[4],
s->deviceID[5],
s->deviceID[6],
s->deviceID[7]);
widget_add_text_box_element(
app->widget, 0, 40, 128, 23, AlignLeft, AlignTop, app->buff, false);
}
if(current_sensor->type->interface == &SINGLE_WIRE) {
snprintf(app->buff, BUFF_SIZE, "\e#Type:\e# %s", current_sensor->type->typename);
widget_add_text_box_element(
app->widget, 0, 16, 128, 23, AlignLeft, AlignTop, app->buff, false);
snprintf(
app->buff,
BUFF_SIZE,
"\e#GPIO:\e# %s",
((SingleWireSensor*)current_sensor->instance)->gpio->name);
widget_add_text_box_element(
app->widget, 0, 28, 128, 23, AlignLeft, AlignTop, app->buff, false);
}
if(current_sensor->type->interface == &I2C) {
snprintf(app->buff, BUFF_SIZE, "\e#Type:\e# %s", current_sensor->type->typename);
widget_add_text_box_element(
app->widget, 0, 16, 128, 23, AlignLeft, AlignTop, app->buff, false);
snprintf(
app->buff,
BUFF_SIZE,
"\e#I2C addr:\e# 0x%02X",
((I2CSensor*)current_sensor->instance)->currentI2CAdr);
widget_add_text_box_element(
app->widget, 0, 28, 128, 23, AlignLeft, AlignTop, app->buff, false);
}
view_set_previous_callback(widget_get_view(app->widget), _delete_exit_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewWidget);
}
/* ========================== Помощь ========================== */
/**
* @brief Функция обработки нажатия кнопки "Назад"
*
* @param context Указатель на данные приложения
* @return ID вида в который нужно переключиться
*/
static uint32_t _help_exit_callback(void* context) {
UNUSED(context);
//Возвращаем ID вида, в который нужно вернуться
return UnitempViewGeneral;
}
/**
* @brief Переключение в виджет помощи
*/
void unitemp_widget_help_switch(void) {
//Очистка виджета
widget_reset(app->widget);
widget_add_icon_element(app->widget, 3, 7, &I_repo_qr_50x50);
widget_add_icon_element(app->widget, 71, 15, &I_DolphinCommon_56x48);
widget_add_string_multiline_element(
app->widget, 55, 5, AlignLeft, AlignTop, FontSecondary, "You can find help\nthere");
widget_add_frame_element(app->widget, 0, 0, 128, 63, 7);
widget_add_frame_element(app->widget, 0, 0, 128, 64, 7);
view_set_previous_callback(widget_get_view(app->widget), _help_exit_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewWidget);
}
/* ========================== О приложении ========================== */
/**
* @brief Переключение в виджет о приложении
*/
void unitemp_widget_about_switch(void) {
//Очистка виджета
widget_reset(app->widget);
widget_add_frame_element(app->widget, 0, 0, 128, 63, 7);
widget_add_frame_element(app->widget, 0, 0, 128, 64, 7);
snprintf(app->buff, BUFF_SIZE, "#Unitemp %s#", UNITEMP_APP_VER);
widget_add_text_box_element(
app->widget, 0, 4, 128, 12, AlignCenter, AlignCenter, app->buff, false);
widget_add_text_scroll_element(
app->widget,
4,
16,
121,
44,
"Universal plugin for viewing the values of temperature\nsensors\n\e#Author: Quenon\ngithub.com/quen0n\n\e#Designer: Svaarich\ngithub.com/Svaarich\n\e#Issues & suggestions\ntiny.one/unitemp");
view_set_previous_callback(widget_get_view(app->widget), _help_exit_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, UnitempViewWidget);
}