Support for PC platform (#12)
* improvements for cross-platform compiling * moved tactility-core to libs/ * splitup improvements * remove git/gitmodules from freertos * better platformbetter platform checks * added build scripts * delete mbedtls * re-add mbedtls * fixes and improvements * added pc build * simplify build scripts * revert build scrpit * updated builds * fix for pc * fix for pc * fix for build
@@ -1,8 +0,0 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "src"
|
||||
INCLUDE_DIRS "src"
|
||||
REQUIRES mbedtls esp_hw_support nvs_flash
|
||||
)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} mlib)
|
||||
|
||||
@@ -1,636 +0,0 @@
|
||||
# GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 [Free Software Foundation, Inc.](http://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 <http://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
|
||||
[http://www.gnu.org/licenses/](http://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
|
||||
[http://www.gnu.org/philosophy/why-not-lgpl.html](http://www.gnu.org/philosophy/why-not-lgpl.html).
|
||||
@@ -1,8 +0,0 @@
|
||||
## Description
|
||||
|
||||
A good portion of this code is derived from the "Furi" code in the [Flipper Zero firmware](https://github.com/flipperdevices/flipperzero-firmware/).
|
||||
Some of it is also inspired by the Android operating system.
|
||||
|
||||
## License
|
||||
|
||||
[GNU General Public License Version 3](LICENSE.md)
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
typedef EventFlag* ApiLock;
|
||||
|
||||
#define TT_API_LOCK_EVENT (1U << 0)
|
||||
|
||||
#define tt_api_lock_alloc_locked() tt_event_flag_alloc()
|
||||
|
||||
#define tt_api_lock_wait_unlock(_lock) \
|
||||
tt_event_flag_wait(_lock, TT_API_LOCK_EVENT, TtFlagWaitAny, TtWaitForever)
|
||||
|
||||
#define tt_api_lock_free(_lock) tt_event_flag_free(_lock)
|
||||
|
||||
#define tt_api_lock_unlock(_lock) tt_event_flag_set(_lock, TT_API_LOCK_EVENT)
|
||||
|
||||
#define tt_api_lock_wait_unlock_and_free(_lock) \
|
||||
tt_api_lock_wait_unlock(_lock); \
|
||||
tt_api_lock_free(_lock);
|
||||
@@ -1,128 +0,0 @@
|
||||
#include "app_i.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static AppFlags tt_app_get_flags_default(AppType type);
|
||||
|
||||
// region Alloc/free
|
||||
|
||||
App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters) {
|
||||
AppData* data = malloc(sizeof(AppData));
|
||||
*data = (AppData) {
|
||||
.mutex = tt_mutex_alloc(MutexTypeRecursive),
|
||||
.state = APP_STATE_INITIAL,
|
||||
.flags = tt_app_get_flags_default(manifest->type),
|
||||
.manifest = manifest,
|
||||
.parameters = parameters,
|
||||
.data = NULL
|
||||
};
|
||||
return (App*)data;
|
||||
}
|
||||
|
||||
void tt_app_free(App app) {
|
||||
AppData* data = (AppData*)app;
|
||||
if (data->parameters) {
|
||||
tt_bundle_free(data->parameters);
|
||||
}
|
||||
tt_mutex_free(data->mutex);
|
||||
free(data);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Internal
|
||||
|
||||
static void tt_app_lock(AppData* data) {
|
||||
tt_mutex_acquire(data->mutex, MutexTypeRecursive);
|
||||
}
|
||||
|
||||
static void tt_app_unlock(AppData* data) {
|
||||
tt_mutex_release(data->mutex);
|
||||
}
|
||||
|
||||
static AppFlags tt_app_get_flags_default(AppType type) {
|
||||
static const AppFlags DEFAULT_DESKTOP_FLAGS = {
|
||||
.show_toolbar = false,
|
||||
.show_statusbar = true
|
||||
};
|
||||
|
||||
static const AppFlags DEFAULT_APP_FLAGS = {
|
||||
.show_toolbar = true,
|
||||
.show_statusbar = true
|
||||
};
|
||||
|
||||
return type == AppTypeDesktop
|
||||
? DEFAULT_DESKTOP_FLAGS
|
||||
: DEFAULT_APP_FLAGS;
|
||||
}
|
||||
|
||||
// endregion Internal
|
||||
|
||||
// region Public getters & setters
|
||||
|
||||
void tt_app_set_state(App app, AppState state) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
data->state = state;
|
||||
tt_app_unlock(data);
|
||||
}
|
||||
|
||||
AppState tt_app_get_state(App app) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
AppState state = data->state;
|
||||
tt_app_unlock(data);
|
||||
return state;
|
||||
}
|
||||
|
||||
const AppManifest* tt_app_get_manifest(App app) {
|
||||
AppData* data = (AppData*)app;
|
||||
// No need to lock const data;
|
||||
return data->manifest;
|
||||
}
|
||||
|
||||
AppFlags tt_app_get_flags(App app) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
AppFlags flags = data->flags;
|
||||
tt_app_unlock(data);
|
||||
return flags;
|
||||
}
|
||||
|
||||
void tt_app_set_flags(App app, AppFlags flags) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
data->flags = flags;
|
||||
tt_app_unlock(data);
|
||||
}
|
||||
|
||||
void* tt_app_get_data(App app) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
void* value = data->data;
|
||||
tt_app_unlock(data);
|
||||
return value;
|
||||
}
|
||||
|
||||
void tt_app_set_data(App app, void* value) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
data->data = value;
|
||||
tt_app_unlock(data);
|
||||
}
|
||||
|
||||
/** TODO: Make this thread-safe.
|
||||
* In practice, the bundle is writeable, so someone could be writing to it
|
||||
* while it is being accessed from another thread.
|
||||
* Consider creating MutableBundle vs Bundle.
|
||||
* Consider not exposing bundle, but expose `app_get_bundle_int(key)` methods with locking in it.
|
||||
*/
|
||||
Bundle* _Nullable tt_app_get_parameters(App app) {
|
||||
AppData* data = (AppData*)app;
|
||||
tt_app_lock(data);
|
||||
Bundle* bundle = data->parameters;
|
||||
tt_app_unlock(data);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
// endregion Public getters & setters
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_manifest.h"
|
||||
#include "bundle.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
APP_STATE_INITIAL, // App is being activated in loader
|
||||
APP_STATE_STARTED, // App is in memory
|
||||
APP_STATE_SHOWING, // App view is created
|
||||
APP_STATE_HIDING, // App view is destroyed
|
||||
APP_STATE_STOPPED // App is not in memory
|
||||
} AppState;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
bool show_statusbar : 1;
|
||||
bool show_toolbar : 1;
|
||||
};
|
||||
unsigned char flags;
|
||||
} AppFlags;
|
||||
|
||||
typedef void* App;
|
||||
|
||||
/** @brief Create an app
|
||||
* @param manifest
|
||||
* @param parameters optional bundle. memory ownership is transferred to App
|
||||
* @return
|
||||
*/
|
||||
App tt_app_alloc(const AppManifest* manifest, Bundle* _Nullable parameters);
|
||||
void tt_app_free(App app);
|
||||
|
||||
void tt_app_set_state(App app, AppState state);
|
||||
AppState tt_app_get_state(App app);
|
||||
|
||||
const AppManifest* tt_app_get_manifest(App app);
|
||||
|
||||
AppFlags tt_app_get_flags(App app);
|
||||
void tt_app_set_flags(App app, AppFlags flags);
|
||||
|
||||
void* _Nullable tt_app_get_data(App app);
|
||||
void tt_app_set_data(App app, void* data);
|
||||
|
||||
Bundle* _Nullable tt_app_get_parameters(App app);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#include "app_manifest.h"
|
||||
#include "mutex.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
Mutex* mutex;
|
||||
const AppManifest* manifest;
|
||||
AppState state;
|
||||
AppFlags flags;
|
||||
/** @brief Optional parameters to start the app with
|
||||
* When these are stored in the app struct, the struct takes ownership.
|
||||
* Do not mutate after app creation.
|
||||
*/
|
||||
Bundle* _Nullable parameters;
|
||||
/** @brief @brief Contextual data related to the running app's instance
|
||||
* The app can attach its data to this.
|
||||
* The lifecycle is determined by the on_start and on_stop methods in the AppManifest.
|
||||
* These manifest methods can optionally allocate/free data that is attached here.
|
||||
*/
|
||||
void* _Nullable data;
|
||||
} AppData;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Forward declarations
|
||||
typedef struct _lv_obj_t lv_obj_t;
|
||||
typedef void* App;
|
||||
|
||||
typedef enum {
|
||||
AppTypeDesktop,
|
||||
AppTypeSystem,
|
||||
AppTypeSettings,
|
||||
AppTypeUser
|
||||
} AppType;
|
||||
|
||||
typedef void (*AppOnStart)(App app);
|
||||
typedef void (*AppOnStop)(App app);
|
||||
typedef void (*AppOnShow)(App app, lv_obj_t* parent);
|
||||
typedef void (*AppOnHide)(App app);
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* The identifier by which the app is launched by the system and other apps.
|
||||
*/
|
||||
const char* id;
|
||||
|
||||
/**
|
||||
* The user-readable name of the app. Used in UI.
|
||||
*/
|
||||
const char* name;
|
||||
|
||||
/**
|
||||
* Optional icon.
|
||||
*/
|
||||
const char* _Nullable icon;
|
||||
|
||||
/**
|
||||
* App type affects launch behaviour.
|
||||
*/
|
||||
const AppType type;
|
||||
|
||||
/**
|
||||
* Non-blocking method to call when app is started.
|
||||
*/
|
||||
const AppOnStart _Nullable on_start;
|
||||
|
||||
/**
|
||||
* Non-blocking method to call when app is stopped.
|
||||
*/
|
||||
const AppOnStop _Nullable on_stop;
|
||||
|
||||
/**
|
||||
* Non-blocking method to create the GUI
|
||||
*/
|
||||
const AppOnShow _Nullable on_show;
|
||||
|
||||
/**
|
||||
* Non-blocking method, called before gui is destroyed
|
||||
*/
|
||||
const AppOnHide _Nullable on_hide;
|
||||
} AppManifest;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,76 +0,0 @@
|
||||
#include "app_manifest_registry.h"
|
||||
|
||||
#include "m-dict.h"
|
||||
#include "m_cstr_dup.h"
|
||||
#include "mutex.h"
|
||||
#include "tactility_core.h"
|
||||
|
||||
#define TAG "app_registry"
|
||||
|
||||
DICT_DEF2(AppManifestDict, const char*, M_CSTR_DUP_OPLIST, const AppManifest*, M_PTR_OPLIST)
|
||||
|
||||
#define APP_REGISTRY_FOR_EACH(manifest_var_name, code_to_execute) \
|
||||
{ \
|
||||
app_registry_lock(); \
|
||||
AppManifestDict_it_t it; \
|
||||
for (AppManifestDict_it(it, app_manifest_dict); !AppManifestDict_end_p(it); AppManifestDict_next(it)) { \
|
||||
const AppManifest*(manifest_var_name) = AppManifestDict_cref(it)->value; \
|
||||
code_to_execute; \
|
||||
} \
|
||||
app_registry_unlock(); \
|
||||
}
|
||||
|
||||
AppManifestDict_t app_manifest_dict;
|
||||
Mutex* hash_mutex = NULL;
|
||||
|
||||
void tt_app_manifest_registry_init() {
|
||||
tt_assert(hash_mutex == NULL);
|
||||
hash_mutex = tt_mutex_alloc(MutexTypeNormal);
|
||||
AppManifestDict_init(app_manifest_dict);
|
||||
}
|
||||
|
||||
void app_registry_lock() {
|
||||
tt_assert(hash_mutex != NULL);
|
||||
tt_mutex_acquire(hash_mutex, TtWaitForever);
|
||||
}
|
||||
|
||||
void app_registry_unlock() {
|
||||
tt_assert(hash_mutex != NULL);
|
||||
tt_mutex_release(hash_mutex);
|
||||
}
|
||||
|
||||
void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest) {
|
||||
TT_LOG_I(TAG, "adding %s", manifest->id);
|
||||
|
||||
app_registry_lock();
|
||||
AppManifestDict_set_at(app_manifest_dict, manifest->id, manifest);
|
||||
app_registry_unlock();
|
||||
}
|
||||
|
||||
void tt_app_manifest_registry_remove(const AppManifest _Nonnull* manifest) {
|
||||
TT_LOG_I(TAG, "removing %s", manifest->id);
|
||||
app_registry_lock();
|
||||
AppManifestDict_erase(app_manifest_dict, manifest->id);
|
||||
app_registry_unlock();
|
||||
}
|
||||
|
||||
const AppManifest _Nullable* tt_app_manifest_registry_find_by_id(const char* id) {
|
||||
app_registry_lock();
|
||||
const AppManifest _Nullable** manifest = AppManifestDict_get(app_manifest_dict, id);
|
||||
app_registry_unlock();
|
||||
return (manifest != NULL) ? *manifest : NULL;
|
||||
}
|
||||
|
||||
void tt_app_manifest_registry_for_each_of_type(AppType type, void* _Nullable context, AppManifestCallback callback) {
|
||||
APP_REGISTRY_FOR_EACH(manifest, {
|
||||
if (manifest->type == type) {
|
||||
callback(manifest, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void tt_app_manifest_registry_for_each(AppManifestCallback callback, void* _Nullable context) {
|
||||
APP_REGISTRY_FOR_EACH(manifest, {
|
||||
callback(manifest, context);
|
||||
});
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_manifest.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*AppManifestCallback)(const AppManifest*, void* context);
|
||||
|
||||
void tt_app_manifest_registry_init();
|
||||
void tt_app_manifest_registry_add(const AppManifest _Nonnull* manifest);
|
||||
void tt_app_manifest_registry_remove(const AppManifest _Nonnull* manifest);
|
||||
const AppManifest _Nullable* tt_app_manifest_registry_find_by_id(const char* id);
|
||||
void tt_app_manifest_registry_for_each(AppManifestCallback callback, void* _Nullable context);
|
||||
void tt_app_manifest_registry_for_each_of_type(AppType type, void* _Nullable context, AppManifestCallback callback);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,205 +0,0 @@
|
||||
#include "bundle.h"
|
||||
|
||||
#include "m-dict.h"
|
||||
#include "m_cstr_dup.h"
|
||||
#include "check.h"
|
||||
|
||||
// region BundleEntry
|
||||
|
||||
typedef enum {
|
||||
BUNDLE_ENTRY_TYPE_BOOL,
|
||||
BUNDLE_ENTRY_TYPE_INT,
|
||||
BUNDLE_ENTRY_TYPE_STRING,
|
||||
} BundleEntryType;
|
||||
|
||||
typedef struct {
|
||||
BundleEntryType type;
|
||||
union {
|
||||
bool bool_value;
|
||||
int int_value;
|
||||
char* string_ptr;
|
||||
};
|
||||
} BundleEntry;
|
||||
|
||||
BundleEntry* bundle_entry_alloc_bool(bool value) {
|
||||
BundleEntry* entry = malloc(sizeof(BundleEntry));
|
||||
entry->type = BUNDLE_ENTRY_TYPE_BOOL;
|
||||
entry->bool_value = value;
|
||||
return entry;
|
||||
}
|
||||
|
||||
BundleEntry* bundle_entry_alloc_int(int value) {
|
||||
BundleEntry* entry = malloc(sizeof(BundleEntry));
|
||||
entry->type = BUNDLE_ENTRY_TYPE_INT;
|
||||
entry->int_value = value;
|
||||
return entry;
|
||||
}
|
||||
|
||||
BundleEntry* bundle_entry_alloc_string(const char* text) {
|
||||
BundleEntry* entry = malloc(sizeof(BundleEntry));
|
||||
entry->type = BUNDLE_ENTRY_TYPE_STRING;
|
||||
entry->string_ptr = malloc(strlen(text) + 1);
|
||||
strcpy(entry->string_ptr, text);
|
||||
return entry;
|
||||
}
|
||||
|
||||
BundleEntry* bundle_entry_alloc_copy(BundleEntry* source) {
|
||||
BundleEntry* entry = malloc(sizeof(BundleEntry));
|
||||
entry->type = source->type;
|
||||
if (source->type == BUNDLE_ENTRY_TYPE_STRING) {
|
||||
entry->string_ptr = malloc(strlen(source->string_ptr) + 1);
|
||||
strcpy(entry->string_ptr, source->string_ptr);
|
||||
} else {
|
||||
entry->int_value = source->int_value;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
void bundle_entry_free(BundleEntry* entry) {
|
||||
if (entry->type == BUNDLE_ENTRY_TYPE_STRING) {
|
||||
free(entry->string_ptr);
|
||||
}
|
||||
free(entry);
|
||||
}
|
||||
|
||||
// endregion BundleEntry
|
||||
|
||||
// region Bundle
|
||||
|
||||
DICT_DEF2(BundleDict, const char*, M_CSTR_DUP_OPLIST, BundleEntry*, M_PTR_OPLIST)
|
||||
|
||||
typedef struct {
|
||||
BundleDict_t dict;
|
||||
} BundleData;
|
||||
|
||||
Bundle tt_bundle_alloc() {
|
||||
BundleData* bundle = malloc(sizeof(BundleData));
|
||||
BundleDict_init(bundle->dict);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
Bundle tt_bundle_alloc_copy(Bundle source) {
|
||||
BundleData* source_data = (BundleData*)source;
|
||||
BundleData* target_data = tt_bundle_alloc();
|
||||
|
||||
BundleDict_it_t it;
|
||||
for (BundleDict_it(it, source_data->dict); !BundleDict_end_p(it); BundleDict_next(it)) {
|
||||
const char* key = BundleDict_cref(it)->key;
|
||||
BundleEntry* entry = BundleDict_cref(it)->value;
|
||||
BundleEntry* entry_copy = bundle_entry_alloc_copy(entry);
|
||||
BundleDict_set_at(target_data->dict, key, entry_copy);
|
||||
}
|
||||
|
||||
return target_data;
|
||||
}
|
||||
|
||||
void tt_bundle_free(Bundle bundle) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
|
||||
BundleDict_it_t it;
|
||||
for (BundleDict_it(it, data->dict); !BundleDict_end_p(it); BundleDict_next(it)) {
|
||||
bundle_entry_free(BundleDict_cref(it)->value);
|
||||
}
|
||||
|
||||
BundleDict_clear(data->dict);
|
||||
free(data);
|
||||
}
|
||||
|
||||
bool tt_bundle_get_bool(Bundle bundle, const char* key) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry = BundleDict_get(data->dict, key);
|
||||
tt_check(entry != NULL);
|
||||
return (*entry)->bool_value;
|
||||
}
|
||||
|
||||
int tt_bundle_get_int(Bundle bundle, const char* key) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry = BundleDict_get(data->dict, key);
|
||||
tt_check(entry != NULL);
|
||||
return (*entry)->int_value;
|
||||
}
|
||||
|
||||
const char* tt_bundle_get_string(Bundle bundle, const char* key) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry = BundleDict_get(data->dict, key);
|
||||
tt_check(entry != NULL);
|
||||
return (*entry)->string_ptr;
|
||||
}
|
||||
|
||||
bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry = BundleDict_get(data->dict, key);
|
||||
if (entry != NULL) {
|
||||
*out = (*entry)->bool_value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry = BundleDict_get(data->dict, key);
|
||||
if (entry != NULL) {
|
||||
*out = (*entry)->int_value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry = BundleDict_get(data->dict, key);
|
||||
if (entry != NULL) {
|
||||
*out = (*entry)->string_ptr;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void tt_bundle_put_bool(Bundle bundle, const char* key, bool value) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry_handle = BundleDict_get(data->dict, key);
|
||||
if (entry_handle != NULL) {
|
||||
BundleEntry* entry = *entry_handle;
|
||||
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_BOOL);
|
||||
entry->bool_value = value;
|
||||
} else {
|
||||
BundleEntry* entry = bundle_entry_alloc_bool(value);
|
||||
BundleDict_set_at(data->dict, key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void tt_bundle_put_int(Bundle bundle, const char* key, int value) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry_handle = BundleDict_get(data->dict, key);
|
||||
if (entry_handle != NULL) {
|
||||
BundleEntry* entry = *entry_handle;
|
||||
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_INT);
|
||||
entry->int_value = value;
|
||||
} else {
|
||||
BundleEntry* entry = bundle_entry_alloc_int(value);
|
||||
BundleDict_set_at(data->dict, key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void tt_bundle_put_string(Bundle bundle, const char* key, const char* value) {
|
||||
BundleData* data = (BundleData*)bundle;
|
||||
BundleEntry** entry_handle = BundleDict_get(data->dict, key);
|
||||
if (entry_handle != NULL) {
|
||||
BundleEntry* entry = *entry_handle;
|
||||
tt_assert(entry->type == BUNDLE_ENTRY_TYPE_STRING);
|
||||
if (entry->string_ptr != NULL) {
|
||||
free(entry->string_ptr);
|
||||
}
|
||||
entry->string_ptr = malloc(strlen(value) + 1);
|
||||
strcpy(entry->string_ptr, value);
|
||||
} else {
|
||||
BundleEntry* entry = bundle_entry_alloc_string(value);
|
||||
BundleDict_set_at(data->dict, key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Bundle
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* @brief key-value storage for general purpose.
|
||||
* Maps strings on a fixed set of data types.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* Bundle;
|
||||
|
||||
Bundle tt_bundle_alloc();
|
||||
Bundle tt_bundle_alloc_copy(Bundle source);
|
||||
void tt_bundle_free(Bundle bundle);
|
||||
|
||||
bool tt_bundle_get_bool(Bundle bundle, const char* key);
|
||||
int tt_bundle_get_int(Bundle bundle, const char* key);
|
||||
const char* tt_bundle_get_string(Bundle bundle, const char* key);
|
||||
|
||||
bool tt_bundle_opt_bool(Bundle bundle, const char* key, bool* out);
|
||||
bool tt_bundle_opt_int(Bundle bundle, const char* key, int* out);
|
||||
bool tt_bundle_opt_string(Bundle bundle, const char* key, char** out);
|
||||
|
||||
void tt_bundle_put_bool(Bundle bundle, const char* key, bool value);
|
||||
void tt_bundle_put_int(Bundle bundle, const char* key, int value);
|
||||
void tt_bundle_put_string(Bundle bundle, const char* key, const char* value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "log.h"
|
||||
|
||||
#define TAG "kernel"
|
||||
|
||||
static void tt_print_memory_info() {
|
||||
TT_LOG_E(TAG, "default caps:");
|
||||
TT_LOG_E(TAG, " total: %u", heap_caps_get_total_size(MALLOC_CAP_DEFAULT));
|
||||
TT_LOG_E(TAG, " free: %u", heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
|
||||
TT_LOG_E(TAG, " min free: %u", heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT));
|
||||
TT_LOG_E(TAG, "internal caps:");
|
||||
TT_LOG_E(TAG, " total: %u", heap_caps_get_total_size(MALLOC_CAP_INTERNAL));
|
||||
TT_LOG_E(TAG, " free: %u", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||
TT_LOG_E(TAG, " min free: %u", heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
|
||||
}
|
||||
|
||||
static void tt_print_task_info() {
|
||||
const char* name = pcTaskGetName(NULL);
|
||||
const char* safe_name = name ? name : "main";
|
||||
TT_LOG_E(TAG, "Task: %s", safe_name);
|
||||
TT_LOG_E(TAG, "Stack watermark: %u", uxTaskGetStackHighWaterMark(NULL) * 4);
|
||||
}
|
||||
|
||||
TT_NORETURN void tt_crash_implementation() {
|
||||
tt_print_task_info();
|
||||
tt_print_memory_info();
|
||||
// TODO: Add breakpoint when debugger is attached.
|
||||
esp_system_abort("System halted. Connect debugger for more info.");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* @file check.h
|
||||
*
|
||||
* Tactility crash and assert functions.
|
||||
*
|
||||
* The main problem with crashing is that you can't do anything without disturbing registers,
|
||||
* and if you disturb registers, you won't be able to see the correct register values in the debugger.
|
||||
*
|
||||
* Current solution works around it by passing the message through r12 and doing some magic with registers in crash function.
|
||||
* r0-r10 are stored in the ram2 on crash routine start and restored at the end.
|
||||
* The only register that is going to be lost is r11.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <m-core.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#define TT_NORETURN [[noreturn]]
|
||||
#else
|
||||
#include <stdnoreturn.h>
|
||||
#define TT_NORETURN noreturn
|
||||
#endif
|
||||
|
||||
/** Crash system */
|
||||
TT_NORETURN void tt_crash_implementation();
|
||||
|
||||
/** Crash system with message. */
|
||||
#define __tt_crash(message) \
|
||||
do { \
|
||||
ESP_LOGE("crash", "%s\n\tat %s:%d", ((message) ? (message) : ""), __FILE__, __LINE__); \
|
||||
tt_crash_implementation(); \
|
||||
} while (0)
|
||||
|
||||
/** Crash system
|
||||
*
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define tt_crash(...) M_APPLY(__tt_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
|
||||
|
||||
/** Halt system
|
||||
*
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define tt_halt(...) M_APPLY(__tt_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
|
||||
|
||||
/** Check condition and crash if check failed */
|
||||
#define __tt_check(__e, __m) \
|
||||
do { \
|
||||
if (!(__e)) { \
|
||||
ESP_LOGE("check", "%s", #__e); \
|
||||
if (__m) { \
|
||||
__tt_crash(#__m); \
|
||||
} else { \
|
||||
__tt_crash(""); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/** Check condition and crash if failed
|
||||
*
|
||||
* @param condition to check
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define tt_check(...) \
|
||||
M_APPLY(__tt_check, M_DEFAULT_ARGS(2, NULL, __VA_ARGS__))
|
||||
|
||||
/** Only in debug build: Assert condition and crash if assert failed */
|
||||
#ifdef TT_DEBUG
|
||||
#define __tt_assert(__e, __m) \
|
||||
do { \
|
||||
if (!(__e)) { \
|
||||
ESP_LOGE("assert", "%s", #__e); \
|
||||
if (__m) { \
|
||||
__tt_crash(#__m); \
|
||||
} else { \
|
||||
__tt_crash(""); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define __tt_assert(__e, __m) \
|
||||
do { \
|
||||
((void)(__e)); \
|
||||
((void)(__m)); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/** Assert condition and crash if failed
|
||||
*
|
||||
* @warning only will do check if firmware compiled in debug mode
|
||||
*
|
||||
* @param condition to check
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define tt_assert(...) \
|
||||
M_APPLY(__tt_assert, M_DEFAULT_ARGS(2, NULL, __VA_ARGS__))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#include "core.h"
|
||||
|
||||
#include "app_manifest_registry.h"
|
||||
#include "service_registry.h"
|
||||
|
||||
#define TAG "tactility"
|
||||
|
||||
void tt_core_init() {
|
||||
TT_LOG_I(TAG, "core init start");
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
|
||||
#if defined(__ARM_ARCH_7A__) && (__ARM_ARCH_7A__ == 0U)
|
||||
/* Service Call interrupt might be configured before kernel start */
|
||||
/* and when its priority is lower or equal to BASEPRI, svc instruction */
|
||||
/* causes a Hard Fault. */
|
||||
NVIC_SetPriority(SVCall_IRQn, 0U);
|
||||
#endif
|
||||
|
||||
tt_service_registry_init();
|
||||
tt_app_manifest_registry_init();
|
||||
TT_LOG_I(TAG, "core init complete");
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tactility_core.h"
|
||||
|
||||
#include "event_flag.h"
|
||||
#include "kernel.h"
|
||||
#include "message_queue.h"
|
||||
#include "mutex.h"
|
||||
#include "pubsub.h"
|
||||
#include "semaphore.h"
|
||||
#include "stream_buffer.h"
|
||||
#include "string.h"
|
||||
#include "thread.h"
|
||||
#include "timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void tt_core_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_extra_defines.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define TT_RETURNS_NONNULL __attribute__((returns_nonnull))
|
||||
|
||||
#define TT_WARN_UNUSED __attribute__((warn_unused_result))
|
||||
|
||||
#define TT_WEAK __attribute__((weak))
|
||||
|
||||
#define TT_PACKED __attribute__((packed))
|
||||
|
||||
// Used by portENABLE_INTERRUPTS and portDISABLE_INTERRUPTS?
|
||||
#define TT_IS_IRQ_MODE() (xPortInIsrContext() == pdTRUE)
|
||||
|
||||
#define TT_IS_ISR() (TT_IS_IRQ_MODE())
|
||||
|
||||
#define TT_CHECK_RETURN __attribute__((__warn_unused_result__))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,114 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a < _b ? _a : _b; \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef ABS
|
||||
#define ABS(a) ({ (a) < 0 ? -(a) : (a); })
|
||||
#endif
|
||||
|
||||
#ifndef ROUND_UP_TO
|
||||
#define ROUND_UP_TO(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a / _b + !!(_a % _b); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef CLAMP
|
||||
#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
|
||||
#endif
|
||||
|
||||
#ifndef COUNT_OF
|
||||
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
|
||||
#endif
|
||||
|
||||
#ifndef TT_SWAP
|
||||
#define TT_SWAP(x, y) \
|
||||
do { \
|
||||
typeof(x) SWAP = x; \
|
||||
x = y; \
|
||||
y = SWAP; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifndef PLACE_IN_SECTION
|
||||
#define PLACE_IN_SECTION(x) __attribute__((section(x)))
|
||||
#endif
|
||||
|
||||
#ifndef ALIGN
|
||||
#define ALIGN(n) __attribute__((aligned(n)))
|
||||
#endif
|
||||
|
||||
#ifndef __weak
|
||||
#define __weak __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifndef UNUSED
|
||||
#define UNUSED(X) (void)(X)
|
||||
#endif
|
||||
|
||||
#ifndef STRINGIFY
|
||||
#define STRINGIFY(x) #x
|
||||
#endif
|
||||
|
||||
#ifndef TOSTRING
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
#endif
|
||||
|
||||
#ifndef CONCATENATE
|
||||
#define CONCATENATE(a, b) CONCATENATE_(a, b)
|
||||
#define CONCATENATE_(a, b) a##b
|
||||
#endif
|
||||
|
||||
#ifndef REVERSE_BYTES_U32
|
||||
#define REVERSE_BYTES_U32(x) \
|
||||
((((x) & 0x000000FF) << 24) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8) | \
|
||||
(((x) & 0xFF000000) >> 24))
|
||||
#endif
|
||||
|
||||
#ifndef TT_BIT
|
||||
#define TT_BIT(x, n) (((x) >> (n)) & 1)
|
||||
#endif
|
||||
|
||||
#ifndef TT_BIT_SET
|
||||
#define TT_BIT_SET(x, n) \
|
||||
({ \
|
||||
__typeof__(x) _x = (1); \
|
||||
(x) |= (_x << (n)); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef TT_BIT_CLEAR
|
||||
#define TT_BIT_CLEAR(x, n) \
|
||||
({ \
|
||||
__typeof__(x) _x = (1); \
|
||||
(x) &= ~(_x << (n)); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#define TT_SW_MEMBARRIER() asm volatile("" : : : "memory")
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <tactility_core_config.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
TtWaitForever = 0xFFFFFFFFU,
|
||||
} TtWait;
|
||||
|
||||
typedef enum {
|
||||
TtFlagWaitAny = 0x00000000U, ///< Wait for any flag (default).
|
||||
TtFlagWaitAll = 0x00000001U, ///< Wait for all flags.
|
||||
TtFlagNoClear = 0x00000002U, ///< Do not clear flags which have been specified to wait for.
|
||||
|
||||
TtFlagError = 0x80000000U, ///< Error indicator.
|
||||
TtFlagErrorUnknown = 0xFFFFFFFFU, ///< TtStatusError (-1).
|
||||
TtFlagErrorTimeout = 0xFFFFFFFEU, ///< TtStatusErrorTimeout (-2).
|
||||
TtFlagErrorResource = 0xFFFFFFFDU, ///< TtStatusErrorResource (-3).
|
||||
TtFlagErrorParameter = 0xFFFFFFFCU, ///< TtStatusErrorParameter (-4).
|
||||
TtFlagErrorISR = 0xFFFFFFFAU, ///< TtStatusErrorISR (-6).
|
||||
} TtFlag;
|
||||
|
||||
typedef enum {
|
||||
TtStatusOk = 0, ///< Operation completed successfully.
|
||||
TtStatusError =
|
||||
-1, ///< Unspecified RTOS error: run-time error but no other error message fits.
|
||||
TtStatusErrorTimeout = -2, ///< Operation not completed within the timeout period.
|
||||
TtStatusErrorResource = -3, ///< Resource not available.
|
||||
TtStatusErrorParameter = -4, ///< Parameter error.
|
||||
TtStatusErrorNoMemory =
|
||||
-5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
|
||||
TtStatusErrorISR =
|
||||
-6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
|
||||
TtStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
|
||||
} TtStatus;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "critical.h"
|
||||
#include "core_defines.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static portMUX_TYPE critical_mutex;
|
||||
|
||||
__TtCriticalInfo __tt_critical_enter(void) {
|
||||
__TtCriticalInfo info;
|
||||
|
||||
info.isrm = 0;
|
||||
info.from_isr = TT_IS_ISR();
|
||||
info.kernel_running = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING);
|
||||
|
||||
if (info.from_isr) {
|
||||
info.isrm = taskENTER_CRITICAL_FROM_ISR();
|
||||
} else if (info.kernel_running) {
|
||||
taskENTER_CRITICAL(&critical_mutex);
|
||||
} else {
|
||||
portDISABLE_INTERRUPTS();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void __tt_critical_exit(__TtCriticalInfo info) {
|
||||
if (info.from_isr) {
|
||||
taskEXIT_CRITICAL_FROM_ISR(info.isrm);
|
||||
} else if (info.kernel_running) {
|
||||
taskEXIT_CRITICAL(&critical_mutex);
|
||||
} else {
|
||||
portENABLE_INTERRUPTS();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef TT_CRITICAL_ENTER
|
||||
#define TT_CRITICAL_ENTER() __TtCriticalInfo __tt_critical_info = __tt_critical_enter();
|
||||
#endif
|
||||
|
||||
#ifndef TT_CRITICAL_EXIT
|
||||
#define TT_CRITICAL_EXIT() __tt_critical_exit(__tt_critical_info);
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint32_t isrm;
|
||||
bool from_isr;
|
||||
bool kernel_running;
|
||||
} __TtCriticalInfo;
|
||||
|
||||
__TtCriticalInfo __tt_critical_enter(void);
|
||||
|
||||
void __tt_critical_exit(__TtCriticalInfo info);
|
||||
@@ -1,146 +0,0 @@
|
||||
#include "event_flag.h"
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#define TT_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U
|
||||
#define TT_EVENT_FLAG_INVALID_BITS (~((1UL << TT_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U))
|
||||
|
||||
EventFlag* tt_event_flag_alloc() {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
|
||||
EventGroupHandle_t handle = xEventGroupCreate();
|
||||
tt_check(handle);
|
||||
|
||||
return ((EventFlag*)handle);
|
||||
}
|
||||
|
||||
void tt_event_flag_free(EventFlag* instance) {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
vEventGroupDelete((EventGroupHandle_t)instance);
|
||||
}
|
||||
|
||||
uint32_t tt_event_flag_set(EventFlag* instance, uint32_t flags) {
|
||||
tt_assert(instance);
|
||||
tt_assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
uint32_t rflags;
|
||||
BaseType_t yield;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
if (xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) {
|
||||
rflags = (uint32_t)TtFlagErrorResource;
|
||||
} else {
|
||||
rflags = flags;
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
} else {
|
||||
rflags = xEventGroupSetBits(hEventGroup, (EventBits_t)flags);
|
||||
}
|
||||
|
||||
/* Return event flags after setting */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_event_flag_clear(EventFlag* instance, uint32_t flags) {
|
||||
tt_assert(instance);
|
||||
tt_assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
uint32_t rflags;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
rflags = xEventGroupGetBitsFromISR(hEventGroup);
|
||||
|
||||
if (xEventGroupClearBitsFromISR(hEventGroup, (EventBits_t)flags) == pdFAIL) {
|
||||
rflags = (uint32_t)TtStatusErrorResource;
|
||||
} else {
|
||||
/* xEventGroupClearBitsFromISR only registers clear operation in the timer command queue. */
|
||||
/* Yield is required here otherwise clear operation might not execute in the right order. */
|
||||
/* See https://github.com/FreeRTOS/FreeRTOS-Kernel/issues/93 for more info. */
|
||||
portYIELD_FROM_ISR(pdTRUE);
|
||||
}
|
||||
} else {
|
||||
rflags = xEventGroupClearBits(hEventGroup, (EventBits_t)flags);
|
||||
}
|
||||
|
||||
/* Return event flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_event_flag_get(EventFlag* instance) {
|
||||
tt_assert(instance);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
uint32_t rflags;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
rflags = xEventGroupGetBitsFromISR(hEventGroup);
|
||||
} else {
|
||||
rflags = xEventGroupGetBits(hEventGroup);
|
||||
}
|
||||
|
||||
/* Return current event flags */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_event_flag_wait(
|
||||
EventFlag* instance,
|
||||
uint32_t flags,
|
||||
uint32_t options,
|
||||
uint32_t timeout
|
||||
) {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
tt_assert(instance);
|
||||
tt_assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
BaseType_t wait_all;
|
||||
BaseType_t exit_clr;
|
||||
uint32_t rflags;
|
||||
|
||||
if (options & TtFlagWaitAll) {
|
||||
wait_all = pdTRUE;
|
||||
} else {
|
||||
wait_all = pdFAIL;
|
||||
}
|
||||
|
||||
if (options & TtFlagNoClear) {
|
||||
exit_clr = pdFAIL;
|
||||
} else {
|
||||
exit_clr = pdTRUE;
|
||||
}
|
||||
|
||||
rflags = xEventGroupWaitBits(
|
||||
hEventGroup,
|
||||
(EventBits_t)flags,
|
||||
exit_clr,
|
||||
wait_all,
|
||||
(TickType_t)timeout
|
||||
);
|
||||
|
||||
if (options & TtFlagWaitAll) {
|
||||
if ((flags & rflags) != flags) {
|
||||
if (timeout > 0U) {
|
||||
rflags = (uint32_t)TtStatusErrorTimeout;
|
||||
} else {
|
||||
rflags = (uint32_t)TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((flags & rflags) == 0U) {
|
||||
if (timeout > 0U) {
|
||||
rflags = (uint32_t)TtStatusErrorTimeout;
|
||||
} else {
|
||||
rflags = (uint32_t)TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return event flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void EventFlag;
|
||||
|
||||
/** Allocate EventFlag
|
||||
*
|
||||
* @return pointer to EventFlag
|
||||
*/
|
||||
EventFlag* tt_event_flag_alloc();
|
||||
|
||||
/** Deallocate EventFlag
|
||||
*
|
||||
* @param instance pointer to EventFlag
|
||||
*/
|
||||
void tt_event_flag_free(EventFlag* instance);
|
||||
|
||||
/** Set flags
|
||||
*
|
||||
* @param instance pointer to EventFlag
|
||||
* @param[in] flags The flags
|
||||
*
|
||||
* @return Resulting flags or error (TtStatus)
|
||||
*/
|
||||
uint32_t tt_event_flag_set(EventFlag* instance, uint32_t flags);
|
||||
|
||||
/** Clear flags
|
||||
*
|
||||
* @param instance pointer to EventFlag
|
||||
* @param[in] flags The flags
|
||||
*
|
||||
* @return Resulting flags or error (TtStatus)
|
||||
*/
|
||||
uint32_t tt_event_flag_clear(EventFlag* instance, uint32_t flags);
|
||||
|
||||
/** Get flags
|
||||
*
|
||||
* @param instance pointer to EventFlag
|
||||
*
|
||||
* @return Resulting flags
|
||||
*/
|
||||
uint32_t tt_event_flag_get(EventFlag* instance);
|
||||
|
||||
/** Wait flags
|
||||
*
|
||||
* @param instance pointer to EventFlag
|
||||
* @param[in] flags The flags
|
||||
* @param[in] options The option flags
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return Resulting flags or error (TtStatus)
|
||||
*/
|
||||
uint32_t tt_event_flag_wait(
|
||||
EventFlag* instance,
|
||||
uint32_t flags,
|
||||
uint32_t options,
|
||||
uint32_t timeout
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "hash.h"
|
||||
|
||||
uint32_t tt_hash_string_djb2(const char* str) {
|
||||
uint32_t hash = 5381;
|
||||
char c = (char)*str++;
|
||||
while (c != 0) {
|
||||
hash = ((hash << 5) + hash) + (uint32_t)c; // hash * 33 + c
|
||||
c = (char)*str++;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is quicker than the m-string.h hashing, as the latter
|
||||
* operates on raw memory blocks and thus a strlen() call is required first.
|
||||
* @param[in] str the string to calculate the hash for
|
||||
* @return the hash
|
||||
*/
|
||||
uint32_t tt_hash_string_djb2(const char* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,182 +0,0 @@
|
||||
#include "kernel.h"
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
#include "core_types.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <rom/ets_sys.h>
|
||||
|
||||
bool tt_kernel_is_irq() {
|
||||
return TT_IS_IRQ_MODE();
|
||||
}
|
||||
|
||||
bool tt_kernel_is_running() {
|
||||
return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING;
|
||||
}
|
||||
|
||||
int32_t tt_kernel_lock() {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
|
||||
int32_t lock;
|
||||
|
||||
switch (xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
lock = 1;
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_RUNNING:
|
||||
vTaskSuspendAll();
|
||||
lock = 0;
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_NOT_STARTED:
|
||||
default:
|
||||
lock = (int32_t)TtStatusError;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return previous lock state */
|
||||
return (lock);
|
||||
}
|
||||
|
||||
int32_t tt_kernel_unlock() {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
|
||||
int32_t lock;
|
||||
|
||||
switch (xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
lock = 1;
|
||||
|
||||
if (xTaskResumeAll() != pdTRUE) {
|
||||
if (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
|
||||
lock = (int32_t)TtStatusError;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_RUNNING:
|
||||
lock = 0;
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_NOT_STARTED:
|
||||
default:
|
||||
lock = (int32_t)TtStatusError;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return previous lock state */
|
||||
return (lock);
|
||||
}
|
||||
|
||||
int32_t tt_kernel_restore_lock(int32_t lock) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
|
||||
switch (xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
case taskSCHEDULER_RUNNING:
|
||||
if (lock == 1) {
|
||||
vTaskSuspendAll();
|
||||
} else {
|
||||
if (lock != 0) {
|
||||
lock = (int32_t)TtStatusError;
|
||||
} else {
|
||||
if (xTaskResumeAll() != pdTRUE) {
|
||||
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
|
||||
lock = (int32_t)TtStatusError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_NOT_STARTED:
|
||||
default:
|
||||
lock = (int32_t)TtStatusError;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return new lock state */
|
||||
return (lock);
|
||||
}
|
||||
|
||||
uint32_t tt_kernel_get_tick_frequency() {
|
||||
/* Return frequency in hertz */
|
||||
return (configTICK_RATE_HZ_RAW);
|
||||
}
|
||||
|
||||
void tt_delay_tick(uint32_t ticks) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
if (ticks == 0U) {
|
||||
taskYIELD();
|
||||
} else {
|
||||
vTaskDelay(ticks);
|
||||
}
|
||||
}
|
||||
|
||||
TtStatus tt_delay_until_tick(uint32_t tick) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
|
||||
TickType_t tcnt, delay;
|
||||
TtStatus stat;
|
||||
|
||||
stat = TtStatusOk;
|
||||
tcnt = xTaskGetTickCount();
|
||||
|
||||
/* Determine remaining number of tick to delay */
|
||||
delay = (TickType_t)tick - tcnt;
|
||||
|
||||
/* Check if target tick has not expired */
|
||||
if ((delay != 0U) && (0 == (delay >> (8 * sizeof(TickType_t) - 1)))) {
|
||||
if (xTaskDelayUntil(&tcnt, delay) == pdFALSE) {
|
||||
/* Did not delay */
|
||||
stat = TtStatusError;
|
||||
}
|
||||
} else {
|
||||
/* No delay or already expired */
|
||||
stat = TtStatusErrorParameter;
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
uint32_t tt_get_tick() {
|
||||
TickType_t ticks;
|
||||
|
||||
if (tt_kernel_is_irq() != 0U) {
|
||||
ticks = xTaskGetTickCountFromISR();
|
||||
} else {
|
||||
ticks = xTaskGetTickCount();
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
uint32_t tt_ms_to_ticks(uint32_t milliseconds) {
|
||||
#if configTICK_RATE_HZ_RAW == 1000
|
||||
return milliseconds;
|
||||
#else
|
||||
return (uint32_t)((float)configTICK_RATE_HZ_RAW) / 1000.0f * (float)milliseconds;
|
||||
#endif
|
||||
}
|
||||
|
||||
void tt_delay_ms(uint32_t milliseconds) {
|
||||
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
|
||||
if (milliseconds > 0 && milliseconds < portMAX_DELAY - 1) {
|
||||
milliseconds += 1;
|
||||
}
|
||||
#if configTICK_RATE_HZ_RAW == 1000
|
||||
tt_delay_tick(milliseconds);
|
||||
#else
|
||||
tt_delay_tick(tt_ms_to_ticks(milliseconds));
|
||||
#endif
|
||||
} else if (milliseconds > 0) {
|
||||
tt_delay_us(milliseconds * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void tt_delay_us(uint32_t microseconds) {
|
||||
ets_delay_us(microseconds);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_types.h"
|
||||
|
||||
#define configTICK_RATE_HZ_RAW CONFIG_FREERTOS_HZ
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Check if CPU is in IRQ or kernel running and IRQ is masked
|
||||
*
|
||||
* Originally this primitive was born as a workaround for FreeRTOS kernel primitives shenanigans with PRIMASK.
|
||||
*
|
||||
* Meaningful use cases are:
|
||||
*
|
||||
* - When kernel is started and you want to ensure that you are not in IRQ or IRQ is not masked(like in critical section)
|
||||
* - When kernel is not started and you want to make sure that you are not in IRQ mode, ignoring PRIMASK.
|
||||
*
|
||||
* As you can see there will be edge case when kernel is not started and PRIMASK is not 0 that may cause some funky behavior.
|
||||
* Most likely it will happen after kernel primitives being used, but control not yet passed to kernel.
|
||||
* It's up to you to figure out if it is safe for your code or not.
|
||||
*
|
||||
* @return true if CPU is in IRQ or kernel running and IRQ is masked
|
||||
*/
|
||||
bool tt_kernel_is_irq();
|
||||
|
||||
/** Check if kernel is running
|
||||
*
|
||||
* @return true if running, false otherwise
|
||||
*/
|
||||
bool tt_kernel_is_running();
|
||||
|
||||
/** Lock kernel, pause process scheduling
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @return previous lock state(0 - unlocked, 1 - locked)
|
||||
*/
|
||||
int32_t tt_kernel_lock();
|
||||
|
||||
/** Unlock kernel, resume process scheduling
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @return previous lock state(0 - unlocked, 1 - locked)
|
||||
*/
|
||||
int32_t tt_kernel_unlock();
|
||||
|
||||
/** Restore kernel lock state
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @param[in] lock The lock state
|
||||
*
|
||||
* @return new lock state or error
|
||||
*/
|
||||
int32_t tt_kernel_restore_lock(int32_t lock);
|
||||
|
||||
/** Get kernel systick frequency
|
||||
*
|
||||
* @return systick counts per second
|
||||
*/
|
||||
uint32_t tt_kernel_get_tick_frequency();
|
||||
|
||||
/** Delay execution
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* Also keep in mind delay is aliased to scheduler timer intervals.
|
||||
*
|
||||
* @param[in] ticks The ticks count to pause
|
||||
*/
|
||||
void tt_delay_tick(uint32_t ticks);
|
||||
|
||||
/** Delay until tick
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @param[in] ticks The tick until which kerel should delay task execution
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_delay_until_tick(uint32_t tick);
|
||||
|
||||
/** Convert milliseconds to ticks
|
||||
*
|
||||
* @param[in] milliseconds time in milliseconds
|
||||
* @return time in ticks
|
||||
*/
|
||||
uint32_t tt_ms_to_ticks(uint32_t milliseconds);
|
||||
|
||||
/** Delay in milliseconds
|
||||
*
|
||||
* This method uses kernel ticks on the inside, which causes delay to be aliased to scheduler timer intervals.
|
||||
* Real wait time will be between X+ milliseconds.
|
||||
* Special value: 0, will cause task yield.
|
||||
* Also if used when kernel is not running will fall back to `tt_delay_us`.
|
||||
*
|
||||
* @warning Cannot be used from ISR
|
||||
*
|
||||
* @param[in] milliseconds milliseconds to wait
|
||||
*/
|
||||
void tt_delay_ms(uint32_t milliseconds);
|
||||
|
||||
/** Delay in microseconds
|
||||
*
|
||||
* Implemented using Cortex DWT counter. Blocking and non aliased.
|
||||
*
|
||||
* @param[in] microseconds microseconds to wait
|
||||
*/
|
||||
void tt_delay_us(uint32_t microseconds);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,72 +0,0 @@
|
||||
#ifndef ESP_PLATFORM
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <time.h>
|
||||
|
||||
static char tt_loglevel_to_prefix(LogLevel level) {
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR:
|
||||
return 'E';
|
||||
case LOG_LEVEL_WARNING:
|
||||
return 'W';
|
||||
case LOG_LEVEL_INFO:
|
||||
return 'I';
|
||||
case LOG_LEVEL_DEBUG:
|
||||
return 'D';
|
||||
case LOG_LEVEL_TRACE:
|
||||
return 'T';
|
||||
default:
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
static const char* tt_loglevel_to_colour(LogLevel level) {
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR:
|
||||
return "\033[1;31m";
|
||||
case LOG_LEVEL_WARNING:
|
||||
return "\033[33m";
|
||||
case LOG_LEVEL_INFO:
|
||||
return "\033[32m";
|
||||
case LOG_LEVEL_DEBUG:
|
||||
return "\033[1;37m";
|
||||
case LOG_LEVEL_TRACE:
|
||||
return "\033[37m";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tt_log_timestamp(void) {
|
||||
if (unlikely(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED)) {
|
||||
return clock() / CLOCKS_PER_SEC * 1000;
|
||||
}
|
||||
static uint32_t base = 0;
|
||||
if (base == 0 && xPortGetCoreID() == 0) {
|
||||
base = clock() / CLOCKS_PER_SEC * 1000;
|
||||
}
|
||||
TickType_t tick_count = xPortInIsrContext() ? xTaskGetTickCountFromISR() : xTaskGetTickCount();
|
||||
return base + tick_count * (1000 / configTICK_RATE_HZ);
|
||||
}
|
||||
|
||||
void tt_log(LogLevel level, const char* tag, const char* format, ...) {
|
||||
printf(
|
||||
"%s%c (%lu) %s: ",
|
||||
tt_loglevel_to_colour(level),
|
||||
tt_loglevel_to_prefix(level),
|
||||
tt_log_timestamp(),
|
||||
tag
|
||||
);
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vprintf(format, args);
|
||||
va_end(args);
|
||||
|
||||
printf("\033[0m\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_log.h"
|
||||
#else
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#define TT_LOG_E(tag, format, ...) \
|
||||
ESP_LOGE(tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_W(tag, format, ...) \
|
||||
ESP_LOGW(tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_I(tag, format, ...) \
|
||||
ESP_LOGI(tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_D(tag, format, ...) \
|
||||
ESP_LOGD(tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_T(tag, format, ...) \
|
||||
ESP_LOGV(tag, format, ##__VA_ARGS__)
|
||||
|
||||
#else
|
||||
|
||||
typedef enum {
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARNING,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG,
|
||||
LOG_LEVEL_TRACE
|
||||
} LogLevel;
|
||||
|
||||
void tt_log(LogLevel level, const char* tag, const char* format, ...);
|
||||
|
||||
#define TT_LOG_E(tag, format, ...) \
|
||||
tt_log(LOG_LEVEL_ERROR, tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_W(tag, format, ...) \
|
||||
tt_log(LOG_LEVEL_WARNING, tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_I(tag, format, ...) \
|
||||
tt_log(LOG_LEVEL_INFO, tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_D(tag, format, ...) \
|
||||
tt_log(LOG_LEVEL_DEBUG, tag, format, ##__VA_ARGS__)
|
||||
#define TT_LOG_T(tag, format, ...) \
|
||||
tt_log(LOG_LEVEL_TRACE, tag, format, ##__VA_ARGS__)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
#include <m-core.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define M_INIT_DUP(a) ((a) = strdup(""))
|
||||
#define M_INIT_SET_DUP(a, b) ((a) = strdup(b))
|
||||
#define M_SET_DUP(a, b) (free((void*)a), (a) = strdup(b))
|
||||
#define M_CLEAR_DUP(a) (free((void*)a))
|
||||
|
||||
#define M_CSTR_DUP_OPLIST \
|
||||
(INIT(M_INIT_DUP), \
|
||||
INIT_SET(M_INIT_SET_DUP), \
|
||||
SET(M_SET_DUP), \
|
||||
CLEAR(M_CLEAR_DUP), \
|
||||
HASH(m_core_cstr_hash), \
|
||||
EQUAL(M_CSTR_EQUAL), \
|
||||
CMP(strcmp), \
|
||||
TYPE(const char*))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,181 +0,0 @@
|
||||
#include "message_queue.h"
|
||||
#include "check.h"
|
||||
#include "kernel.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
MessageQueue* tt_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) {
|
||||
tt_assert((tt_kernel_is_irq() == 0U) && (msg_count > 0U) && (msg_size > 0U));
|
||||
|
||||
QueueHandle_t handle = xQueueCreate(msg_count, msg_size);
|
||||
tt_check(handle);
|
||||
|
||||
return ((MessageQueue*)handle);
|
||||
}
|
||||
|
||||
void tt_message_queue_free(MessageQueue* instance) {
|
||||
tt_assert(tt_kernel_is_irq() == 0U);
|
||||
tt_assert(instance);
|
||||
|
||||
vQueueDelete((QueueHandle_t)instance);
|
||||
}
|
||||
|
||||
TtStatus tt_message_queue_put(MessageQueue* instance, const void* msg_ptr, uint32_t timeout) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
TtStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = TtStatusOk;
|
||||
|
||||
if (tt_kernel_is_irq() != 0U) {
|
||||
if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
yield = pdFALSE;
|
||||
|
||||
if (xQueueSendToBackFromISR(hQueue, msg_ptr, &yield) != pdTRUE) {
|
||||
stat = TtStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((hQueue == NULL) || (msg_ptr == NULL)) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
if (xQueueSendToBack(hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
|
||||
if (timeout != 0U) {
|
||||
stat = TtStatusErrorTimeout;
|
||||
} else {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
TtStatus tt_message_queue_get(MessageQueue* instance, void* msg_ptr, uint32_t timeout_ticks) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
TtStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = TtStatusOk;
|
||||
|
||||
if (tt_kernel_is_irq() != 0U) {
|
||||
if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout_ticks != 0U)) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
yield = pdFALSE;
|
||||
|
||||
if (xQueueReceiveFromISR(hQueue, msg_ptr, &yield) != pdPASS) {
|
||||
stat = TtStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((hQueue == NULL) || (msg_ptr == NULL)) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
if (xQueueReceive(hQueue, msg_ptr, (TickType_t)timeout_ticks) != pdPASS) {
|
||||
if (timeout_ticks != 0U) {
|
||||
stat = TtStatusErrorTimeout;
|
||||
} else {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
uint32_t tt_message_queue_get_capacity(MessageQueue* instance) {
|
||||
StaticQueue_t* mq = (StaticQueue_t*)instance;
|
||||
uint32_t capacity;
|
||||
|
||||
if (mq == NULL) {
|
||||
capacity = 0U;
|
||||
} else {
|
||||
/* capacity = pxQueue->uxLength */
|
||||
capacity = mq->uxDummy4[1];
|
||||
}
|
||||
|
||||
/* Return maximum number of messages */
|
||||
return (capacity);
|
||||
}
|
||||
|
||||
uint32_t tt_message_queue_get_message_size(MessageQueue* instance) {
|
||||
StaticQueue_t* mq = (StaticQueue_t*)instance;
|
||||
uint32_t size;
|
||||
|
||||
if (mq == NULL) {
|
||||
size = 0U;
|
||||
} else {
|
||||
/* size = pxQueue->uxItemSize */
|
||||
size = mq->uxDummy4[2];
|
||||
}
|
||||
|
||||
/* Return maximum message size */
|
||||
return (size);
|
||||
}
|
||||
|
||||
uint32_t tt_message_queue_get_count(MessageQueue* instance) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
UBaseType_t count;
|
||||
|
||||
if (hQueue == NULL) {
|
||||
count = 0U;
|
||||
} else if (tt_kernel_is_irq() != 0U) {
|
||||
count = uxQueueMessagesWaitingFromISR(hQueue);
|
||||
} else {
|
||||
count = uxQueueMessagesWaiting(hQueue);
|
||||
}
|
||||
|
||||
/* Return number of queued messages */
|
||||
return ((uint32_t)count);
|
||||
}
|
||||
|
||||
uint32_t tt_message_queue_get_space(MessageQueue* instance) {
|
||||
StaticQueue_t* mq = (StaticQueue_t*)instance;
|
||||
uint32_t space;
|
||||
uint32_t isrm;
|
||||
|
||||
if (mq == NULL) {
|
||||
space = 0U;
|
||||
} else if (tt_kernel_is_irq() != 0U) {
|
||||
isrm = taskENTER_CRITICAL_FROM_ISR();
|
||||
|
||||
/* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */
|
||||
space = mq->uxDummy4[1] - mq->uxDummy4[0];
|
||||
|
||||
taskEXIT_CRITICAL_FROM_ISR(isrm);
|
||||
} else {
|
||||
space = (uint32_t)uxQueueSpacesAvailable((QueueHandle_t)mq);
|
||||
}
|
||||
|
||||
/* Return number of available slots */
|
||||
return (space);
|
||||
}
|
||||
|
||||
TtStatus tt_message_queue_reset(MessageQueue* instance) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
TtStatus stat;
|
||||
|
||||
if (tt_kernel_is_irq() != 0U) {
|
||||
stat = TtStatusErrorISR;
|
||||
} else if (hQueue == NULL) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
stat = TtStatusOk;
|
||||
(void)xQueueReset(hQueue);
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* @file message_queue.h
|
||||
* MessageQueue
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void MessageQueue;
|
||||
|
||||
/** Allocate message queue
|
||||
*
|
||||
* @param[in] msg_count The message count
|
||||
* @param[in] msg_size The message size
|
||||
*
|
||||
* @return pointer to MessageQueue instance
|
||||
*/
|
||||
MessageQueue* tt_message_queue_alloc(uint32_t msg_count, uint32_t msg_size);
|
||||
|
||||
/** Free queue
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
*/
|
||||
void tt_message_queue_free(MessageQueue* instance);
|
||||
|
||||
/** Put message into queue
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
* @param[in] msg_ptr The message pointer
|
||||
* @param[in] timeout The timeout
|
||||
* @param[in] msg_prio The message prio
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_message_queue_put(MessageQueue* instance, const void* msg_ptr, uint32_t timeout);
|
||||
|
||||
/** Get message from queue
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
* @param msg_ptr The message pointer
|
||||
* @param msg_prio The message prioority
|
||||
* @param[in] timeout_ticks The timeout
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_message_queue_get(MessageQueue* instance, void* msg_ptr, uint32_t timeout_ticks);
|
||||
|
||||
/** Get queue capacity
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
*
|
||||
* @return capacity in object count
|
||||
*/
|
||||
uint32_t tt_message_queue_get_capacity(MessageQueue* instance);
|
||||
|
||||
/** Get message size
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
*
|
||||
* @return Message size in bytes
|
||||
*/
|
||||
uint32_t tt_message_queue_get_message_size(MessageQueue* instance);
|
||||
|
||||
/** Get message count in queue
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
*
|
||||
* @return Message count
|
||||
*/
|
||||
uint32_t tt_message_queue_get_count(MessageQueue* instance);
|
||||
|
||||
/** Get queue available space
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
*
|
||||
* @return Message count
|
||||
*/
|
||||
uint32_t tt_message_queue_get_space(MessageQueue* instance);
|
||||
|
||||
/** Reset queue
|
||||
*
|
||||
* @param instance pointer to MessageQueue instance
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_message_queue_reset(MessageQueue* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,126 +0,0 @@
|
||||
#include "mutex.h"
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "log.h"
|
||||
|
||||
Mutex* tt_mutex_alloc(MutexType type) {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
|
||||
SemaphoreHandle_t hMutex = NULL;
|
||||
|
||||
if (type == MutexTypeNormal) {
|
||||
hMutex = xSemaphoreCreateMutex();
|
||||
} else if (type == MutexTypeRecursive) {
|
||||
hMutex = xSemaphoreCreateRecursiveMutex();
|
||||
} else {
|
||||
tt_crash("Programming error");
|
||||
}
|
||||
|
||||
tt_check(hMutex != NULL);
|
||||
|
||||
if (type == MutexTypeRecursive) {
|
||||
/* Set LSB as 'recursive mutex flag' */
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)hMutex | 1U);
|
||||
}
|
||||
|
||||
/* Return mutex ID */
|
||||
return ((Mutex*)hMutex);
|
||||
}
|
||||
|
||||
void tt_mutex_free(Mutex* instance) {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
tt_assert(instance);
|
||||
|
||||
vSemaphoreDelete((SemaphoreHandle_t)((uint32_t)instance & ~1U));
|
||||
}
|
||||
|
||||
TtStatus tt_mutex_acquire(Mutex* instance, uint32_t timeout) {
|
||||
SemaphoreHandle_t hMutex;
|
||||
TtStatus stat;
|
||||
uint32_t rmtx;
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
/* Extract recursive mutex flag */
|
||||
rmtx = (uint32_t)instance & 1U;
|
||||
|
||||
stat = TtStatusOk;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
stat = TtStatusErrorISR;
|
||||
} else if (hMutex == NULL) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
if (rmtx != 0U) {
|
||||
if (xSemaphoreTakeRecursive(hMutex, timeout) != pdPASS) {
|
||||
if (timeout != 0U) {
|
||||
stat = TtStatusErrorTimeout;
|
||||
} else {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (xSemaphoreTake(hMutex, timeout) != pdPASS) {
|
||||
if (timeout != 0U) {
|
||||
stat = TtStatusErrorTimeout;
|
||||
} else {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
TtStatus tt_mutex_release(Mutex* instance) {
|
||||
SemaphoreHandle_t hMutex;
|
||||
TtStatus stat;
|
||||
uint32_t rmtx;
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
/* Extract recursive mutex flag */
|
||||
rmtx = (uint32_t)instance & 1U;
|
||||
|
||||
stat = TtStatusOk;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
stat = TtStatusErrorISR;
|
||||
} else if (hMutex == NULL) {
|
||||
stat = TtStatusErrorParameter;
|
||||
} else {
|
||||
if (rmtx != 0U) {
|
||||
if (xSemaphoreGiveRecursive(hMutex) != pdPASS) {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
} else {
|
||||
if (xSemaphoreGive(hMutex) != pdPASS) {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
ThreadId tt_mutex_get_owner(Mutex* instance) {
|
||||
SemaphoreHandle_t hMutex;
|
||||
ThreadId owner;
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
if ((TT_IS_IRQ_MODE()) || (hMutex == NULL)) {
|
||||
owner = 0;
|
||||
} else {
|
||||
owner = (ThreadId)xSemaphoreGetMutexHolder(hMutex);
|
||||
}
|
||||
|
||||
/* Return owner thread ID */
|
||||
return (owner);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* @file mutex.h
|
||||
* Mutex
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core_types.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MutexTypeNormal,
|
||||
MutexTypeRecursive,
|
||||
} MutexType;
|
||||
|
||||
typedef void Mutex;
|
||||
|
||||
/** Allocate Mutex
|
||||
*
|
||||
* @param[in] type The mutex type
|
||||
*
|
||||
* @return pointer to Mutex instance
|
||||
*/
|
||||
Mutex* tt_mutex_alloc(MutexType type);
|
||||
|
||||
/** Free Mutex
|
||||
*
|
||||
* @param instance The pointer to Mutex instance
|
||||
*/
|
||||
void tt_mutex_free(Mutex* instance);
|
||||
|
||||
/** Acquire mutex
|
||||
*
|
||||
* @param instance The pointer to Mutex instance
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_mutex_acquire(Mutex* instance, uint32_t timeout);
|
||||
|
||||
/** Release mutex
|
||||
*
|
||||
* @param instance The pointer to Mutex instance
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_mutex_release(Mutex* instance);
|
||||
|
||||
/** Get mutex owner thread id
|
||||
*
|
||||
* @param instance The pointer to Mutex instance
|
||||
*
|
||||
* @return The thread identifier.
|
||||
*/
|
||||
ThreadId tt_mutex_get_owner(Mutex* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,93 +0,0 @@
|
||||
#include "pubsub.h"
|
||||
#include "check.h"
|
||||
#include "mutex.h"
|
||||
|
||||
#include <m-list.h>
|
||||
|
||||
struct PubSubSubscription {
|
||||
PubSubCallback callback;
|
||||
void* callback_context;
|
||||
};
|
||||
|
||||
LIST_DEF(PubSubSubscriptionList, PubSubSubscription, M_POD_OPLIST);
|
||||
|
||||
struct PubSub {
|
||||
PubSubSubscriptionList_t items;
|
||||
Mutex* mutex;
|
||||
};
|
||||
|
||||
PubSub* tt_pubsub_alloc() {
|
||||
PubSub* pubsub = malloc(sizeof(PubSub));
|
||||
|
||||
pubsub->mutex = tt_mutex_alloc(MutexTypeRecursive);
|
||||
tt_assert(pubsub->mutex);
|
||||
|
||||
PubSubSubscriptionList_init(pubsub->items);
|
||||
|
||||
return pubsub;
|
||||
}
|
||||
|
||||
void tt_pubsub_free(PubSub* pubsub) {
|
||||
tt_assert(pubsub);
|
||||
|
||||
tt_check(PubSubSubscriptionList_size(pubsub->items) == 0);
|
||||
|
||||
PubSubSubscriptionList_clear(pubsub->items);
|
||||
|
||||
tt_mutex_free(pubsub->mutex);
|
||||
|
||||
free(pubsub);
|
||||
}
|
||||
|
||||
PubSubSubscription* tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_context) {
|
||||
tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk);
|
||||
// put uninitialized item to the list
|
||||
PubSubSubscription* item = PubSubSubscriptionList_push_raw(pubsub->items);
|
||||
|
||||
// initialize item
|
||||
item->callback = callback;
|
||||
item->callback_context = callback_context;
|
||||
|
||||
tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscription) {
|
||||
tt_assert(pubsub);
|
||||
tt_assert(pubsub_subscription);
|
||||
|
||||
tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk);
|
||||
bool result = false;
|
||||
|
||||
// iterate over items
|
||||
PubSubSubscriptionList_it_t it;
|
||||
for (PubSubSubscriptionList_it(it, pubsub->items); !PubSubSubscriptionList_end_p(it);
|
||||
PubSubSubscriptionList_next(it)) {
|
||||
const PubSubSubscription* item = PubSubSubscriptionList_cref(it);
|
||||
|
||||
// if the iterator is equal to our element
|
||||
if (item == pubsub_subscription) {
|
||||
PubSubSubscriptionList_remove(pubsub->items, it);
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk);
|
||||
tt_check(result);
|
||||
}
|
||||
|
||||
void tt_pubsub_publish(PubSub* pubsub, void* message) {
|
||||
tt_check(tt_mutex_acquire(pubsub->mutex, TtWaitForever) == TtStatusOk);
|
||||
|
||||
// iterate over subscribers
|
||||
PubSubSubscriptionList_it_t it;
|
||||
for (PubSubSubscriptionList_it(it, pubsub->items); !PubSubSubscriptionList_end_p(it);
|
||||
PubSubSubscriptionList_next(it)) {
|
||||
const PubSubSubscription* item = PubSubSubscriptionList_cref(it);
|
||||
item->callback(message, item->callback_context);
|
||||
}
|
||||
|
||||
tt_check(tt_mutex_release(pubsub->mutex) == TtStatusOk);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* @file pubsub.h
|
||||
* PubSub
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** PubSub Callback type */
|
||||
typedef void (*PubSubCallback)(const void* message, void* context);
|
||||
|
||||
/** PubSub type */
|
||||
typedef struct PubSub PubSub;
|
||||
|
||||
/** PubSubSubscription type */
|
||||
typedef struct PubSubSubscription PubSubSubscription;
|
||||
|
||||
/** Allocate PubSub
|
||||
*
|
||||
* Reentrable, Not threadsafe, one owner
|
||||
*
|
||||
* @return pointer to PubSub instance
|
||||
*/
|
||||
PubSub* tt_pubsub_alloc();
|
||||
|
||||
/** Free PubSub
|
||||
*
|
||||
* @param pubsub PubSub instance
|
||||
*/
|
||||
void tt_pubsub_free(PubSub* pubsub);
|
||||
|
||||
/** Subscribe to PubSub
|
||||
*
|
||||
* Threadsafe, Reentrable
|
||||
*
|
||||
* @param pubsub pointer to PubSub instance
|
||||
* @param[in] callback The callback
|
||||
* @param callback_context The callback context
|
||||
*
|
||||
* @return pointer to PubSubSubscription instance
|
||||
*/
|
||||
PubSubSubscription*
|
||||
tt_pubsub_subscribe(PubSub* pubsub, PubSubCallback callback, void* callback_context);
|
||||
|
||||
/** Unsubscribe from PubSub
|
||||
*
|
||||
* No use of `pubsub_subscription` allowed after call of this method
|
||||
* Threadsafe, Reentrable.
|
||||
*
|
||||
* @param pubsub pointer to PubSub instance
|
||||
* @param pubsub_subscription pointer to PubSubSubscription instance
|
||||
*/
|
||||
void tt_pubsub_unsubscribe(PubSub* pubsub, PubSubSubscription* pubsub_subscription);
|
||||
|
||||
/** Publish message to PubSub
|
||||
*
|
||||
* Threadsafe, Reentrable.
|
||||
*
|
||||
* @param pubsub pointer to PubSub instance
|
||||
* @param message message pointer to publish
|
||||
*/
|
||||
void tt_pubsub_publish(PubSub* pubsub, void* message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,182 +0,0 @@
|
||||
#include "secure.h"
|
||||
|
||||
#include "check.h"
|
||||
#include "log.h"
|
||||
#include "mbedtls/aes.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_cpu.h"
|
||||
#include "esp_mac.h"
|
||||
#endif
|
||||
|
||||
#define TAG "secure"
|
||||
#define TT_NVS_NAMESPACE "tt_secure"
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
/**
|
||||
* Get a key based on hardware parameters.
|
||||
* @param[out] key the output key
|
||||
*/
|
||||
static void get_hardware_key(uint8_t key[32]) {
|
||||
uint8_t mac[8];
|
||||
// MAC can be 6 or 8 bytes
|
||||
size_t mac_length = esp_mac_addr_len_get(ESP_MAC_EFUSE_FACTORY);
|
||||
TT_LOG_I(TAG, "Using MAC with length %u", mac_length);
|
||||
tt_check(mac_length <= 8);
|
||||
ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_EFUSE_FACTORY));
|
||||
|
||||
// Fill buffer with repeating MAC
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
key[i] = mac[i % mac_length];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
/**
|
||||
* The key is built up as follows:
|
||||
* - Fetch 32 bytes from NVS storage and store as key data
|
||||
* - Fetch 6-8 MAC bytes and overwrite the first 6-8 bytes of the key with this info
|
||||
*
|
||||
* When flash encryption is disabled:
|
||||
* Without the MAC data, an attack would look like this:
|
||||
* - Retrieve all the partitions from the ESP32
|
||||
* - Read the key from NVS flash
|
||||
* - Use the key to decrypt
|
||||
* With the MAC data added, an attacker would have to do much more:
|
||||
* - Retrieve all the partitions from the ESP32 (copy app)
|
||||
* - Upload custom app to retrieve internal MAC
|
||||
* - Read the key from NVS flash
|
||||
* - Re-flash original app and combine it with the MAC
|
||||
* - Use the key to decrypt
|
||||
* - Re-flash the device with original firmware.
|
||||
*
|
||||
* Adding the MAC doesn't add a lot of extra security, but I think it's worth it.
|
||||
*
|
||||
* @param[out] key the output key
|
||||
*/
|
||||
static void get_nvs_key(uint8_t key[32]) {
|
||||
nvs_handle_t handle;
|
||||
esp_err_t result = nvs_open(TT_NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
|
||||
if (result != ESP_OK) {
|
||||
TT_LOG_E(TAG, "Failed to get key from NVS (%s)", esp_err_to_name(result));
|
||||
tt_crash();
|
||||
}
|
||||
|
||||
size_t length = 32;
|
||||
if (nvs_get_blob(handle, "key", key, &length) == ESP_OK) {
|
||||
TT_LOG_I(TAG, "Fetched key from NVS (%d bytes)", length);
|
||||
tt_check(length == 32);
|
||||
} else {
|
||||
esp_cpu_cycle_count_t cycle_count = esp_cpu_get_cycle_count();
|
||||
unsigned seed = (unsigned)cycle_count;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
key[i] = (uint8_t)rand_r(&seed);
|
||||
}
|
||||
ESP_ERROR_CHECK(nvs_set_blob(handle, "key", key, 32));
|
||||
TT_LOG_I(TAG, "Stored new key in NVS");
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Performs XOR on 2 memory regions and stores it in a third
|
||||
* @param[in] in_left input buffer for XOR
|
||||
* @param[in] in_right second input buffer for XOR
|
||||
* @param[out] out output buffer for result of XOR
|
||||
* @param[in] length data length (all buffers must be at least this size)
|
||||
*/
|
||||
static void xor_key(const uint8_t* in_left, const uint8_t* in_right, uint8_t* out, size_t length) {
|
||||
for (int i = 0; i < length; ++i) {
|
||||
out[i] = in_left[i] ^ in_right[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines a stored key and a hardware key into a single reliable key value.
|
||||
* @param[out] key the key output
|
||||
*/
|
||||
static void get_key(uint8_t key[32]) {
|
||||
#if !defined(CONFIG_SECURE_BOOT) || !defined(CONFIG_SECURE_FLASH_ENC_ENABLED)
|
||||
TT_LOG_W(TAG, "Using tt_secure_* code with secure boot and/or flash encryption disabled.");
|
||||
TT_LOG_W(TAG, "An attacker with physical access to your ESP32 can decrypt your secure data.");
|
||||
#endif
|
||||
|
||||
uint8_t hardware_key[32];
|
||||
uint8_t nvs_key[32];
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
get_hardware_key(hardware_key);
|
||||
get_nvs_key(nvs_key);
|
||||
xor_key(hardware_key, nvs_key, key, 32);
|
||||
#else
|
||||
TT_LOG_W(TAG, "Using unsafe key for debugging purposes.");
|
||||
memset(key, 0, 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
void tt_secure_get_iv_from_string(const char* input, uint8_t iv[16]) {
|
||||
memset((void*)iv, 0, 16);
|
||||
char c = *input++;
|
||||
int index = 0;
|
||||
while (c) {
|
||||
iv[index] = c;
|
||||
index++;
|
||||
c = *input++;
|
||||
}
|
||||
}
|
||||
|
||||
static int tt_aes256_crypt_cbc(
|
||||
const uint8_t key[32],
|
||||
int mode,
|
||||
size_t length,
|
||||
unsigned char iv[16],
|
||||
const unsigned char* input,
|
||||
unsigned char* output
|
||||
) {
|
||||
tt_check(key && iv && input && output);
|
||||
|
||||
if ((length % AES_BLOCK_BYTES) || (length == 0)) {
|
||||
return ERR_ESP_AES_INVALID_INPUT_LENGTH;
|
||||
}
|
||||
|
||||
mbedtls_aes_context master;
|
||||
mbedtls_aes_init(&master);
|
||||
if (mode == MBEDTLS_AES_ENCRYPT) {
|
||||
mbedtls_aes_setkey_enc(&master, key, 256);
|
||||
} else {
|
||||
mbedtls_aes_setkey_dec(&master, key, 256);
|
||||
}
|
||||
int result = mbedtls_aes_crypt_cbc(&master, mode, length, iv, input, output);
|
||||
mbedtls_aes_free(&master);
|
||||
return result;
|
||||
}
|
||||
|
||||
int tt_secure_encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) {
|
||||
tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
|
||||
uint8_t key[32];
|
||||
get_key(key);
|
||||
|
||||
// TODO: Is this still needed after switching to regular AES functions?
|
||||
uint8_t iv_copy[16];
|
||||
memcpy(iv_copy, iv, sizeof(iv_copy));
|
||||
|
||||
return tt_aes256_crypt_cbc(key, MBEDTLS_AES_ENCRYPT, length, iv_copy, in_data, out_data);
|
||||
}
|
||||
|
||||
int tt_secure_decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) {
|
||||
tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
|
||||
uint8_t key[32];
|
||||
get_key(key);
|
||||
|
||||
// TODO: Is this still needed after switching to regular AES functions?
|
||||
uint8_t iv_copy[16];
|
||||
memcpy(iv_copy, iv, sizeof(iv_copy));
|
||||
|
||||
return tt_aes256_crypt_cbc(key, MBEDTLS_AES_DECRYPT, length, iv_copy, in_data, out_data);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/** @file secure.h
|
||||
*
|
||||
* @brief Hardware-bound encryption methods.
|
||||
* @warning Enable secure boot and flash encryption to increase security.
|
||||
*
|
||||
* Offers AES 256 CBC encryption with built-in key.
|
||||
* The key is built from data including:
|
||||
* - the internal factory MAC address
|
||||
* - random data stored in NVS
|
||||
*
|
||||
* It's important to use flash encryption to avoid an attacker to get
|
||||
* access to your encrypted data. If flash encryption is disabled,
|
||||
* someone can fetch the key from the partitions.
|
||||
*
|
||||
* See:
|
||||
* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/secure-boot-v2.html
|
||||
* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/security/flash-encryption.html
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Fills the IV with zeros and then copies up to 16 characters of the string into the IV.
|
||||
* @param input input text
|
||||
* @param iv output IV
|
||||
*/
|
||||
void tt_secure_get_iv_from_string(const char* input, uint8_t iv[16]);
|
||||
|
||||
/**
|
||||
* @brief Encrypt data.
|
||||
*
|
||||
* Important: Use flash encryption to increase security.
|
||||
* Important: input and output data must be aligned to 16 bytes.
|
||||
*
|
||||
* @param iv the AES IV
|
||||
* @param data_in input data
|
||||
* @param data_out output data
|
||||
* @param length data length, a multiple of 16
|
||||
* @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*)
|
||||
*/
|
||||
int tt_secure_encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Decrypt data.
|
||||
*
|
||||
* Important: Use flash encryption to increase security.
|
||||
* Important: input and output data must be aligned to 16 bytes.
|
||||
*
|
||||
* @param iv AES IV
|
||||
* @param data_in input data
|
||||
* @param data_out output data
|
||||
* @param length data length, a multiple of 16
|
||||
* @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*)
|
||||
*/
|
||||
int tt_secure_decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
#include "semaphore.h"
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
Semaphore* tt_semaphore_alloc(uint32_t max_count, uint32_t initial_count) {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
tt_assert((max_count > 0U) && (initial_count <= max_count));
|
||||
|
||||
SemaphoreHandle_t hSemaphore = NULL;
|
||||
if (max_count == 1U) {
|
||||
hSemaphore = xSemaphoreCreateBinary();
|
||||
if ((hSemaphore != NULL) && (initial_count != 0U)) {
|
||||
if (xSemaphoreGive(hSemaphore) != pdPASS) {
|
||||
vSemaphoreDelete(hSemaphore);
|
||||
hSemaphore = NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hSemaphore = xSemaphoreCreateCounting(max_count, initial_count);
|
||||
}
|
||||
|
||||
tt_check(hSemaphore);
|
||||
|
||||
return (Semaphore*)hSemaphore;
|
||||
}
|
||||
|
||||
void tt_semaphore_free(Semaphore* instance) {
|
||||
tt_assert(instance);
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
|
||||
vSemaphoreDelete(hSemaphore);
|
||||
}
|
||||
|
||||
TtStatus tt_semaphore_acquire(Semaphore* instance, uint32_t timeout) {
|
||||
tt_assert(instance);
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
TtStatus status;
|
||||
BaseType_t yield;
|
||||
|
||||
status = TtStatusOk;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
if (timeout != 0U) {
|
||||
status = TtStatusErrorParameter;
|
||||
} else {
|
||||
yield = pdFALSE;
|
||||
|
||||
if (xSemaphoreTakeFromISR(hSemaphore, &yield) != pdPASS) {
|
||||
status = TtStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (xSemaphoreTake(hSemaphore, (TickType_t)timeout) != pdPASS) {
|
||||
if (timeout != 0U) {
|
||||
status = TtStatusErrorTimeout;
|
||||
} else {
|
||||
status = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
TtStatus tt_semaphore_release(Semaphore* instance) {
|
||||
tt_assert(instance);
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
TtStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = TtStatusOk;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
|
||||
if (xSemaphoreGiveFromISR(hSemaphore, &yield) != pdTRUE) {
|
||||
stat = TtStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
} else {
|
||||
if (xSemaphoreGive(hSemaphore) != pdPASS) {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
uint32_t tt_semaphore_get_count(Semaphore* instance) {
|
||||
tt_assert(instance);
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
uint32_t count;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
// TODO: uxSemaphoreGetCountFromISR is not supported on esp-idf 5.1.2 - perhaps later on?
|
||||
#ifdef uxSemaphoreGetCountFromISR
|
||||
count = (uint32_t)uxSemaphoreGetCountFromISR(hSemaphore);
|
||||
#else
|
||||
count = (uint32_t)uxQueueMessagesWaitingFromISR((QueueHandle_t)hSemaphore);
|
||||
#endif
|
||||
} else {
|
||||
count = (uint32_t)uxSemaphoreGetCount(hSemaphore);
|
||||
}
|
||||
|
||||
/* Return number of tokens */
|
||||
return (count);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_types.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void Semaphore;
|
||||
|
||||
/** Allocate semaphore
|
||||
*
|
||||
* @param[in] max_count The maximum count
|
||||
* @param[in] initial_count The initial count
|
||||
*
|
||||
* @return pointer to Semaphore instance
|
||||
*/
|
||||
Semaphore* tt_semaphore_alloc(uint32_t max_count, uint32_t initial_count);
|
||||
|
||||
/** Free semaphore
|
||||
*
|
||||
* @param instance The pointer to Semaphore instance
|
||||
*/
|
||||
void tt_semaphore_free(Semaphore* instance);
|
||||
|
||||
/** Acquire semaphore
|
||||
*
|
||||
* @param instance The pointer to Semaphore instance
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_semaphore_acquire(Semaphore* instance, uint32_t timeout);
|
||||
|
||||
/** Release semaphore
|
||||
*
|
||||
* @param instance The pointer to Semaphore instance
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_semaphore_release(Semaphore* instance);
|
||||
|
||||
/** Get semaphore count
|
||||
*
|
||||
* @param instance The pointer to Semaphore instance
|
||||
*
|
||||
* @return Semaphore count
|
||||
*/
|
||||
uint32_t tt_semaphore_get_count(Semaphore* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,62 +0,0 @@
|
||||
#include "log.h"
|
||||
#include "service_i.h"
|
||||
#include "tactility_core.h"
|
||||
|
||||
// region Alloc/free
|
||||
|
||||
ServiceData* tt_service_alloc(const ServiceManifest* _Nonnull manifest) {
|
||||
ServiceData* data = malloc(sizeof(ServiceData));
|
||||
*data = (ServiceData) {
|
||||
.manifest = manifest,
|
||||
.mutex = tt_mutex_alloc(MutexTypeRecursive),
|
||||
.data = NULL
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
void tt_service_free(ServiceData* data) {
|
||||
tt_assert(data);
|
||||
tt_mutex_free(data->mutex);
|
||||
free(data);
|
||||
}
|
||||
|
||||
// endregion Alloc/free
|
||||
|
||||
// region Internal
|
||||
|
||||
static void tt_service_lock(ServiceData * data) {
|
||||
tt_mutex_acquire(data->mutex, MutexTypeRecursive);
|
||||
}
|
||||
|
||||
static void tt_service_unlock(ServiceData* data) {
|
||||
tt_mutex_release(data->mutex);
|
||||
}
|
||||
|
||||
// endregion Internal
|
||||
|
||||
// region Getters & Setters
|
||||
|
||||
const ServiceManifest* tt_service_get_manifest(Service service) {
|
||||
ServiceData* data = (ServiceData*)service;
|
||||
tt_service_lock(data);
|
||||
const ServiceManifest* manifest = data->manifest;
|
||||
tt_service_unlock(data);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
void tt_service_set_data(Service service, void* value) {
|
||||
ServiceData* data = (ServiceData*)service;
|
||||
tt_service_lock(data);
|
||||
data->data = value;
|
||||
tt_service_unlock(data);
|
||||
}
|
||||
|
||||
void* _Nullable tt_service_get_data(Service service) {
|
||||
ServiceData* data = (ServiceData*)service;
|
||||
tt_service_lock(data);
|
||||
void* value = data->data;
|
||||
tt_service_unlock(data);
|
||||
return value;
|
||||
}
|
||||
|
||||
// endregion Getters & Setters
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "service_manifest.h"
|
||||
|
||||
typedef void* Service;
|
||||
|
||||
const ServiceManifest* tt_service_get_manifest(Service service);
|
||||
|
||||
void tt_service_set_data(Service service, void* value);
|
||||
void* _Nullable tt_service_get_data(Service service);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "service.h"
|
||||
|
||||
#include "mutex.h"
|
||||
#include "service_manifest.h"
|
||||
|
||||
typedef struct {
|
||||
Mutex* mutex;
|
||||
const ServiceManifest* manifest;
|
||||
void* data;
|
||||
} ServiceData;
|
||||
|
||||
ServiceData* tt_service_alloc(const ServiceManifest* _Nonnull manifest);
|
||||
void tt_service_free(ServiceData* _Nonnull service);
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void* Service;
|
||||
|
||||
typedef void (*ServiceOnStart)(Service service);
|
||||
typedef void (*ServiceOnStop)(Service service);
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* The identifier by which the app is launched by the system and other apps.
|
||||
*/
|
||||
const char* _Nonnull id;
|
||||
|
||||
/**
|
||||
* Non-blocking method to call when service is started.
|
||||
*/
|
||||
const ServiceOnStart _Nullable on_start;
|
||||
|
||||
/**
|
||||
* Non-blocking method to call when service is stopped.
|
||||
*/
|
||||
const ServiceOnStop _Nullable on_stop;
|
||||
|
||||
} ServiceManifest;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "service_registry.h"
|
||||
|
||||
#include "m-dict.h"
|
||||
#include "m_cstr_dup.h"
|
||||
#include "mutex.h"
|
||||
#include "service_i.h"
|
||||
#include "tactility_core.h"
|
||||
|
||||
#define TAG "service_registry"
|
||||
|
||||
DICT_DEF2(ServiceManifestDict, const char*, M_CSTR_DUP_OPLIST, const ServiceManifest*, M_PTR_OPLIST)
|
||||
DICT_DEF2(ServiceInstanceDict, const char*, M_CSTR_DUP_OPLIST, const ServiceData*, M_PTR_OPLIST)
|
||||
|
||||
#define APP_REGISTRY_FOR_EACH(manifest_var_name, code_to_execute) \
|
||||
{ \
|
||||
service_registry_manifest_lock(); \
|
||||
ServiceManifestDict_it_t it; \
|
||||
for (ServiceManifestDict_it(it, service_manifest_dict); !ServiceManifestDict_end_p(it); ServiceManifestDict_next(it)) { \
|
||||
const ServiceManifest*(manifest_var_name) = ServiceManifestDict_cref(it)->value; \
|
||||
code_to_execute; \
|
||||
} \
|
||||
service_registry_manifest_unlock(); \
|
||||
}
|
||||
|
||||
static ServiceManifestDict_t service_manifest_dict;
|
||||
static ServiceInstanceDict_t service_instance_dict;
|
||||
static Mutex* manifest_mutex = NULL;
|
||||
static Mutex* instance_mutex = NULL;
|
||||
|
||||
void tt_service_registry_init() {
|
||||
tt_assert(manifest_mutex == NULL);
|
||||
manifest_mutex = tt_mutex_alloc(MutexTypeNormal);
|
||||
ServiceManifestDict_init(service_manifest_dict);
|
||||
|
||||
tt_assert(instance_mutex == NULL);
|
||||
instance_mutex = tt_mutex_alloc(MutexTypeNormal);
|
||||
ServiceInstanceDict_init(service_instance_dict);
|
||||
}
|
||||
|
||||
void service_registry_instance_lock() {
|
||||
tt_assert(instance_mutex != NULL);
|
||||
tt_mutex_acquire(instance_mutex, TtWaitForever);
|
||||
}
|
||||
|
||||
void service_registry_instance_unlock() {
|
||||
tt_assert(instance_mutex != NULL);
|
||||
tt_mutex_release(instance_mutex);
|
||||
}
|
||||
|
||||
void service_registry_manifest_lock() {
|
||||
tt_assert(manifest_mutex != NULL);
|
||||
tt_mutex_acquire(manifest_mutex, TtWaitForever);
|
||||
}
|
||||
|
||||
void service_registry_manifest_unlock() {
|
||||
tt_assert(manifest_mutex != NULL);
|
||||
tt_mutex_release(manifest_mutex);
|
||||
}
|
||||
void tt_service_registry_add(const ServiceManifest _Nonnull* manifest) {
|
||||
TT_LOG_I(TAG, "adding %s", manifest->id);
|
||||
|
||||
service_registry_manifest_lock();
|
||||
ServiceManifestDict_set_at(service_manifest_dict, manifest->id, manifest);
|
||||
service_registry_manifest_unlock();
|
||||
}
|
||||
|
||||
void tt_service_registry_remove(const ServiceManifest _Nonnull* manifest) {
|
||||
TT_LOG_I(TAG, "removing %s", manifest->id);
|
||||
service_registry_manifest_lock();
|
||||
ServiceManifestDict_erase(service_manifest_dict, manifest->id);
|
||||
service_registry_manifest_unlock();
|
||||
}
|
||||
|
||||
const ServiceManifest* _Nullable tt_service_registry_find_manifest_by_id(const char* id) {
|
||||
service_registry_manifest_lock();
|
||||
const ServiceManifest** _Nullable manifest = ServiceManifestDict_get(service_manifest_dict, id);
|
||||
service_registry_manifest_unlock();
|
||||
return (manifest != NULL) ? *manifest : NULL;
|
||||
}
|
||||
|
||||
ServiceData* _Nullable service_registry_find_instance_by_id(const char* id) {
|
||||
service_registry_instance_lock();
|
||||
const ServiceData** _Nullable service_ptr = ServiceInstanceDict_get(service_instance_dict, id);
|
||||
if (service_ptr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ServiceData* service = (ServiceData*)*service_ptr;
|
||||
service_registry_instance_unlock();
|
||||
return service;
|
||||
}
|
||||
|
||||
void tt_service_registry_for_each_manifest(ServiceManifestCallback callback, void* _Nullable context) {
|
||||
APP_REGISTRY_FOR_EACH(manifest, {
|
||||
callback(manifest, context);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: return proper error/status instead of BOOL
|
||||
bool tt_service_registry_start(const char* service_id) {
|
||||
TT_LOG_I(TAG, "starting %s", service_id);
|
||||
const ServiceManifest* manifest = tt_service_registry_find_manifest_by_id(service_id);
|
||||
if (manifest == NULL) {
|
||||
TT_LOG_E(TAG, "manifest not found for service %s", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
Service service = tt_service_alloc(manifest);
|
||||
manifest->on_start(service);
|
||||
|
||||
service_registry_instance_lock();
|
||||
ServiceInstanceDict_set_at(service_instance_dict, manifest->id, service);
|
||||
service_registry_instance_unlock();
|
||||
TT_LOG_I(TAG, "started %s", service_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tt_service_registry_stop(const char* service_id) {
|
||||
TT_LOG_I(TAG, "stopping %s", service_id);
|
||||
ServiceData* service = service_registry_find_instance_by_id(service_id);
|
||||
if (service == NULL) {
|
||||
TT_LOG_W(TAG, "service not running: %s", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
service->manifest->on_stop(service);
|
||||
tt_service_free(service);
|
||||
|
||||
service_registry_instance_lock();
|
||||
ServiceInstanceDict_erase(service_instance_dict, service_id);
|
||||
service_registry_instance_unlock();
|
||||
|
||||
TT_LOG_I(TAG, "stopped %s", service_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "service_manifest.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void (*ServiceManifestCallback)(const ServiceManifest*, void* context);
|
||||
|
||||
void tt_service_registry_init();
|
||||
|
||||
void tt_service_registry_add(const ServiceManifest* manifest);
|
||||
void tt_service_registry_remove(const ServiceManifest* manifest);
|
||||
const ServiceManifest _Nullable* tt_service_registry_find_manifest_by_id(const char* id);
|
||||
void tt_service_registry_for_each_manifest(ServiceManifestCallback callback, void* _Nullable context);
|
||||
|
||||
bool tt_service_registry_start(const char* service_id);
|
||||
bool tt_service_registry_stop(const char* service_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
#include "stream_buffer.h"
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
#include "core_types.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/stream_buffer.h"
|
||||
|
||||
StreamBuffer* tt_stream_buffer_alloc(size_t size, size_t trigger_level) {
|
||||
tt_assert(size != 0);
|
||||
|
||||
StreamBufferHandle_t handle = xStreamBufferCreate(size, trigger_level);
|
||||
tt_check(handle);
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
void tt_stream_buffer_free(StreamBuffer* stream_buffer) {
|
||||
tt_assert(stream_buffer);
|
||||
vStreamBufferDelete(stream_buffer);
|
||||
};
|
||||
|
||||
bool tt_stream_set_trigger_level(StreamBuffer* stream_buffer, size_t trigger_level) {
|
||||
tt_assert(stream_buffer);
|
||||
return xStreamBufferSetTriggerLevel(stream_buffer, trigger_level) == pdTRUE;
|
||||
};
|
||||
|
||||
size_t tt_stream_buffer_send(
|
||||
StreamBuffer* stream_buffer,
|
||||
const void* data,
|
||||
size_t length,
|
||||
uint32_t timeout
|
||||
) {
|
||||
size_t ret;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
BaseType_t yield;
|
||||
ret = xStreamBufferSendFromISR(stream_buffer, data, length, &yield);
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
ret = xStreamBufferSend(stream_buffer, data, length, timeout);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
size_t tt_stream_buffer_receive(
|
||||
StreamBuffer* stream_buffer,
|
||||
void* data,
|
||||
size_t length,
|
||||
uint32_t timeout
|
||||
) {
|
||||
size_t ret;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
BaseType_t yield;
|
||||
ret = xStreamBufferReceiveFromISR(stream_buffer, data, length, &yield);
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
ret = xStreamBufferReceive(stream_buffer, data, length, timeout);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t tt_stream_buffer_bytes_available(StreamBuffer* stream_buffer) {
|
||||
return xStreamBufferBytesAvailable(stream_buffer);
|
||||
};
|
||||
|
||||
size_t tt_stream_buffer_spaces_available(StreamBuffer* stream_buffer) {
|
||||
return xStreamBufferSpacesAvailable(stream_buffer);
|
||||
};
|
||||
|
||||
bool tt_stream_buffer_is_full(StreamBuffer* stream_buffer) {
|
||||
return xStreamBufferIsFull(stream_buffer) == pdTRUE;
|
||||
};
|
||||
|
||||
bool tt_stream_buffer_is_empty(StreamBuffer* stream_buffer) {
|
||||
return (xStreamBufferIsEmpty(stream_buffer) == pdTRUE);
|
||||
};
|
||||
|
||||
TtStatus tt_stream_buffer_reset(StreamBuffer* stream_buffer) {
|
||||
if (xStreamBufferReset(stream_buffer) == pdPASS) {
|
||||
return TtStatusOk;
|
||||
} else {
|
||||
return TtStatusError;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* @file stream_buffer.h
|
||||
* Tactility stream buffer primitive.
|
||||
*
|
||||
* Stream buffers are used to send a continuous stream of data from one task or
|
||||
* interrupt to another. Their implementation is light weight, making them
|
||||
* particularly suited for interrupt to task and core to core communication
|
||||
* scenarios.
|
||||
*
|
||||
* ***NOTE***: Stream buffer implementation assumes there is only one task or
|
||||
* interrupt that will write to the buffer (the writer), and only one task or
|
||||
* interrupt that will read from the buffer (the reader).
|
||||
*/
|
||||
#pragma once
|
||||
#include "core_types.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void StreamBuffer;
|
||||
|
||||
/**
|
||||
* @brief Allocate stream buffer instance.
|
||||
* Stream buffer implementation assumes there is only one task or
|
||||
* interrupt that will write to the buffer (the writer), and only one task or
|
||||
* interrupt that will read from the buffer (the reader).
|
||||
*
|
||||
* @param size The total number of bytes the stream buffer will be able to hold at any one time.
|
||||
* @param trigger_level The number of bytes that must be in the stream buffer
|
||||
* before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state.
|
||||
* @return The stream buffer instance.
|
||||
*/
|
||||
StreamBuffer* tt_stream_buffer_alloc(size_t size, size_t trigger_level);
|
||||
|
||||
/**
|
||||
* @brief Free stream buffer instance
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
*/
|
||||
void tt_stream_buffer_free(StreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Set trigger level for stream buffer.
|
||||
* A stream buffer's trigger level is the number of bytes that must be in the
|
||||
* stream buffer before a task that is blocked on the stream buffer to
|
||||
* wait for data is moved out of the blocked state.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance
|
||||
* @param trigger_level The new trigger level for the stream buffer.
|
||||
* @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length).
|
||||
* @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length).
|
||||
*/
|
||||
bool tt_stream_set_trigger_level(StreamBuffer* stream_buffer, size_t trigger_level);
|
||||
|
||||
/**
|
||||
* @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer.
|
||||
* Wakes up task waiting for data to become available if called from ISR.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @param data A pointer to the data that is to be copied into the stream buffer.
|
||||
* @param length The maximum number of bytes to copy from data into the stream buffer.
|
||||
* @param timeout The maximum amount of time the task should remain in the
|
||||
* Blocked state to wait for space to become available if the stream buffer is full.
|
||||
* Will return immediately if timeout is zero.
|
||||
* Setting timeout to TtWaitForever will cause the task to wait indefinitely.
|
||||
* Ignored if called from ISR.
|
||||
* @return The number of bytes actually written to the stream buffer.
|
||||
*/
|
||||
size_t tt_stream_buffer_send(
|
||||
StreamBuffer* stream_buffer,
|
||||
const void* data,
|
||||
size_t length,
|
||||
uint32_t timeout
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Receives bytes from a stream buffer.
|
||||
* Wakes up task waiting for space to become available if called from ISR.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @param data A pointer to the buffer into which the received bytes will be
|
||||
* copied.
|
||||
* @param length The length of the buffer pointed to by the data parameter.
|
||||
* @param timeout The maximum amount of time the task should remain in the
|
||||
* Blocked state to wait for data to become available if the stream buffer is empty.
|
||||
* Will return immediately if timeout is zero.
|
||||
* Setting timeout to TtWaitForever will cause the task to wait indefinitely.
|
||||
* Ignored if called from ISR.
|
||||
* @return The number of bytes read from the stream buffer, if any.
|
||||
*/
|
||||
size_t tt_stream_buffer_receive(
|
||||
StreamBuffer* stream_buffer,
|
||||
void* data,
|
||||
size_t length,
|
||||
uint32_t timeout
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see how much data it contains, which is equal to
|
||||
* the number of bytes that can be read from the stream buffer before the stream
|
||||
* buffer would be empty.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return The number of bytes that can be read from the stream buffer before
|
||||
* the stream buffer would be empty.
|
||||
*/
|
||||
size_t tt_stream_buffer_bytes_available(StreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see how much free space it contains, which is
|
||||
* equal to the amount of data that can be sent to the stream buffer before it
|
||||
* is full.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return The number of bytes that can be written to the stream buffer before
|
||||
* the stream buffer would be full.
|
||||
*/
|
||||
size_t tt_stream_buffer_spaces_available(StreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see if it is full.
|
||||
*
|
||||
* @param stream_buffer stream buffer instance.
|
||||
* @return true if the stream buffer is full.
|
||||
* @return false if the stream buffer is not full.
|
||||
*/
|
||||
bool tt_stream_buffer_is_full(StreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see if it is empty.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return true if the stream buffer is empty.
|
||||
* @return false if the stream buffer is not empty.
|
||||
*/
|
||||
bool tt_stream_buffer_is_empty(StreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Resets a stream buffer to its initial, empty, state. Any data that was
|
||||
* in the stream buffer is discarded. A stream buffer can only be reset if there
|
||||
* are no tasks blocked waiting to either send to or receive from the stream buffer.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return TtStatusOk if the stream buffer is reset.
|
||||
* @return TtStatusError if there was a task blocked waiting to send to or read
|
||||
* from the stream buffer then the stream buffer is not reset.
|
||||
*/
|
||||
TtStatus tt_stream_buffer_reset(StreamBuffer* stream_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "app.h"
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
#include "core_extra_defines.h"
|
||||
#include "core_types.h"
|
||||
#include "critical.h"
|
||||
#include "event_flag.h"
|
||||
#include "log.h"
|
||||
#include "service.h"
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define TT_CONFIG_THREAD_MAX_PRIORITIES (32)
|
||||
@@ -1,531 +0,0 @@
|
||||
#include "thread.h"
|
||||
|
||||
#include "check.h"
|
||||
#include "core_defines.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "kernel.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Thread"
|
||||
|
||||
#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
|
||||
|
||||
// Limits
|
||||
#define MAX_BITS_TASK_NOTIFY 31U
|
||||
#define MAX_BITS_EVENT_GROUPS 24U
|
||||
|
||||
#define THREAD_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_TASK_NOTIFY) - 1U))
|
||||
#define EVENT_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_EVENT_GROUPS) - 1U))
|
||||
|
||||
struct Thread {
|
||||
ThreadState state;
|
||||
int32_t ret;
|
||||
|
||||
ThreadCallback callback;
|
||||
void* context;
|
||||
|
||||
ThreadStateCallback state_callback;
|
||||
void* state_context;
|
||||
|
||||
char* name;
|
||||
char* appid;
|
||||
|
||||
ThreadPriority priority;
|
||||
|
||||
TaskHandle_t task_handle;
|
||||
|
||||
// Keep all non-alignable byte types in one place,
|
||||
// this ensures that the size of this structure is minimal
|
||||
bool is_static;
|
||||
|
||||
configSTACK_DEPTH_TYPE stack_size;
|
||||
};
|
||||
|
||||
/** Catch threads that are trying to exit wrong way */
|
||||
__attribute__((__noreturn__)) void tt_thread_catch() { //-V1082
|
||||
// If you're here it means you're probably doing something wrong
|
||||
// with critical sections or with scheduler state
|
||||
asm volatile("nop"); // extra magic
|
||||
tt_crash("You are doing it wrong"); //-V779
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static void tt_thread_set_state(Thread* thread, ThreadState state) {
|
||||
tt_assert(thread);
|
||||
thread->state = state;
|
||||
if (thread->state_callback) {
|
||||
thread->state_callback(state, thread->state_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void tt_thread_body(void* context) {
|
||||
tt_assert(context);
|
||||
Thread* thread = context;
|
||||
|
||||
// store thread instance to thread local storage
|
||||
tt_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) == NULL);
|
||||
vTaskSetThreadLocalStoragePointer(NULL, 0, thread);
|
||||
|
||||
tt_assert(thread->state == ThreadStateStarting);
|
||||
tt_thread_set_state(thread, ThreadStateRunning);
|
||||
|
||||
thread->ret = thread->callback(thread->context);
|
||||
|
||||
tt_assert(thread->state == ThreadStateRunning);
|
||||
|
||||
if (thread->is_static) {
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"%s service thread TCB memory will not be reclaimed",
|
||||
thread->name ? thread->name : "<unnamed service>"
|
||||
);
|
||||
}
|
||||
|
||||
tt_thread_set_state(thread, ThreadStateStopped);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
tt_thread_catch();
|
||||
}
|
||||
|
||||
Thread* tt_thread_alloc() {
|
||||
Thread* thread = malloc(sizeof(Thread));
|
||||
// TODO: create default struct instead of using memset()
|
||||
memset(thread, 0, sizeof(Thread));
|
||||
thread->is_static = false;
|
||||
|
||||
Thread* parent = NULL;
|
||||
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
// TLS is not available, if we called not from thread context
|
||||
parent = pvTaskGetThreadLocalStoragePointer(NULL, 0);
|
||||
|
||||
if (parent && parent->appid) {
|
||||
tt_thread_set_appid(thread, parent->appid);
|
||||
} else {
|
||||
tt_thread_set_appid(thread, "unknown");
|
||||
}
|
||||
} else {
|
||||
// if scheduler is not started, we are starting driver thread
|
||||
tt_thread_set_appid(thread, "driver");
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
Thread* tt_thread_alloc_ex(
|
||||
const char* name,
|
||||
uint32_t stack_size,
|
||||
ThreadCallback callback,
|
||||
void* context
|
||||
) {
|
||||
Thread* thread = tt_thread_alloc();
|
||||
tt_thread_set_name(thread, name);
|
||||
tt_thread_set_stack_size(thread, stack_size);
|
||||
tt_thread_set_callback(thread, callback);
|
||||
tt_thread_set_context(thread, context);
|
||||
return thread;
|
||||
}
|
||||
|
||||
void tt_thread_free(Thread* thread) {
|
||||
tt_assert(thread);
|
||||
|
||||
// Ensure that use join before free
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
tt_assert(thread->task_handle == NULL);
|
||||
|
||||
if (thread->name) free(thread->name);
|
||||
if (thread->appid) free(thread->appid);
|
||||
|
||||
free(thread);
|
||||
}
|
||||
|
||||
void tt_thread_set_name(Thread* thread, const char* name) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
if (thread->name) free(thread->name);
|
||||
thread->name = name ? strdup(name) : NULL;
|
||||
}
|
||||
|
||||
void tt_thread_set_appid(Thread* thread, const char* appid) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
if (thread->appid) free(thread->appid);
|
||||
thread->appid = appid ? strdup(appid) : NULL;
|
||||
}
|
||||
|
||||
void tt_thread_mark_as_static(Thread* thread) {
|
||||
thread->is_static = true;
|
||||
}
|
||||
|
||||
bool tt_thread_mark_is_service(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
assert(!TT_IS_IRQ_MODE() && (hTask != NULL));
|
||||
Thread* thread = (Thread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
|
||||
assert(thread != NULL);
|
||||
return thread->is_static;
|
||||
}
|
||||
|
||||
void tt_thread_set_stack_size(Thread* thread, size_t stack_size) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
tt_assert(stack_size % 4 == 0);
|
||||
thread->stack_size = stack_size;
|
||||
}
|
||||
|
||||
void tt_thread_set_callback(Thread* thread, ThreadCallback callback) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
thread->callback = callback;
|
||||
}
|
||||
|
||||
void tt_thread_set_context(Thread* thread, void* context) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
thread->context = context;
|
||||
}
|
||||
|
||||
void tt_thread_set_priority(Thread* thread, ThreadPriority priority) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
tt_assert(priority >= ThreadPriorityIdle && priority <= ThreadPriorityIsr);
|
||||
thread->priority = priority;
|
||||
}
|
||||
|
||||
void tt_thread_set_current_priority(ThreadPriority priority) {
|
||||
UBaseType_t new_priority = priority ? priority : ThreadPriorityNormal;
|
||||
vTaskPrioritySet(NULL, new_priority);
|
||||
}
|
||||
|
||||
ThreadPriority tt_thread_get_current_priority() {
|
||||
return (ThreadPriority)uxTaskPriorityGet(NULL);
|
||||
}
|
||||
|
||||
void tt_thread_set_state_callback(Thread* thread, ThreadStateCallback callback) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
thread->state_callback = callback;
|
||||
}
|
||||
|
||||
void tt_thread_set_state_context(Thread* thread, void* context) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
thread->state_context = context;
|
||||
}
|
||||
|
||||
ThreadState tt_thread_get_state(Thread* thread) {
|
||||
tt_assert(thread);
|
||||
return thread->state;
|
||||
}
|
||||
|
||||
void tt_thread_start(Thread* thread) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->callback);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
tt_assert(thread->stack_size > 0 && thread->stack_size < (UINT16_MAX * sizeof(StackType_t)));
|
||||
|
||||
tt_thread_set_state(thread, ThreadStateStarting);
|
||||
|
||||
uint32_t stack = thread->stack_size / sizeof(StackType_t);
|
||||
UBaseType_t priority = thread->priority ? thread->priority : ThreadPriorityNormal;
|
||||
if (thread->is_static) {
|
||||
thread->task_handle = xTaskCreateStatic(
|
||||
tt_thread_body,
|
||||
thread->name,
|
||||
stack,
|
||||
thread,
|
||||
priority,
|
||||
malloc(sizeof(StackType_t) * stack),
|
||||
malloc(sizeof(StaticTask_t))
|
||||
);
|
||||
} else {
|
||||
BaseType_t ret = xTaskCreate(
|
||||
tt_thread_body, thread->name, stack, thread, priority, &thread->task_handle
|
||||
);
|
||||
tt_check(ret == pdPASS);
|
||||
}
|
||||
|
||||
tt_check(thread->task_handle);
|
||||
}
|
||||
|
||||
void tt_thread_cleanup_tcb_event(TaskHandle_t task) {
|
||||
Thread* thread = pvTaskGetThreadLocalStoragePointer(task, 0);
|
||||
if (thread) {
|
||||
// clear thread local storage
|
||||
vTaskSetThreadLocalStoragePointer(task, 0, NULL);
|
||||
tt_assert(thread->task_handle == task);
|
||||
thread->task_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool tt_thread_join(Thread* thread) {
|
||||
tt_assert(thread);
|
||||
|
||||
tt_check(tt_thread_get_current() != thread);
|
||||
|
||||
// !!! IMPORTANT NOTICE !!!
|
||||
//
|
||||
// If your thread exited, but your app stuck here: some other thread uses
|
||||
// all cpu time, which delays kernel from releasing task handle
|
||||
while (thread->task_handle) {
|
||||
tt_delay_ms(10);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ThreadId tt_thread_get_id(Thread* thread) {
|
||||
tt_assert(thread);
|
||||
return thread->task_handle;
|
||||
}
|
||||
|
||||
int32_t tt_thread_get_return_code(Thread* thread) {
|
||||
tt_assert(thread);
|
||||
tt_assert(thread->state == ThreadStateStopped);
|
||||
return thread->ret;
|
||||
}
|
||||
|
||||
ThreadId tt_thread_get_current_id() {
|
||||
return xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
Thread* tt_thread_get_current() {
|
||||
Thread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0);
|
||||
return thread;
|
||||
}
|
||||
|
||||
void tt_thread_yield() {
|
||||
tt_assert(!TT_IS_IRQ_MODE());
|
||||
taskYIELD();
|
||||
}
|
||||
|
||||
uint32_t tt_thread_flags_set(ThreadId thread_id, uint32_t flags) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
uint32_t rflags;
|
||||
BaseType_t yield;
|
||||
|
||||
if ((hTask == NULL) || ((flags & THREAD_FLAGS_INVALID_BITS) != 0U)) {
|
||||
rflags = (uint32_t)TtStatusErrorParameter;
|
||||
} else {
|
||||
rflags = (uint32_t)TtStatusError;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
|
||||
(void)xTaskNotifyIndexedFromISR(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits, &yield);
|
||||
(void)xTaskNotifyAndQueryIndexedFromISR(
|
||||
hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags, NULL
|
||||
);
|
||||
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
(void)xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits);
|
||||
(void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags);
|
||||
}
|
||||
}
|
||||
/* Return flags after setting */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_thread_flags_clear(uint32_t flags) {
|
||||
TaskHandle_t hTask;
|
||||
uint32_t rflags, cflags;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
rflags = (uint32_t)TtStatusErrorISR;
|
||||
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
|
||||
rflags = (uint32_t)TtStatusErrorParameter;
|
||||
} else {
|
||||
hTask = xTaskGetCurrentTaskHandle();
|
||||
|
||||
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &cflags) ==
|
||||
pdPASS) {
|
||||
rflags = cflags;
|
||||
cflags &= ~flags;
|
||||
|
||||
if (xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) !=
|
||||
pdPASS) {
|
||||
rflags = (uint32_t)TtStatusError;
|
||||
}
|
||||
} else {
|
||||
rflags = (uint32_t)TtStatusError;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_thread_flags_get(void) {
|
||||
TaskHandle_t hTask;
|
||||
uint32_t rflags;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
rflags = (uint32_t)TtStatusErrorISR;
|
||||
} else {
|
||||
hTask = xTaskGetCurrentTaskHandle();
|
||||
|
||||
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) !=
|
||||
pdPASS) {
|
||||
rflags = (uint32_t)TtStatusError;
|
||||
}
|
||||
}
|
||||
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
|
||||
uint32_t rflags, nval;
|
||||
uint32_t clear;
|
||||
TickType_t t0, td, tout;
|
||||
BaseType_t rval;
|
||||
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
rflags = (uint32_t)TtStatusErrorISR;
|
||||
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
|
||||
rflags = (uint32_t)TtStatusErrorParameter;
|
||||
} else {
|
||||
if ((options & TtFlagNoClear) == TtFlagNoClear) {
|
||||
clear = 0U;
|
||||
} else {
|
||||
clear = flags;
|
||||
}
|
||||
|
||||
rflags = 0U;
|
||||
tout = timeout;
|
||||
|
||||
t0 = xTaskGetTickCount();
|
||||
do {
|
||||
rval = xTaskNotifyWaitIndexed(THREAD_NOTIFY_INDEX, 0, clear, &nval, tout);
|
||||
|
||||
if (rval == pdPASS) {
|
||||
rflags &= flags;
|
||||
rflags |= nval;
|
||||
|
||||
if ((options & TtFlagWaitAll) == TtFlagWaitAll) {
|
||||
if ((flags & rflags) == flags) {
|
||||
break;
|
||||
} else {
|
||||
if (timeout == 0U) {
|
||||
rflags = (uint32_t)TtStatusErrorResource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((flags & rflags) != 0) {
|
||||
break;
|
||||
} else {
|
||||
if (timeout == 0U) {
|
||||
rflags = (uint32_t)TtStatusErrorResource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update timeout */
|
||||
td = xTaskGetTickCount() - t0;
|
||||
|
||||
if (td > tout) {
|
||||
tout = 0;
|
||||
} else {
|
||||
tout -= td;
|
||||
}
|
||||
} else {
|
||||
if (timeout == 0) {
|
||||
rflags = (uint32_t)TtStatusErrorResource;
|
||||
} else {
|
||||
rflags = (uint32_t)TtStatusErrorTimeout;
|
||||
}
|
||||
}
|
||||
} while (rval != pdFAIL);
|
||||
}
|
||||
|
||||
/* Return flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t tt_thread_enumerate(ThreadId* thread_array, uint32_t array_items) {
|
||||
uint32_t i, count;
|
||||
TaskStatus_t* task;
|
||||
|
||||
if (TT_IS_IRQ_MODE() || (thread_array == NULL) || (array_items == 0U)) {
|
||||
count = 0U;
|
||||
} else {
|
||||
vTaskSuspendAll();
|
||||
|
||||
count = uxTaskGetNumberOfTasks();
|
||||
task = pvPortMalloc(count * sizeof(TaskStatus_t));
|
||||
|
||||
if (task != NULL) {
|
||||
count = uxTaskGetSystemState(task, count, NULL);
|
||||
|
||||
for (i = 0U; (i < count) && (i < array_items); i++) {
|
||||
thread_array[i] = (ThreadId)task[i].xHandle;
|
||||
}
|
||||
count = i;
|
||||
}
|
||||
(void)xTaskResumeAll();
|
||||
|
||||
vPortFree(task);
|
||||
}
|
||||
|
||||
return (count);
|
||||
}
|
||||
|
||||
const char* tt_thread_get_name(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
const char* name;
|
||||
|
||||
if (TT_IS_IRQ_MODE() || (hTask == NULL)) {
|
||||
name = NULL;
|
||||
} else {
|
||||
name = pcTaskGetName(hTask);
|
||||
}
|
||||
|
||||
return (name);
|
||||
}
|
||||
|
||||
const char* tt_thread_get_appid(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
const char* appid = "system";
|
||||
|
||||
if (!TT_IS_IRQ_MODE() && (hTask != NULL)) {
|
||||
Thread* thread = (Thread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
|
||||
if (thread) {
|
||||
appid = thread->appid;
|
||||
}
|
||||
}
|
||||
|
||||
return (appid);
|
||||
}
|
||||
|
||||
uint32_t tt_thread_get_stack_space(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
uint32_t sz;
|
||||
|
||||
if (TT_IS_IRQ_MODE() || (hTask == NULL)) {
|
||||
sz = 0U;
|
||||
} else {
|
||||
sz = (uint32_t)(uxTaskGetStackHighWaterMark(hTask) * sizeof(StackType_t));
|
||||
}
|
||||
|
||||
return (sz);
|
||||
}
|
||||
|
||||
void tt_thread_suspend(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
vTaskSuspend(hTask);
|
||||
}
|
||||
|
||||
void tt_thread_resume(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
if (TT_IS_IRQ_MODE()) {
|
||||
xTaskResumeFromISR(hTask);
|
||||
} else {
|
||||
vTaskResume(hTask);
|
||||
}
|
||||
}
|
||||
|
||||
bool tt_thread_is_suspended(ThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
return eTaskGetState(hTask) == eSuspended;
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_defines.h"
|
||||
#include "core_types.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** ThreadState */
|
||||
typedef enum {
|
||||
ThreadStateStopped,
|
||||
ThreadStateStarting,
|
||||
ThreadStateRunning,
|
||||
} ThreadState;
|
||||
|
||||
/** ThreadPriority */
|
||||
typedef enum {
|
||||
ThreadPriorityNone = 0, /**< Uninitialized, choose system default */
|
||||
ThreadPriorityIdle = 1, /**< Idle priority */
|
||||
ThreadPriorityLowest = 14, /**< Lowest */
|
||||
ThreadPriorityLow = 15, /**< Low */
|
||||
ThreadPriorityNormal = 16, /**< Normal */
|
||||
ThreadPriorityHigh = 17, /**< High */
|
||||
ThreadPriorityHighest = 18, /**< Highest */
|
||||
ThreadPriorityIsr =
|
||||
(TT_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */
|
||||
} ThreadPriority;
|
||||
|
||||
/** Thread anonymous structure */
|
||||
typedef struct Thread Thread;
|
||||
|
||||
/** ThreadId proxy type to OS low level functions */
|
||||
typedef void* ThreadId;
|
||||
|
||||
/** ThreadCallback Your callback to run in new thread
|
||||
* @warning never use osThreadExit in Thread
|
||||
*/
|
||||
typedef int32_t (*ThreadCallback)(void* context);
|
||||
|
||||
/** Write to stdout callback
|
||||
* @param data pointer to data
|
||||
* @param size data size @warning your handler must consume everything
|
||||
*/
|
||||
typedef void (*ThreadStdoutWriteCallback)(const char* data, size_t size);
|
||||
|
||||
/** Thread state change callback called upon thread state change
|
||||
* @param state new thread state
|
||||
* @param context callback context
|
||||
*/
|
||||
typedef void (*ThreadStateCallback)(ThreadState state, void* context);
|
||||
|
||||
/** Allocate Thread
|
||||
*
|
||||
* @return Thread instance
|
||||
*/
|
||||
Thread* tt_thread_alloc();
|
||||
|
||||
/** Allocate Thread, shortcut version
|
||||
*
|
||||
* @param name
|
||||
* @param stack_size
|
||||
* @param callback
|
||||
* @param context
|
||||
* @return Thread*
|
||||
*/
|
||||
Thread* tt_thread_alloc_ex(
|
||||
const char* name,
|
||||
uint32_t stack_size,
|
||||
ThreadCallback callback,
|
||||
void* context
|
||||
);
|
||||
|
||||
/** Release Thread
|
||||
*
|
||||
* @warning see tt_thread_join
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*/
|
||||
void tt_thread_free(Thread* thread);
|
||||
|
||||
/** Set Thread name
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param name string
|
||||
*/
|
||||
void tt_thread_set_name(Thread* thread, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Set Thread appid
|
||||
* Technically, it is like a "process id", but it is not a system-wide unique identifier.
|
||||
* All threads spawned by the same app will have the same appid.
|
||||
*
|
||||
* @param thread
|
||||
* @param appid
|
||||
*/
|
||||
void tt_thread_set_appid(Thread* thread, const char* appid);
|
||||
|
||||
/** Mark thread as service
|
||||
* The service cannot be stopped or removed, and cannot exit from the thread body
|
||||
*
|
||||
* @param thread
|
||||
*/
|
||||
void tt_thread_mark_as_static(Thread* thread);
|
||||
|
||||
/** Set Thread stack size
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param stack_size stack size in bytes
|
||||
*/
|
||||
void tt_thread_set_stack_size(Thread* thread, size_t stack_size);
|
||||
|
||||
/** Set Thread callback
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param callback ThreadCallback, called upon thread run
|
||||
*/
|
||||
void tt_thread_set_callback(Thread* thread, ThreadCallback callback);
|
||||
|
||||
/** Set Thread context
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param context pointer to context for thread callback
|
||||
*/
|
||||
void tt_thread_set_context(Thread* thread, void* context);
|
||||
|
||||
/** Set Thread priority
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param priority ThreadPriority value
|
||||
*/
|
||||
void tt_thread_set_priority(Thread* thread, ThreadPriority priority);
|
||||
|
||||
/** Set current thread priority
|
||||
*
|
||||
* @param priority ThreadPriority value
|
||||
*/
|
||||
void tt_thread_set_current_priority(ThreadPriority priority);
|
||||
|
||||
/** Get current thread priority
|
||||
*
|
||||
* @return ThreadPriority value
|
||||
*/
|
||||
ThreadPriority tt_thread_get_current_priority();
|
||||
|
||||
/** Set Thread state change callback
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param callback state change callback
|
||||
*/
|
||||
void tt_thread_set_state_callback(Thread* thread, ThreadStateCallback callback);
|
||||
|
||||
/** Set Thread state change context
|
||||
*
|
||||
* @param thread Thread instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void tt_thread_set_state_context(Thread* thread, void* context);
|
||||
|
||||
/** Get Thread state
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return thread state from ThreadState
|
||||
*/
|
||||
ThreadState tt_thread_get_state(Thread* thread);
|
||||
|
||||
/** Start Thread
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*/
|
||||
void tt_thread_start(Thread* thread);
|
||||
|
||||
/** Join Thread
|
||||
*
|
||||
* @warning Use this method only when CPU is not busy(Idle task receives
|
||||
* control), otherwise it will wait forever.
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
bool tt_thread_join(Thread* thread);
|
||||
|
||||
/** Get FreeRTOS ThreadId for Thread instance
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return ThreadId or NULL
|
||||
*/
|
||||
ThreadId tt_thread_get_id(Thread* thread);
|
||||
|
||||
/** Enable heap tracing
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*/
|
||||
void tt_thread_enable_heap_trace(Thread* thread);
|
||||
|
||||
/** Disable heap tracing
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*/
|
||||
void tt_thread_disable_heap_trace(Thread* thread);
|
||||
|
||||
/** Get thread heap size
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
size_t tt_thread_get_heap_size(Thread* thread);
|
||||
|
||||
/** Get thread return code
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return return code
|
||||
*/
|
||||
int32_t tt_thread_get_return_code(Thread* thread);
|
||||
|
||||
/** Thread related methods that doesn't involve Thread directly */
|
||||
|
||||
/** Get FreeRTOS ThreadId for current thread
|
||||
*
|
||||
* @param thread Thread instance
|
||||
*
|
||||
* @return ThreadId or NULL
|
||||
*/
|
||||
ThreadId tt_thread_get_current_id();
|
||||
|
||||
/** Get Thread instance for current thread
|
||||
*
|
||||
* @return pointer to Thread or NULL if this thread doesn't belongs to Tactility
|
||||
*/
|
||||
Thread* tt_thread_get_current();
|
||||
|
||||
/** Return control to scheduler */
|
||||
void tt_thread_yield();
|
||||
|
||||
uint32_t tt_thread_flags_set(ThreadId thread_id, uint32_t flags);
|
||||
|
||||
uint32_t tt_thread_flags_clear(uint32_t flags);
|
||||
|
||||
uint32_t tt_thread_flags_get(void);
|
||||
|
||||
uint32_t tt_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Enumerate threads
|
||||
*
|
||||
* @param thread_array array of ThreadId, where thread ids will be stored
|
||||
* @param array_items array size
|
||||
* @return uint32_t threads count
|
||||
*/
|
||||
uint32_t tt_thread_enumerate(ThreadId* thread_array, uint32_t array_items);
|
||||
|
||||
/**
|
||||
* @brief Get thread name
|
||||
*
|
||||
* @param thread_id
|
||||
* @return const char* name or NULL
|
||||
*/
|
||||
const char* tt_thread_get_name(ThreadId thread_id);
|
||||
|
||||
/**
|
||||
* @brief Get thread appid
|
||||
*
|
||||
* @param thread_id
|
||||
* @return const char* appid
|
||||
*/
|
||||
const char* tt_thread_get_appid(ThreadId thread_id);
|
||||
|
||||
/**
|
||||
* @brief Get thread stack watermark
|
||||
*
|
||||
* @param thread_id
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t tt_thread_get_stack_space(ThreadId thread_id);
|
||||
|
||||
/** Get STDOUT callback for thead
|
||||
*
|
||||
* @return STDOUT callback
|
||||
*/
|
||||
ThreadStdoutWriteCallback tt_thread_get_stdout_callback();
|
||||
|
||||
/** Set STDOUT callback for thread
|
||||
*
|
||||
* @param callback callback or NULL to clear
|
||||
*/
|
||||
void tt_thread_set_stdout_callback(ThreadStdoutWriteCallback callback);
|
||||
|
||||
/** Write data to buffered STDOUT
|
||||
*
|
||||
* @param data input data
|
||||
* @param size input data size
|
||||
*
|
||||
* @return size_t written data size
|
||||
*/
|
||||
size_t tt_thread_stdout_write(const char* data, size_t size);
|
||||
|
||||
/** Flush data to STDOUT
|
||||
*
|
||||
* @return int32_t error code
|
||||
*/
|
||||
int32_t tt_thread_stdout_flush();
|
||||
|
||||
/** Suspend thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
*/
|
||||
void tt_thread_suspend(ThreadId thread_id);
|
||||
|
||||
/** Resume thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
*/
|
||||
void tt_thread_resume(ThreadId thread_id);
|
||||
|
||||
/** Get thread suspended state
|
||||
*
|
||||
* @param thread_id thread id
|
||||
* @return true if thread is suspended
|
||||
*/
|
||||
bool tt_thread_is_suspended(ThreadId thread_id);
|
||||
|
||||
bool tt_thread_mark_is_service(ThreadId thread_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,173 +0,0 @@
|
||||
#include "timer.h"
|
||||
#include "check.h"
|
||||
#include "kernel.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/timers.h"
|
||||
|
||||
typedef struct {
|
||||
TimerCallback func;
|
||||
void* context;
|
||||
} TimerCallback_t;
|
||||
|
||||
static void timer_callback(TimerHandle_t hTimer) {
|
||||
TimerCallback_t* callb;
|
||||
|
||||
/* Retrieve pointer to callback function and context */
|
||||
callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer);
|
||||
|
||||
/* Remove dynamic allocation flag */
|
||||
callb = (TimerCallback_t*)((uint32_t)callb & ~1U);
|
||||
|
||||
if (callb != NULL) {
|
||||
callb->func(callb->context);
|
||||
}
|
||||
}
|
||||
|
||||
Timer* tt_timer_alloc(TimerCallback func, TimerType type, void* context) {
|
||||
tt_assert((tt_kernel_is_irq() == 0U) && (func != NULL));
|
||||
|
||||
TimerHandle_t hTimer;
|
||||
TimerCallback_t* callb;
|
||||
UBaseType_t reload;
|
||||
|
||||
hTimer = NULL;
|
||||
|
||||
/* Dynamic memory allocation is available: if memory for callback and */
|
||||
/* its context is not provided, allocate it from dynamic memory pool */
|
||||
callb = (TimerCallback_t*)malloc(sizeof(TimerCallback_t));
|
||||
|
||||
callb->func = func;
|
||||
callb->context = context;
|
||||
|
||||
if (type == TimerTypeOnce) {
|
||||
reload = pdFALSE;
|
||||
} else {
|
||||
reload = pdTRUE;
|
||||
}
|
||||
|
||||
/* Store callback memory dynamic allocation flag */
|
||||
callb = (TimerCallback_t*)((uint32_t)callb | 1U);
|
||||
// TimerCallback function is always provided as a callback and is used to call application
|
||||
// specified function with its context both stored in structure callb.
|
||||
// TODO: should we use pointer to function or function directly as-is?
|
||||
hTimer = xTimerCreate(NULL, portMAX_DELAY, reload, callb, timer_callback);
|
||||
tt_check(hTimer);
|
||||
|
||||
/* Return timer ID */
|
||||
return ((Timer*)hTimer);
|
||||
}
|
||||
|
||||
void tt_timer_free(Timer* instance) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
tt_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
TimerCallback_t* callb;
|
||||
|
||||
callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer);
|
||||
|
||||
tt_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS);
|
||||
|
||||
while (tt_timer_is_running(instance)) tt_delay_tick(2);
|
||||
|
||||
if ((uint32_t)callb & 1U) {
|
||||
/* Callback memory was allocated from dynamic pool, clear flag */
|
||||
callb = (TimerCallback_t*)((uint32_t)callb & ~1U);
|
||||
|
||||
/* Return allocated memory to dynamic pool */
|
||||
free(callb);
|
||||
}
|
||||
}
|
||||
|
||||
TtStatus tt_timer_start(Timer* instance, uint32_t ticks) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
tt_assert(instance);
|
||||
tt_assert(ticks < portMAX_DELAY);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
TtStatus stat;
|
||||
|
||||
if (xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS) {
|
||||
stat = TtStatusOk;
|
||||
} else {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
TtStatus tt_timer_restart(Timer* instance, uint32_t ticks) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
tt_assert(instance);
|
||||
tt_assert(ticks < portMAX_DELAY);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
TtStatus stat;
|
||||
|
||||
if (xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS &&
|
||||
xTimerReset(hTimer, portMAX_DELAY) == pdPASS) {
|
||||
stat = TtStatusOk;
|
||||
} else {
|
||||
stat = TtStatusErrorResource;
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
TtStatus tt_timer_stop(Timer* instance) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
tt_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
tt_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS);
|
||||
|
||||
return TtStatusOk;
|
||||
}
|
||||
|
||||
uint32_t tt_timer_is_running(Timer* instance) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
tt_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
/* Return 0: not running, 1: running */
|
||||
return (uint32_t)xTimerIsTimerActive(hTimer);
|
||||
}
|
||||
|
||||
uint32_t tt_timer_get_expire_time(Timer* instance) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
tt_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
return (uint32_t)xTimerGetExpiryTime(hTimer);
|
||||
}
|
||||
|
||||
void tt_timer_pending_callback(TimerPendigCallback callback, void* context, uint32_t arg) {
|
||||
BaseType_t ret = pdFAIL;
|
||||
if (tt_kernel_is_irq()) {
|
||||
ret = xTimerPendFunctionCallFromISR(callback, context, arg, NULL);
|
||||
} else {
|
||||
ret = xTimerPendFunctionCall(callback, context, arg, TtWaitForever);
|
||||
}
|
||||
tt_check(ret == pdPASS);
|
||||
}
|
||||
|
||||
void tt_timer_set_thread_priority(TimerThreadPriority priority) {
|
||||
tt_assert(!tt_kernel_is_irq());
|
||||
|
||||
TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle();
|
||||
tt_check(task_handle); // Don't call this method before timer task start
|
||||
|
||||
if (priority == TimerThreadPriorityNormal) {
|
||||
vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY);
|
||||
} else if (priority == TimerThreadPriorityElevated) {
|
||||
vTaskPrioritySet(task_handle, configMAX_PRIORITIES - 1);
|
||||
} else {
|
||||
tt_crash();
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*TimerCallback)(void* context);
|
||||
|
||||
typedef enum {
|
||||
TimerTypeOnce = 0, ///< One-shot timer.
|
||||
TimerTypePeriodic = 1 ///< Repeating timer.
|
||||
} TimerType;
|
||||
|
||||
typedef void Timer;
|
||||
|
||||
/** Allocate timer
|
||||
*
|
||||
* @param[in] func The callback function
|
||||
* @param[in] type The timer type
|
||||
* @param context The callback context
|
||||
*
|
||||
* @return The pointer to Timer instance
|
||||
*/
|
||||
Timer* tt_timer_alloc(TimerCallback func, TimerType type, void* context);
|
||||
|
||||
/** Free timer
|
||||
*
|
||||
* @param instance The pointer to Timer instance
|
||||
*/
|
||||
void tt_timer_free(Timer* instance);
|
||||
|
||||
/** Start timer
|
||||
*
|
||||
* @warning This is asynchronous call, real operation will happen as soon as
|
||||
* timer service process this request.
|
||||
*
|
||||
* @param instance The pointer to Timer instance
|
||||
* @param[in] ticks The interval in ticks
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_timer_start(Timer* instance, uint32_t ticks);
|
||||
|
||||
/** Restart timer with previous timeout value
|
||||
*
|
||||
* @warning This is asynchronous call, real operation will happen as soon as
|
||||
* timer service process this request.
|
||||
*
|
||||
* @param instance The pointer to Timer instance
|
||||
* @param[in] ticks The interval in ticks
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_timer_restart(Timer* instance, uint32_t ticks);
|
||||
|
||||
/** Stop timer
|
||||
*
|
||||
* @warning This is asynchronous call, real operation will happen as soon as
|
||||
* timer service process this request.
|
||||
*
|
||||
* @param instance The pointer to Timer instance
|
||||
*
|
||||
* @return The status.
|
||||
*/
|
||||
TtStatus tt_timer_stop(Timer* instance);
|
||||
|
||||
/** Is timer running
|
||||
*
|
||||
* @warning This cal may and will return obsolete timer state if timer
|
||||
* commands are still in the queue. Please read FreeRTOS timer
|
||||
* documentation first.
|
||||
*
|
||||
* @param instance The pointer to Timer instance
|
||||
*
|
||||
* @return 0: not running, 1: running
|
||||
*/
|
||||
uint32_t tt_timer_is_running(Timer* instance);
|
||||
|
||||
/** Get timer expire time
|
||||
*
|
||||
* @param instance The Timer instance
|
||||
*
|
||||
* @return expire tick
|
||||
*/
|
||||
uint32_t tt_timer_get_expire_time(Timer* instance);
|
||||
|
||||
typedef void (*TimerPendigCallback)(void* context, uint32_t arg);
|
||||
|
||||
void tt_timer_pending_callback(TimerPendigCallback callback, void* context, uint32_t arg);
|
||||
|
||||
typedef enum {
|
||||
TimerThreadPriorityNormal, /**< Lower then other threads */
|
||||
TimerThreadPriorityElevated, /**< Same as other threads */
|
||||
} TimerThreadPriority;
|
||||
|
||||
/** Set Timer thread priority
|
||||
*
|
||||
* @param[in] priority The priority
|
||||
*/
|
||||
void tt_timer_set_thread_priority(TimerThreadPriority priority);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
idf_component_register(
|
||||
REQUIRES spiffs
|
||||
)
|
||||
|
||||
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
|
||||
spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT)
|
||||
|
||||
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/config")
|
||||
spiffs_create_partition_image(config ${CONFIG_SRC_DIR} FLASH_IN_PROJECT)
|
||||
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 224 B After Width: | Height: | Size: 224 B |
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 309 B |
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 307 B |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
|
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
|
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 370 B |
@@ -19,14 +19,8 @@ idf_component_register(
|
||||
esp_wifi
|
||||
driver
|
||||
fatfs
|
||||
tactility-core
|
||||
nvs_flash
|
||||
spiffs
|
||||
)
|
||||
|
||||
set(ASSETS_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
|
||||
spiffs_create_partition_image(assets ${ASSETS_SRC_DIR} FLASH_IN_PROJECT)
|
||||
|
||||
set(CONFIG_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/config")
|
||||
spiffs_create_partition_image(config ${CONFIG_SRC_DIR} FLASH_IN_PROJECT)
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} ${IDF_TARGET_NAME} tactility-core)
|
||||
|
||||
@@ -18,7 +18,7 @@ Lvgl tt_graphics_init(Hardware _Nonnull* hardware) {
|
||||
DisplayDevice _Nonnull* display = hardware->display;
|
||||
|
||||
// Add display
|
||||
ESP_LOGD(TAG, "lvgl add display");
|
||||
TT_LOG_I(TAG, "lvgl add display");
|
||||
const lvgl_port_display_cfg_t disp_cfg = {
|
||||
.io_handle = display->io_handle,
|
||||
.panel_handle = display->display_handle,
|
||||
|
||||
@@ -4,11 +4,14 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_wifi.h"
|
||||
#include "pubsub.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
/** Radio was turned on */
|
||||
WifiEventTypeRadioStateOn,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include "wifi_credentials.h"
|
||||
|
||||
#include "nvs_flash.h"
|
||||
@@ -235,3 +237,4 @@ bool tt_wifi_credentials_remove(const char* ssid) {
|
||||
|
||||
// end region Wi-Fi Credentials - public
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef ESP_PLATFORM
|
||||
|
||||
#include "wifi_credentials.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#define TAG "wifi_credentials"
|
||||
|
||||
// region Wi-Fi Credentials - public
|
||||
|
||||
bool tt_wifi_credentials_contains(const char* ssid) {
|
||||
// TODO: Implement
|
||||
return false;
|
||||
}
|
||||
|
||||
void tt_wifi_credentials_init() {
|
||||
TT_LOG_W(TAG, "init mock implementation");
|
||||
}
|
||||
|
||||
bool tt_wifi_credentials_get(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
|
||||
// TODO: Implement
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tt_wifi_credentials_set(const char* ssid, char password[TT_WIFI_CREDENTIALS_PASSWORD_LIMIT]) {
|
||||
// TODO: Implement
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tt_wifi_credentials_remove(const char* ssid) {
|
||||
// TODO: Implement
|
||||
return false;
|
||||
}
|
||||
|
||||
// end region Wi-Fi Credentials - public
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,5 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include "wifi.h"
|
||||
|
||||
#include "check.h"
|
||||
@@ -227,16 +229,16 @@ static void wifi_publish_event_simple(Wifi* wifi, WifiEventType type) {
|
||||
static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||
UNUSED(arg);
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
ESP_LOGI(TAG, "event_handler: sta start");
|
||||
TT_LOG_I(TAG, "event_handler: sta start");
|
||||
if (wifi_singleton->radio_state == WIFI_RADIO_CONNECTION_PENDING) {
|
||||
esp_wifi_connect();
|
||||
}
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
xEventGroupSetBits(wifi_singleton->event_group, WIFI_FAIL_BIT);
|
||||
ESP_LOGI(TAG, "event_handler: disconnected");
|
||||
TT_LOG_I(TAG, "event_handler: disconnected");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
|
||||
ESP_LOGI(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
TT_LOG_I(TAG, "event_handler: got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
xEventGroupSetBits(wifi_singleton->event_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
@@ -465,15 +467,15 @@ static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_messag
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
wifi->radio_state = WIFI_RADIO_CONNECTION_ACTIVE;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeConnectionSuccess);
|
||||
ESP_LOGI(TAG, "Connected to %s", connect_message->ssid);
|
||||
TT_LOG_I(TAG, "Connected to %s", connect_message->ssid);
|
||||
} else if (bits & WIFI_FAIL_BIT) {
|
||||
wifi->radio_state = WIFI_RADIO_ON;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeConnectionFailed);
|
||||
ESP_LOGI(TAG, "Failed to connect to %s", connect_message->ssid);
|
||||
TT_LOG_I(TAG, "Failed to connect to %s", connect_message->ssid);
|
||||
} else {
|
||||
wifi->radio_state = WIFI_RADIO_ON;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeConnectionFailed);
|
||||
ESP_LOGE(TAG, "UNEXPECTED EVENT");
|
||||
TT_LOG_E(TAG, "UNEXPECTED EVENT");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,3 +592,5 @@ const ServiceManifest wifi_service = {
|
||||
.on_start = &wifi_service_start,
|
||||
.on_stop = &wifi_service_stop
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,361 @@
|
||||
#ifndef ESP_PLATFORM
|
||||
|
||||
#include "wifi.h"
|
||||
|
||||
#include "check.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "log.h"
|
||||
#include "message_queue.h"
|
||||
#include "mutex.h"
|
||||
#include "pubsub.h"
|
||||
#include "service.h"
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
#define TAG "wifi"
|
||||
#define WIFI_SCAN_RECORD_LIMIT 16 // default, can be overridden
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
|
||||
typedef struct {
|
||||
/** @brief Locking mechanism for modifying the Wifi instance */
|
||||
Mutex* mutex;
|
||||
/** @brief The public event bus */
|
||||
PubSub* pubsub;
|
||||
/** @brief The internal message queue */
|
||||
MessageQueue* queue;
|
||||
/** @brief Scanning results */
|
||||
wifi_ap_record_t* _Nullable scan_list;
|
||||
/** @brief The current item count in scan_list (-1 when scan_list is NULL) */
|
||||
uint16_t scan_list_count;
|
||||
/** @brief Maximum amount of records to scan (value > 0) */
|
||||
uint16_t scan_list_limit;
|
||||
bool scan_active;
|
||||
EventGroupHandle_t event_group;
|
||||
WifiRadioState radio_state;
|
||||
} Wifi;
|
||||
|
||||
typedef enum {
|
||||
WifiMessageTypeRadioOn,
|
||||
WifiMessageTypeRadioOff,
|
||||
WifiMessageTypeScan,
|
||||
WifiMessageTypeConnect,
|
||||
WifiMessageTypeDisconnect
|
||||
} WifiMessageType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t ssid[32];
|
||||
uint8_t password[64];
|
||||
} WifiConnectMessage;
|
||||
|
||||
typedef struct {
|
||||
WifiMessageType type;
|
||||
union {
|
||||
WifiConnectMessage connect_message;
|
||||
};
|
||||
} WifiMessage;
|
||||
|
||||
static Wifi* wifi_singleton = NULL;
|
||||
|
||||
// Forward declarations
|
||||
static void wifi_scan_list_free_safely(Wifi* wifi);
|
||||
static void wifi_disconnect_internal(Wifi* wifi);
|
||||
static void wifi_lock(Wifi* wifi);
|
||||
static void wifi_unlock(Wifi* wifi);
|
||||
|
||||
// region Alloc
|
||||
|
||||
static Wifi* wifi_alloc() {
|
||||
Wifi* instance = malloc(sizeof(Wifi));
|
||||
instance->mutex = tt_mutex_alloc(MutexTypeRecursive);
|
||||
instance->pubsub = tt_pubsub_alloc();
|
||||
// TODO: Deal with messages that come in while an action is ongoing
|
||||
// for example: when scanning and you turn off the radio, the scan should probably stop or turning off
|
||||
// the radio should disable the on/off button in the app as it is pending.
|
||||
instance->queue = tt_message_queue_alloc(1, sizeof(WifiMessage));
|
||||
instance->scan_active = false;
|
||||
instance->scan_list = NULL;
|
||||
instance->scan_list_count = 0;
|
||||
instance->scan_list_limit = WIFI_SCAN_RECORD_LIMIT;
|
||||
instance->event_group = xEventGroupCreate();
|
||||
instance->radio_state = WIFI_RADIO_OFF;
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void wifi_free(Wifi* instance) {
|
||||
tt_mutex_free(instance->mutex);
|
||||
tt_pubsub_free(instance->pubsub);
|
||||
tt_message_queue_free(instance->queue);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
// endregion Alloc
|
||||
|
||||
// region Public functions
|
||||
|
||||
PubSub* wifi_get_pubsub() {
|
||||
tt_assert(wifi_singleton);
|
||||
return wifi_singleton->pubsub;
|
||||
}
|
||||
|
||||
WifiRadioState wifi_get_radio_state() {
|
||||
return wifi_singleton->radio_state;
|
||||
}
|
||||
|
||||
void wifi_scan() {
|
||||
tt_assert(wifi_singleton);
|
||||
WifiMessage message = {.type = WifiMessageTypeScan};
|
||||
// No need to lock for queue
|
||||
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
bool wifi_is_scanning() {
|
||||
tt_assert(wifi_singleton);
|
||||
return wifi_singleton->scan_active;
|
||||
}
|
||||
|
||||
void wifi_connect(const char* ssid, const char _Nullable password[64]) {
|
||||
tt_assert(wifi_singleton);
|
||||
tt_check(strlen(ssid) <= 32);
|
||||
WifiMessage message = {.type = WifiMessageTypeConnect};
|
||||
memcpy(message.connect_message.ssid, ssid, 32);
|
||||
if (password != NULL) {
|
||||
memcpy(message.connect_message.password, password, 64);
|
||||
} else {
|
||||
message.connect_message.password[0] = 0;
|
||||
}
|
||||
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
void wifi_disconnect() {
|
||||
tt_assert(wifi_singleton);
|
||||
WifiMessage message = {.type = WifiMessageTypeDisconnect};
|
||||
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
void wifi_set_scan_records(uint16_t records) {
|
||||
tt_assert(wifi_singleton);
|
||||
if (records != wifi_singleton->scan_list_limit) {
|
||||
wifi_scan_list_free_safely(wifi_singleton);
|
||||
wifi_singleton->scan_list_limit = records;
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_get_scan_results(WifiApRecord records[], uint16_t limit, uint16_t* result_count) {
|
||||
tt_check(wifi_singleton);
|
||||
tt_check(result_count);
|
||||
|
||||
if (wifi_singleton->scan_list_count == 0) {
|
||||
*result_count = 0;
|
||||
} else {
|
||||
uint16_t i = 0;
|
||||
TT_LOG_I(TAG, "processing up to %d APs", wifi_singleton->scan_list_count);
|
||||
uint16_t last_index = MIN(wifi_singleton->scan_list_count, limit);
|
||||
for (; i < last_index; ++i) {
|
||||
memcpy(records[i].ssid, wifi_singleton->scan_list[i].ssid, 33);
|
||||
records[i].rssi = wifi_singleton->scan_list[i].rssi;
|
||||
records[i].auth_mode = wifi_singleton->scan_list[i].authmode;
|
||||
}
|
||||
// The index already overflowed right before the for-loop was terminated,
|
||||
// so it effectively became the list count:
|
||||
*result_count = i;
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_set_enabled(bool enabled) {
|
||||
if (enabled) {
|
||||
WifiMessage message = {.type = WifiMessageTypeRadioOn};
|
||||
// No need to lock for queue
|
||||
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
|
||||
} else {
|
||||
WifiMessage message = {.type = WifiMessageTypeRadioOff};
|
||||
// No need to lock for queue
|
||||
tt_message_queue_put(wifi_singleton->queue, &message, 100 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Public functions
|
||||
|
||||
static void wifi_lock(Wifi* wifi) {
|
||||
tt_crash("this fails for now");
|
||||
tt_assert(wifi);
|
||||
tt_assert(wifi->mutex);
|
||||
tt_check(xSemaphoreTakeRecursive(wifi->mutex, portMAX_DELAY) == pdPASS);
|
||||
}
|
||||
|
||||
static void wifi_unlock(Wifi* wifi) {
|
||||
tt_assert(wifi);
|
||||
tt_assert(wifi->mutex);
|
||||
tt_check(xSemaphoreGiveRecursive(wifi->mutex) == pdPASS);
|
||||
}
|
||||
|
||||
static void wifi_scan_list_alloc(Wifi* wifi) {
|
||||
tt_check(wifi->scan_list == NULL);
|
||||
wifi->scan_list = malloc(sizeof(wifi_ap_record_t) * wifi->scan_list_limit);
|
||||
wifi->scan_list_count = 0;
|
||||
}
|
||||
|
||||
static void wifi_scan_list_alloc_safely(Wifi* wifi) {
|
||||
if (wifi->scan_list == NULL) {
|
||||
wifi_scan_list_alloc(wifi);
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_scan_list_free(Wifi* wifi) {
|
||||
tt_check(wifi->scan_list != NULL);
|
||||
free(wifi->scan_list);
|
||||
wifi->scan_list = NULL;
|
||||
wifi->scan_list_count = 0;
|
||||
}
|
||||
|
||||
static void wifi_scan_list_free_safely(Wifi* wifi) {
|
||||
if (wifi->scan_list != NULL) {
|
||||
wifi_scan_list_free(wifi);
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_publish_event_simple(Wifi* wifi, WifiEventType type) {
|
||||
WifiEvent turning_on_event = {.type = type};
|
||||
tt_pubsub_publish(wifi->pubsub, &turning_on_event);
|
||||
}
|
||||
|
||||
static void wifi_enable(Wifi* wifi) {
|
||||
WifiRadioState state = wifi->radio_state;
|
||||
if (
|
||||
state == WIFI_RADIO_ON ||
|
||||
state == WIFI_RADIO_ON_PENDING ||
|
||||
state == WIFI_RADIO_OFF_PENDING
|
||||
) {
|
||||
TT_LOG_W(TAG, "Can't enable from current state");
|
||||
return;
|
||||
}
|
||||
|
||||
wifi->radio_state = WIFI_RADIO_ON;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOn);
|
||||
TT_LOG_I(TAG, "Enabled");
|
||||
}
|
||||
|
||||
static void wifi_disable(Wifi* wifi) {
|
||||
WifiRadioState state = wifi->radio_state;
|
||||
if (
|
||||
state == WIFI_RADIO_OFF ||
|
||||
state == WIFI_RADIO_OFF_PENDING ||
|
||||
state == WIFI_RADIO_ON_PENDING
|
||||
) {
|
||||
TT_LOG_W(TAG, "Can't disable from current state");
|
||||
return;
|
||||
}
|
||||
|
||||
wifi->radio_state = WIFI_RADIO_OFF;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeRadioStateOff);
|
||||
TT_LOG_I(TAG, "Disabled");
|
||||
}
|
||||
|
||||
static void wifi_scan_internal(Wifi* wifi) {
|
||||
WifiRadioState state = wifi->radio_state;
|
||||
if (state != WIFI_RADIO_ON && state != WIFI_RADIO_CONNECTION_ACTIVE) {
|
||||
TT_LOG_W(TAG, "Scan unavailable: wifi not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Starting scan");
|
||||
wifi->scan_active = true;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeScanStarted);
|
||||
|
||||
// TODO: fake entries
|
||||
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeScanFinished);
|
||||
wifi->scan_active = false;
|
||||
TT_LOG_I(TAG, "Finished scan");
|
||||
}
|
||||
|
||||
static void wifi_connect_internal(Wifi* wifi, WifiConnectMessage* connect_message) {
|
||||
wifi_disconnect_internal(wifi);
|
||||
|
||||
wifi->radio_state = WIFI_RADIO_CONNECTION_PENDING;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeConnectionPending);
|
||||
|
||||
// TODO: Sleep?
|
||||
|
||||
wifi->radio_state = WIFI_RADIO_CONNECTION_ACTIVE;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeConnectionSuccess);
|
||||
|
||||
TT_LOG_I(TAG, "Connected to %s", connect_message->ssid);
|
||||
}
|
||||
|
||||
static void wifi_disconnect_internal(Wifi* wifi) {
|
||||
wifi->radio_state = WIFI_RADIO_ON;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
|
||||
TT_LOG_I(TAG, "Disconnected");
|
||||
}
|
||||
|
||||
static void wifi_disconnect_internal_but_keep_active(Wifi* wifi) {
|
||||
wifi->radio_state = WIFI_RADIO_ON;
|
||||
wifi_publish_event_simple(wifi, WifiEventTypeDisconnected);
|
||||
TT_LOG_I(TAG, "Disconnected");
|
||||
}
|
||||
|
||||
// ESP wifi APIs need to run from the main task, so we can't just spawn a thread
|
||||
_Noreturn int32_t wifi_main(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
TT_LOG_I(TAG, "Started main loop");
|
||||
tt_check(wifi_singleton != NULL);
|
||||
Wifi* wifi = wifi_singleton;
|
||||
MessageQueue* queue = wifi->queue;
|
||||
|
||||
WifiMessage message;
|
||||
while (true) {
|
||||
if (tt_message_queue_get(queue, &message, 1000 / portTICK_PERIOD_MS) == TtStatusOk) {
|
||||
TT_LOG_I(TAG, "Processing message of type %d", message.type);
|
||||
switch (message.type) {
|
||||
case WifiMessageTypeRadioOn:
|
||||
wifi_enable(wifi);
|
||||
break;
|
||||
case WifiMessageTypeRadioOff:
|
||||
wifi_disable(wifi);
|
||||
break;
|
||||
case WifiMessageTypeScan:
|
||||
wifi_scan_internal(wifi);
|
||||
break;
|
||||
case WifiMessageTypeConnect:
|
||||
wifi_connect_internal(wifi, &message.connect_message);
|
||||
break;
|
||||
case WifiMessageTypeDisconnect:
|
||||
wifi_disconnect_internal_but_keep_active(wifi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_service_start(Service service) {
|
||||
UNUSED(service);
|
||||
tt_check(wifi_singleton == NULL);
|
||||
wifi_singleton = wifi_alloc();
|
||||
}
|
||||
|
||||
static void wifi_service_stop(Service service) {
|
||||
UNUSED(service);
|
||||
tt_check(wifi_singleton != NULL);
|
||||
|
||||
WifiRadioState state = wifi_singleton->radio_state;
|
||||
if (state != WIFI_RADIO_OFF) {
|
||||
wifi_disable(wifi_singleton);
|
||||
}
|
||||
|
||||
wifi_free(wifi_singleton);
|
||||
wifi_singleton = NULL;
|
||||
|
||||
// wifi_main() cannot be stopped yet as it runs in the main task.
|
||||
// We could theoretically exit it, but then we wouldn't be able to restart the service.
|
||||
tt_crash("not fully implemented");
|
||||
}
|
||||
|
||||
const ServiceManifest wifi_service = {
|
||||
.id = "wifi",
|
||||
.on_start = &wifi_service_start,
|
||||
.on_stop = &wifi_service_stop
|
||||
};
|
||||
|
||||
#endif
|
||||