first commit
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
46
README.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Universal_HamRadio_Remote_HTML5
|
||||||
|
Universal HamRadio Remote HTML5 interface.
|
||||||
|
This is an implementation of a python server and HTML5 frontend to provide a web interface to use your TRX for both RX and TX.
|
||||||
|
You can use basic and some advanced functions of your radio.
|
||||||
|
You use the speaker and microphone of your computer to communicate.
|
||||||
|
This project is more oriented for voice (phone) or CW.
|
||||||
|
|
||||||
|
<b>Please send me an email with your success story.</b> olivier@f4htb.fr
|
||||||
|
<b>Modification fvor compatible<b> F4IYT Xavier
|
||||||
|
|
||||||
|
<b>More info on the wiki page:</b> https://github.com/F4HTB/Universal_HamRadio_Remote_HTML5/wiki
|
||||||
|
|
||||||
|
<b>News:</b> https://github.com/F4HTB/Universal_HamRadio_Remote_HTML5/wiki/History<br>
|
||||||
|
|
||||||
|
Caution:
|
||||||
|
It is designed for Raspberry Pi OS (32-bit) Lite (actually "Minimal image based on Debian Buster").
|
||||||
|
Use only if it is legal in your country.
|
||||||
|
It is intended for remote use, it is not designed for use on the same computer as an interface even though it will likely work.
|
||||||
|
Please don't raise an issue for anything outside of the intended design.
|
||||||
|
|
||||||
|
|
||||||
|
![UHRR_Pict](https://user-images.githubusercontent.com/18350938/99989724-e1263580-2daa-11eb-9e3e-c132d4c2d7eb.png)
|
||||||
|
|
||||||
|
This utility is used to set up an amateur radio station remotely via a web browser.
|
||||||
|
|
||||||
|
You need:
|
||||||
|
- a radio station compatible with Hamlib.
|
||||||
|
- a cat interface.
|
||||||
|
- a circuit making it possible to adapt the audio levels between the microphone input, the speaker output and the sound card.
|
||||||
|
|
||||||
|
Assuming your raspberry pi hostname is set to UHRR, you can access it at https://UHRR.local:8888/
|
||||||
|
Note the HTTP <b> S </b>.
|
||||||
|
You can configure all of this by logging into https://UHRR.local:8888/CONFIG
|
||||||
|
If the original configuration is invalid or missing, this will automatically switch to the configuration page.
|
||||||
|
|
||||||
|
|
||||||
|
![func_princ](https://user-images.githubusercontent.com/18350938/99989800-f3a06f00-2daa-11eb-9b45-d695b75904f7.png)
|
||||||
|
|
||||||
|
![sound_diagram](https://user-images.githubusercontent.com/18350938/99989819-fe5b0400-2daa-11eb-884f-c09341a03541.png)
|
||||||
|
|
||||||
|
Special thanks to :
|
||||||
|
|
||||||
|
-Mike W9MDB! and all the hamlib team for all their hard work
|
||||||
|
|
||||||
|
-All contributors :)
|
||||||
|
|
BIN
RPI partie 13-V1.0.pdf
Normal file
21
UHRH.crt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUFZazUUKBBIYvF+HJuDNOXN1FQewwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA1MjAxODA2MjhaFw0yMDA2
|
||||||
|
MTkxODA2MjhaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||||
|
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4IBDwAwggEKAoIBAQCcydQ7PQ2q9mh0RxiQ7D0xYpLmUrsWq6J1oEMzT1Q0
|
||||||
|
Cglib2cRkwE4gWx67tHmL+IqA0HdYyZQkvc81T4z8DFgchQY997uhCFZvmtEvJln
|
||||||
|
KvJIS8wPXuZqQCJAKSfmyGGS+WBiMb7l1nrM0kDVGgllWFxleTWkjn+dKJHlcTQs
|
||||||
|
ankV2SmPKSBbp96tid5oLF9Po9l3A7HoCTGSiV+CnNPsr0ptAx7wYjx+FXZiwBx3
|
||||||
|
zBfiprtCyfja0bQZLkCZOkRjNn6Px5g8vuN8O/NO6UdEKJdoNwfn40nQ+ledOwca
|
||||||
|
/TgcbHwzFvZD1cMXceScZDQxhzaVlOYUxxEKgxlxOXSzAgMBAAGjUzBRMB0GA1Ud
|
||||||
|
DgQWBBRFcsDAW0GZCgiFES0Yfr8ITkCtnjAfBgNVHSMEGDAWgBRFcsDAW0GZCgiF
|
||||||
|
ES0Yfr8ITkCtnjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBb
|
||||||
|
F1Lq/K8MYtQ8co9QY4C/sRiFpxh+gSZan4N7wdpCupqlphsN58G0zw/YxjrqLu8k
|
||||||
|
wdmv4nV95zFqzpBH+eit3dIrGSRdO3rln+fMLiNMhvK23v1BsfWbwgqd513RTTlQ
|
||||||
|
mUloWsPIca2S6PiMcfTnZRTyyZOg6ciVsKv1b+NyMfnFDHc90+WNz6dZG2TjgdCJ
|
||||||
|
mC5oV52KG+Ju3sQYnv/1a8zUsAeB6uG/gjFbbg/ANPdWh7jvmY8wXSd22jYVzWxZ
|
||||||
|
YTkPLIBf+GegWvWLkNTbydmDdmAROzlJVLVhfMaOQuvAnrdKpd0oFqDs+uNiPMgB
|
||||||
|
b7H5eusoH7uXuOD8NQNK
|
||||||
|
-----END CERTIFICATE-----
|
27
UHRH.key
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAnMnUOz0NqvZodEcYkOw9MWKS5lK7FquidaBDM09UNAoJYm9n
|
||||||
|
EZMBOIFseu7R5i/iKgNB3WMmUJL3PNU+M/AxYHIUGPfe7oQhWb5rRLyZZyrySEvM
|
||||||
|
D17makAiQCkn5shhkvlgYjG+5dZ6zNJA1RoJZVhcZXk1pI5/nSiR5XE0LGp5Fdkp
|
||||||
|
jykgW6ferYneaCxfT6PZdwOx6AkxkolfgpzT7K9KbQMe8GI8fhV2YsAcd8wX4qa7
|
||||||
|
Qsn42tG0GS5AmTpEYzZ+j8eYPL7jfDvzTulHRCiXaDcH5+NJ0PpXnTsHGv04HGx8
|
||||||
|
Mxb2Q9XDF3HknGQ0MYc2lZTmFMcRCoMZcTl0swIDAQABAoIBAHD+Z6x1mKcQREEg
|
||||||
|
h8zR5Fv1/YZuMxTohwGciTGuRzHl1dOSE8avmh6d7489FBp/gc/jXxFtBkzlTbcS
|
||||||
|
u2x0+zDVpjREVu6wXNSvjeEQxsF6SvfdYGfnbck/BTAWOQJygReKD3NVBI3hn8iC
|
||||||
|
8mRiCkl2f8hFrWo1pDSf611e00n6JRg2YPlH/gBqV2T0mDJL7CXTP6Tggaa6jInQ
|
||||||
|
Ux7OgPz8djEvk5jFm0lGVxHzo/kbTGfBTLbKfJv/NTxiHH7Qt8NeaFzzJo7sH1eM
|
||||||
|
E8JT8sZZdCiBtlEmmgp/vX+r9M1gqi2i/evOzJxayx6J+CaGiS/j/VuXC5sQJB8L
|
||||||
|
jaorwikCgYEAyc1YZ0uxWoCy2M6fYG3ClJ9P3n2rfwdBwzhXaPm6B8EI7UCPR/0/
|
||||||
|
EcckGe34QGc2XmtwgxHIJ+hiidzLTn5hMDHYO82JOKa8cQppIKdGGnRwGhyiZhaf
|
||||||
|
tRyUD5ySPEFL8GR+Mau19NYy5bic5mDrFmSiCtgsxf3kzgAPmxF/BZcCgYEAxuWe
|
||||||
|
yRsLX5XZHZJRdrGt6lrSnr0BosKIjNxJDwh9VUVh49bzWa1Kvp7u+XMlN48A335I
|
||||||
|
Aukicujtoe0gIGS9btFOPX5seBs+lReWFpVn0Xa0OJQby7oX3Fp1XDTsbvtrcDHV
|
||||||
|
vXsZLNv3ip0OGKRh1p1va/idTpajeqPRDB5HBUUCgYACu/2OqL/mcgf6WBJgxBv2
|
||||||
|
15HFef5w4jBJ7OGCUp/qqvrr/Av09cF9BC3BDDBo7v0Vmm8T15HWuJddNtiqX5wB
|
||||||
|
gyti5A4P7nJvNazm/F0+zoUWVXz91SCk25ZF/+EbX+cfgr0S/zif8KcP5ch6dqW4
|
||||||
|
z/RCIVu58w6+m9GaUEpgUQKBgQCX7E60GBdA5MnZn6jf++n3B3a3z3EPbH428hBQ
|
||||||
|
DlEFsCCMkuSAjDB6mBW7rmswG+gzzlac+ozYrvjMZb7TX3+exPt5VzbtKwpLgZ+g
|
||||||
|
EnEhewU/7kmo/LU7GFFqo/Yw85RmN3qm5/8b180mML7SrcUZ1FmGZHlrzP6EL9r+
|
||||||
|
4aWn7QKBgFXxU/vI5RVpvG35dt5BwomBMIx0W2UtXIG+/u8j7S9GBUXFXUyUVr9W
|
||||||
|
rPsI1TpIZKR9r95RIaj8B25DyUApJVlH7jf2DDoASqr5dR77fmQVaANrrDhgMjNl
|
||||||
|
QmPd4kw52/wr5KLJukLK9dg8kmwDIMGvHy9rBGo5uoKk4+daUjTW
|
||||||
|
-----END RSA PRIVATE KEY-----
|
828
UHRR
Executable file
|
@ -0,0 +1,828 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tornado.httpserver
|
||||||
|
import tornado.ioloop
|
||||||
|
import tornado.web
|
||||||
|
import tornado.websocket
|
||||||
|
import alsaaudio
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import numpy
|
||||||
|
import gc
|
||||||
|
from opus.decoder import Decoder as OpusDecoder
|
||||||
|
import datetime
|
||||||
|
import configparser
|
||||||
|
import sys
|
||||||
|
import Hamlib
|
||||||
|
from rtlsdr import RtlSdr
|
||||||
|
import numpy as np
|
||||||
|
import math
|
||||||
|
|
||||||
|
############ Global variables ##################################
|
||||||
|
CTRX=None
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read('UHRR.conf')
|
||||||
|
e="No"
|
||||||
|
|
||||||
|
############ Global functions ##################################
|
||||||
|
def writte_log(logmsg):
|
||||||
|
logfile = open(config['SERVER']['log_file'],"w")
|
||||||
|
msg = str(datetime.datetime.now())+":"+str(logmsg)
|
||||||
|
logfile.write(msg)
|
||||||
|
print(msg)
|
||||||
|
logfile.close()
|
||||||
|
|
||||||
|
############ BaseHandler tornado ##############
|
||||||
|
class BaseHandler(tornado.web.RequestHandler):
|
||||||
|
def get_current_user(self):
|
||||||
|
return self.get_secure_cookie("user")
|
||||||
|
|
||||||
|
############ Generate and send FFT from RTLSDR ##############
|
||||||
|
is_rtlsdr_present = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
FFTSIZE=4096
|
||||||
|
nbBuffer=24
|
||||||
|
nbsamples=nbBuffer/2*FFTSIZE
|
||||||
|
ptime=nbsamples/int(config['PANADAPTER']['sample_rate'])
|
||||||
|
sdr_windows = eval("np."+config['PANADAPTER']['fft_window']+ "(FFTSIZE)")
|
||||||
|
fftpaquetlen=int(FFTSIZE*8/2048)
|
||||||
|
sdr = RtlSdr()
|
||||||
|
sdr.sample_rate = int(config['PANADAPTER']['sample_rate']) # Hz
|
||||||
|
sdr.center_freq = int(config['PANADAPTER']['center_freq']) # Hz
|
||||||
|
sdr.freq_correction = int(config['PANADAPTER']['freq_correction']) # PPM
|
||||||
|
sdr.gain = int(config['PANADAPTER']['gain']) #or 'auto'
|
||||||
|
except:
|
||||||
|
is_rtlsdr_present = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AudioPanaHandlerClients = []
|
||||||
|
|
||||||
|
class loadFFTdata(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.get_log_power_spectrum_w = np.empty(FFTSIZE)
|
||||||
|
for i in range(FFTSIZE):
|
||||||
|
self.get_log_power_spectrum_w[i] = 0.5 * (1. - math.cos((2 * math.pi * i) / (FFTSIZE - 1)))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
time.sleep(ptime)
|
||||||
|
self.getFFT_data()
|
||||||
|
|
||||||
|
|
||||||
|
def get_log_power_spectrum(self,data):
|
||||||
|
|
||||||
|
pulse = 10
|
||||||
|
rejected_count = 0
|
||||||
|
power_spectrum = np.zeros(FFTSIZE)
|
||||||
|
db_adjust = 20. * math.log10(FFTSIZE * 2 ** 15)
|
||||||
|
|
||||||
|
# Time-domain analysis: Often we have long normal signals interrupted
|
||||||
|
# by huge wide-band pulses that degrade our power spectrum average.
|
||||||
|
# We find the "normal" signal level, by computing the median of the
|
||||||
|
# absolute value. We only do this for the first buffer of a chunk,
|
||||||
|
# using the median for the remaining buffers in the chunk.
|
||||||
|
# A "noise pulse" is a signal level greater than some threshold
|
||||||
|
# times the median. When such a pulse is found, we skip the current
|
||||||
|
# buffer. It would be better to blank out just the pulse, but that
|
||||||
|
# would be more costly in CPU time.
|
||||||
|
|
||||||
|
# Find the median abs value of first buffer to use for this chunk.
|
||||||
|
td_median = np.median(np.abs(data[:FFTSIZE]))
|
||||||
|
# Calculate our current threshold relative to measured median.
|
||||||
|
td_threshold = pulse * td_median
|
||||||
|
nbuf_taken = 0 # Actual number of buffers accumulated
|
||||||
|
for ic in range(nbBuffer-1):
|
||||||
|
start=ic * int(FFTSIZE/2)
|
||||||
|
end=start+FFTSIZE
|
||||||
|
td_segment = data[start:end]*sdr_windows
|
||||||
|
|
||||||
|
# remove the 0hz spike
|
||||||
|
td_segment = np.subtract(td_segment, np.average(td_segment))
|
||||||
|
|
||||||
|
td_max = np.amax(np.abs(td_segment)) # Do we have a noise pulse?
|
||||||
|
if td_max < td_threshold: # No, get pwr spectrum etc.
|
||||||
|
# EXPERIMENTAL TAPERfd
|
||||||
|
td_segment *= self.get_log_power_spectrum_w
|
||||||
|
|
||||||
|
fd_spectrum = np.fft.fft(td_segment)
|
||||||
|
# Frequency-domain:
|
||||||
|
# Rotate array to place 0 freq. in center. (It was at left.)
|
||||||
|
fd_spectrum_rot = np.fft.fftshift(fd_spectrum)
|
||||||
|
# Compute the real-valued squared magnitude (ie power) and
|
||||||
|
# accumulate into pwr_acc.
|
||||||
|
# fastest way to sum |z|**2 ??
|
||||||
|
nbuf_taken += 1
|
||||||
|
power_spectrum = power_spectrum + \
|
||||||
|
np.real(fd_spectrum_rot * fd_spectrum_rot.conj())
|
||||||
|
else: # Yes, abort buffer.
|
||||||
|
rejected_count += 1
|
||||||
|
# if DEBUG: print "REJECT! %d" % self.rejected_count
|
||||||
|
if nbuf_taken > 0:
|
||||||
|
power_spectrum = power_spectrum / nbuf_taken # normalize the sum.
|
||||||
|
else:
|
||||||
|
power_spectrum = np.ones(FFTSIZE) # if no good buffers!
|
||||||
|
# Convert to dB. Note log(0) = "-inf" in Numpy. It can happen if ADC
|
||||||
|
# isn't working right. Numpy issues a warning.
|
||||||
|
log_power_spectrum = 10. * np.log10(power_spectrum)
|
||||||
|
return log_power_spectrum - db_adjust # max poss. signal = 0 dB
|
||||||
|
|
||||||
|
def getFFT_data(self):
|
||||||
|
samples = sdr.read_samples(nbsamples)
|
||||||
|
samples = np.imag(samples) + 1j * np.real(samples)
|
||||||
|
|
||||||
|
max_pow = -254
|
||||||
|
min_pow = 0
|
||||||
|
|
||||||
|
power = self.get_log_power_spectrum(samples)
|
||||||
|
|
||||||
|
# search whole data set for maximum and minimum value
|
||||||
|
for dat in power:
|
||||||
|
if dat > max_pow:
|
||||||
|
max_pow = dat
|
||||||
|
elif dat < min_pow:
|
||||||
|
min_pow = dat
|
||||||
|
|
||||||
|
byteslist=bytearray()
|
||||||
|
try:
|
||||||
|
for dat in power:
|
||||||
|
try:
|
||||||
|
byteslist.append(self.FFTmymap(dat, min_pow, max_pow, 0, 255))
|
||||||
|
except (RuntimeError, TypeError, NameError):
|
||||||
|
byteslist.append(255)
|
||||||
|
pass
|
||||||
|
byteslist+=bytearray((65280+int(min_pow)).to_bytes(2, byteorder="big"))
|
||||||
|
byteslist+=bytearray((65280+int(max_pow)).to_bytes(2, byteorder="big"))
|
||||||
|
for c in AudioPanaHandlerClients:
|
||||||
|
c.fftframes.append(bytes(byteslist))
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def FFTmymap(self, x, in_min, in_max, out_min, out_max):
|
||||||
|
ret=int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WS_panFFTHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
|
||||||
|
@tornado.gen.coroutine
|
||||||
|
def sendFFT(self):
|
||||||
|
global ptime, fftpaquetlen
|
||||||
|
try:
|
||||||
|
while len(self.fftframes)>0:
|
||||||
|
yield self.write_message(self.fftframes[0],binary=True)
|
||||||
|
del self.fftframes[0]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=ptime), self.sendFFT)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
global is_rtlsdr_present
|
||||||
|
print('new connection on FFT socket, is_rtlsdr_present = '+str(is_rtlsdr_present))
|
||||||
|
if self not in AudioPanaHandlerClients:
|
||||||
|
AudioPanaHandlerClients.append(self)
|
||||||
|
self.fftframes = []
|
||||||
|
|
||||||
|
def on_message(self, data) :
|
||||||
|
print(data)
|
||||||
|
if str(data)=="ready":
|
||||||
|
self.sendFFT()
|
||||||
|
elif str(data)=="init":
|
||||||
|
self.write_message("fftsr:"+str(config['PANADAPTER']['sample_rate']));
|
||||||
|
self.write_message("fftsz:"+str(FFTSIZE));
|
||||||
|
self.write_message("fftst");
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
print('connection closed for FFT socket')
|
||||||
|
|
||||||
|
############ websocket for send RX audio from TRX ##############
|
||||||
|
flagWavstart = False
|
||||||
|
AudioRXHandlerClients = []
|
||||||
|
|
||||||
|
class loadWavdata(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
global flagWavstart
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
#self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, channels=1, rate=8000, format=alsaaudio.PCM_FORMAT_FLOAT_LE, periodsize=256, device=config['AUDIO']['inputdevice'])
|
||||||
|
self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL, device=config['AUDIO']['inputdevice'])
|
||||||
|
self.inp.setchannels(1)
|
||||||
|
self.inp.setrate(8000)
|
||||||
|
self.inp.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE)
|
||||||
|
self.inp.setperiodsize(256)
|
||||||
|
print('recording...')
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
global Wavframes, flagWavstart
|
||||||
|
ret=b''
|
||||||
|
while True:
|
||||||
|
while not flagWavstart:
|
||||||
|
time.sleep(0.5)
|
||||||
|
l, ret = self.inp.read()
|
||||||
|
if l > 0:
|
||||||
|
for c in AudioRXHandlerClients:
|
||||||
|
c.Wavframes.append(ret)
|
||||||
|
else:
|
||||||
|
print("overrun")
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
class WS_AudioRXHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
self.set_nodelay(True)
|
||||||
|
global flagWavstart
|
||||||
|
if self not in AudioRXHandlerClients:
|
||||||
|
AudioRXHandlerClients.append(self)
|
||||||
|
self.Wavframes = []
|
||||||
|
print('new connection on AudioRXHandler socket.')
|
||||||
|
flagWavstart = True
|
||||||
|
self.tailstream()
|
||||||
|
self.set_nodelay(True)
|
||||||
|
|
||||||
|
@tornado.gen.coroutine
|
||||||
|
def tailstream(self):
|
||||||
|
while flagWavstart:
|
||||||
|
while len(self.Wavframes)==0:
|
||||||
|
yield tornado.gen.sleep(0.1)
|
||||||
|
yield self.write_message(self.Wavframes[0],binary=True)
|
||||||
|
del self.Wavframes[0]
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
if self in AudioRXHandlerClients:
|
||||||
|
AudioRXHandlerClients.remove(self)
|
||||||
|
global flagWavstart
|
||||||
|
print('connection closed for audioRX')
|
||||||
|
if len(AudioRXHandlerClients)<=0:
|
||||||
|
flagWavstart = False
|
||||||
|
self.Wavframes = []
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
############ websocket for control TX ##############
|
||||||
|
last_AudioTXHandler_msg_time=0
|
||||||
|
AudioTXHandlerClients = []
|
||||||
|
|
||||||
|
class WS_AudioTXHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
|
||||||
|
def stoppttontimeout(self):
|
||||||
|
global last_AudioTXHandler_msg_time
|
||||||
|
try:
|
||||||
|
if time.time() > last_AudioTXHandler_msg_time + 10:
|
||||||
|
if self.ws_connection and CTRX.infos["PTT"]==True:
|
||||||
|
CTRX.setPTT("false")
|
||||||
|
print("stop ptt on timeout")
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=1), self.stoppttontimeout)
|
||||||
|
|
||||||
|
|
||||||
|
def TX_init(self, msg) :
|
||||||
|
|
||||||
|
itrate, is_encoded, op_rate, op_frm_dur = [int(i) for i in msg.split(',')]
|
||||||
|
self.is_encoded = is_encoded
|
||||||
|
self.decoder = OpusDecoder(op_rate, 1)
|
||||||
|
self.frame_size = op_frm_dur * op_rate
|
||||||
|
|
||||||
|
device = config['AUDIO']['outputdevice']
|
||||||
|
self.inp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK, channels=1, rate=itrate, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=2048, device=device)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
global last_AudioTXHandler_msg_time, AudioTXHandlerClients
|
||||||
|
if self not in AudioTXHandlerClients:
|
||||||
|
AudioTXHandlerClients.append(self)
|
||||||
|
print('new connection on AudioTXHandler socket.')
|
||||||
|
last_AudioTXHandler_msg_time=time.time()
|
||||||
|
self.stoppttontimeout()
|
||||||
|
self.set_nodelay(True)
|
||||||
|
|
||||||
|
def on_message(self, data) :
|
||||||
|
global last_AudioTXHandler_msg_time
|
||||||
|
last_AudioTXHandler_msg_time=time.time()
|
||||||
|
|
||||||
|
if str(data).startswith('m:') :
|
||||||
|
self.TX_init(str(data[2:]))
|
||||||
|
elif str(data).startswith('s:') :
|
||||||
|
self.inp.close()
|
||||||
|
else :
|
||||||
|
if self.is_encoded :
|
||||||
|
pcm = self.decoder.decode(data, self.frame_size, False)
|
||||||
|
self.inp.write(pcm)
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
else :
|
||||||
|
self.inp.write(data)
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
global AudioTXHandlerClients
|
||||||
|
if(hasattr(self,"inp")):
|
||||||
|
self.inp.close()
|
||||||
|
if self in AudioTXHandlerClients:
|
||||||
|
AudioTXHandlerClients.remove(self)
|
||||||
|
if (not len(AudioTXHandlerClients)) and (CTRX.infos["PTT"]==True):
|
||||||
|
CTRX.setPTT("false")
|
||||||
|
print('connection closed for TX socket')
|
||||||
|
|
||||||
|
############ websocket for control TRX ##############
|
||||||
|
ControlTRXHandlerClients = []
|
||||||
|
LastPing = time.time()
|
||||||
|
|
||||||
|
class TRXRIG:
|
||||||
|
def __init__(self):
|
||||||
|
self.spoints = {"0":-54, "1":-48, "2":-42, "3":-36, "4":-30, "5":-24, "6":-18, "7":-12, "8":-6, "9":0, "10":10, "20":20, "30":30, "40":40, "50":50, "60":60}
|
||||||
|
self.infos = {}
|
||||||
|
self.infos["PTT"]=False
|
||||||
|
self.infos["powerstat"]=False
|
||||||
|
self.serialport = Hamlib.hamlib_port_parm_serial
|
||||||
|
self.serialport.rate=config['HAMLIB']['rig_rate']
|
||||||
|
try:
|
||||||
|
Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE)
|
||||||
|
self.rig_model = "RIG_MODEL_"+str(config['HAMLIB']['rig_model'])
|
||||||
|
self.rig_pathname = config['HAMLIB']['rig_pathname']
|
||||||
|
self.rig = Hamlib.Rig(Hamlib.__dict__[self.rig_model]) # Look up the model's numerical index in Hamlib's symbol dictionary.
|
||||||
|
self.rig.set_conf("rig_pathname", self.rig_pathname)
|
||||||
|
if(config['HAMLIB']['rig_rate']!=""):
|
||||||
|
self.rig.set_conf("serial_speed", str(config['HAMLIB']['rig_rate']))
|
||||||
|
if(config['HAMLIB']['data_bits']!=""):
|
||||||
|
self.rig.set_conf("data_bits", str(config['HAMLIB']['data_bits'])) #8 as default
|
||||||
|
if(config['HAMLIB']['stop_bits']!=""):
|
||||||
|
self.rig.set_conf("stop_bits", str(config['HAMLIB']['stop_bits'])) #2 as default
|
||||||
|
if(config['HAMLIB']['serial_parity']!=""):
|
||||||
|
self.rig.set_conf("serial_parity", str(config['HAMLIB']['serial_parity']))# None as default NONE ODD EVEN MARK SPACE
|
||||||
|
if(config['HAMLIB']['serial_handshake']!=""):
|
||||||
|
self.rig.set_conf("serial_handshake", str(config['HAMLIB']['serial_handshake'])) # None as default NONE XONXOFF HARDWARE
|
||||||
|
if(config['HAMLIB']['dtr_state']!=""):
|
||||||
|
self.rig.set_conf("dtr_state", str(config['HAMLIB']['dtr_state'])) #ON or OFF
|
||||||
|
if(config['HAMLIB']['rts_state']!=""):
|
||||||
|
self.rig.set_conf("rts_state", str(config['HAMLIB']['rts_state'])) #ON or OFF
|
||||||
|
self.rig.set_conf("retry", config['HAMLIB']['retry'])
|
||||||
|
self.rig.open()
|
||||||
|
except:
|
||||||
|
print("Could not open a communication channel to the rig via Hamlib!")
|
||||||
|
|
||||||
|
self.setPower(1)
|
||||||
|
self.getvfo()
|
||||||
|
self.getFreq()
|
||||||
|
self.getMode()
|
||||||
|
|
||||||
|
def parsedbtospoint(self,spoint):
|
||||||
|
for key, value in self.spoints.items():
|
||||||
|
if (spoint<value):
|
||||||
|
return key
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def getvfo(self):
|
||||||
|
try:
|
||||||
|
self.infos["VFO"] = (self.rig.get_vfo())
|
||||||
|
except:
|
||||||
|
print("Could not obtain the current VFO via Hamlib!")
|
||||||
|
return self.infos["VFO"]
|
||||||
|
|
||||||
|
def setFreq(self,frequency):
|
||||||
|
try:
|
||||||
|
self.rig.set_freq(Hamlib.RIG_VFO_CURR, float(frequency))
|
||||||
|
self.getFreq()
|
||||||
|
except:
|
||||||
|
print("Could not set the frequency via Hamlib!")
|
||||||
|
return self.infos["FREQ"]
|
||||||
|
|
||||||
|
def getFreq(self):
|
||||||
|
try:
|
||||||
|
self.infos["FREQ"] = (int(self.rig.get_freq()))
|
||||||
|
except:
|
||||||
|
print("Could not obtain the current frequency via Hamlib!")
|
||||||
|
return self.infos["FREQ"]
|
||||||
|
|
||||||
|
def setMode(self,MODE):
|
||||||
|
try:
|
||||||
|
|
||||||
|
self.rig.set_mode(Hamlib.rig_parse_mode(MODE))
|
||||||
|
self.getMode()
|
||||||
|
except:
|
||||||
|
print("Could not set the mode via Hamlib!")
|
||||||
|
return self.infos["MODE"]
|
||||||
|
|
||||||
|
def getMode(self):
|
||||||
|
try:
|
||||||
|
(mode, width) = self.rig.get_mode()
|
||||||
|
self.infos["MODE"] = Hamlib.rig_strrmode(mode).upper()
|
||||||
|
self.infos["WIDTH"] = width
|
||||||
|
except:
|
||||||
|
print("Could not obtain the current Mode via Hamlib!")
|
||||||
|
return self.infos["MODE"]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def getStrgLVL(self):
|
||||||
|
try:
|
||||||
|
self.infos["StrgLVLi"] = self.rig.get_level_i(Hamlib.RIG_LEVEL_STRENGTH)
|
||||||
|
self.infos["StrgLVL"] = self.parsedbtospoint(self.infos["StrgLVLi"])
|
||||||
|
except:
|
||||||
|
print("Could not obtain the current Strength signal RX level via Hamlib!")
|
||||||
|
return self.infos["StrgLVL"]
|
||||||
|
|
||||||
|
def setPTT(self,status):
|
||||||
|
try:
|
||||||
|
if status == "true":
|
||||||
|
self.rig.set_ptt(Hamlib.RIG_VFO_CURR,Hamlib.RIG_PTT_ON)
|
||||||
|
self.infos["PTT"]=True
|
||||||
|
else:
|
||||||
|
self.rig.set_ptt(Hamlib.RIG_VFO_CURR,Hamlib.RIG_PTT_OFF)
|
||||||
|
self.infos["PTT"]=False
|
||||||
|
except:
|
||||||
|
print("Could not set the mode via Hamlib!")
|
||||||
|
return self.infos["PTT"]
|
||||||
|
|
||||||
|
def getPTT(self,status):
|
||||||
|
return self.infos["PTT"]
|
||||||
|
|
||||||
|
def setPower(self,status=1):
|
||||||
|
try:
|
||||||
|
if status:
|
||||||
|
self.rig.set_powerstat(Hamlib.RIG_POWER_ON)
|
||||||
|
else:
|
||||||
|
self.rig.set_powerstat(Hamlib.RIG_POWER_OFF)
|
||||||
|
self.infos["powerstat"] = status
|
||||||
|
except:
|
||||||
|
print("Could not set power status via Hamlib!")
|
||||||
|
return self.infos["powerstat"]
|
||||||
|
|
||||||
|
class ticksTRXRIG(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
if CTRX.infos["powerstat"]:
|
||||||
|
CTRX.getStrgLVL()
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
class WS_ControlTRX(tornado.websocket.WebSocketHandler):
|
||||||
|
|
||||||
|
def send_to_all_clients(self,msg):
|
||||||
|
print ("Send to all: "+msg)
|
||||||
|
for client in ControlTRXHandlerClients:
|
||||||
|
client.write_message(msg)
|
||||||
|
|
||||||
|
def sendPTINFOS(self):
|
||||||
|
try:
|
||||||
|
if self.StrgLVL != CTRX.infos["StrgLVL"]:
|
||||||
|
self.write_message("getSignalLevel:"+str(CTRX.infos["StrgLVL"]))
|
||||||
|
self.StrgLVL=CTRX.infos["StrgLVL"]
|
||||||
|
except:
|
||||||
|
print("error TXMETER")
|
||||||
|
return None
|
||||||
|
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=float(config['CTRL']['interval_smeter_update'])), self.sendPTINFOS)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
if self not in ControlTRXHandlerClients:
|
||||||
|
ControlTRXHandlerClients.append(self)
|
||||||
|
self.StrgLVL=0
|
||||||
|
self.sendPTINFOS()
|
||||||
|
CTRX.setPower(1)
|
||||||
|
print('new connection on ControlTRX socket.')
|
||||||
|
if(is_rtlsdr_present):
|
||||||
|
self.write_message("panfft")
|
||||||
|
self.set_nodelay(True)
|
||||||
|
|
||||||
|
@tornado.gen.coroutine
|
||||||
|
def on_message(self, data) :
|
||||||
|
global LastPing
|
||||||
|
if bool(config['CTRL']['debug']):
|
||||||
|
print(data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
(action, datato) = data.split(':')
|
||||||
|
except ValueError:
|
||||||
|
action = data
|
||||||
|
pass
|
||||||
|
|
||||||
|
if(action == "PING"):
|
||||||
|
self.write_message("PONG")
|
||||||
|
elif(action == "getFreq"):
|
||||||
|
yield self.send_to_all_clients("getFreq:"+str(CTRX.getFreq()))
|
||||||
|
elif(action == "setFreq"):
|
||||||
|
yield self.send_to_all_clients("getFreq:"+str(CTRX.setFreq(datato)))
|
||||||
|
elif(action == "getMode"):
|
||||||
|
yield self.send_to_all_clients("getMode:"+str(CTRX.getMode()))
|
||||||
|
elif(action == "setMode"):
|
||||||
|
yield self.send_to_all_clients("getMode:"+str(CTRX.setMode(datato)))
|
||||||
|
elif(action == "setPTT"):
|
||||||
|
yield self.send_to_all_clients("getPTT:"+str(CTRX.setPTT(datato)))
|
||||||
|
|
||||||
|
LastPing = time.time();
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
if self in ControlTRXHandlerClients:
|
||||||
|
ControlTRXHandlerClients.remove(self)
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
def timeoutTRXshutdown():
|
||||||
|
global LastPing
|
||||||
|
if(LastPing+300) < time.time():
|
||||||
|
print("Shutdown TRX")
|
||||||
|
CTRX.setPower(0)
|
||||||
|
|
||||||
|
class threadtimeoutTRXshutdown(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
time.sleep(60)
|
||||||
|
timeoutTRXshutdown()
|
||||||
|
|
||||||
|
############ Config ##############
|
||||||
|
class ConfigHandler(BaseHandler):
|
||||||
|
def get(self):
|
||||||
|
|
||||||
|
if bool(config['SERVER']['auth']) and not self.current_user:
|
||||||
|
self.redirect("/login")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.application.settings.get("compiled_template_cache", False)
|
||||||
|
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||||
|
try:
|
||||||
|
from serial.tools.list_ports import comports
|
||||||
|
except ImportError:
|
||||||
|
return None
|
||||||
|
audiodevicesoutput=[s for s in alsaaudio.pcms(0) if "plughw" in s]
|
||||||
|
audiodevicesinput=[s for s in alsaaudio.pcms(1) if "plughw" in s]
|
||||||
|
comports=list(comports())
|
||||||
|
rig_models=[s[10:] for s in dir(Hamlib) if "RIG_MODEL_" in s]
|
||||||
|
self.write("""<html><form method="POST" action="/CONFIG">""")
|
||||||
|
self.write("""[SERVER]<br/><br/>""")
|
||||||
|
self.write("""SERVER TCP/IP port:<input type="text" name="SERVER.port" value="""+config['SERVER']['port']+""">Defautl:<b>8888</b>.The server port<br/><br/>""")
|
||||||
|
self.write("""SERVER Authentification type:<input type="text" name="SERVER.auth" value="""+config['SERVER']['auth']+"""> Defautl:<b>leave blank</b>. Else you can use "FILE" or/and "PAM".<br/><br/>""")
|
||||||
|
self.write("""SERVER database users file:<input type="text" name="SERVER.db_users_file" value="""+config['SERVER']['db_users_file']+"""> Defautl:<b>UHRR_users.db</b> Only if you use Authentification type "FILE".<br/><br/>""")
|
||||||
|
self.write("""You can change database users file in UHRR.conf.<br/> To add a user in FILE type, add it in UHRR_users.db (default file name).<br/>Add one account per line as login password.<br/>""")
|
||||||
|
self.write("""If you plan to use PAM you can add account in command line: adduser --no-create-home --system thecallsign.<br/><br/>""")
|
||||||
|
self.write("""If you want to change certfile and keyfile, replace "UHRH.crt" and "UHRH.key" in the boot folder, and when the pi boot, it will use those files to start http ssl.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""[AUDIO]<br/><br/>""")
|
||||||
|
self.write("""AUDIO outputdevice:<select name="AUDIO.outputdevice">""")
|
||||||
|
if(config['AUDIO']['outputdevice']!=""):
|
||||||
|
self.write("""<option value="""+config['AUDIO']['outputdevice']+""" selected>"""+config['AUDIO']['outputdevice']+"""</option>""")
|
||||||
|
for c in audiodevicesoutput:
|
||||||
|
self.write("""<option value="""+c+""">"""+c+"""</option>""")
|
||||||
|
self.write("""</select> Output from audio soundcard to the mic input of TRX.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""AUDIO inputdevice:<select name="AUDIO.inputdevice">""")
|
||||||
|
if(config['AUDIO']['inputdevice']!=""):
|
||||||
|
self.write("""<option value="""+config['AUDIO']['inputdevice']+""" selected>"""+config['AUDIO']['inputdevice']+"""</option>""")
|
||||||
|
for c in audiodevicesinput:
|
||||||
|
self.write("""<option value="""+c+""">"""+c+"""</option>""")
|
||||||
|
self.write("""</select> Input from audio soundcard from the speaker output of TRX.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""[HAMLIB]<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB radio model:<select name="HAMLIB.rig_model">""")
|
||||||
|
if(config['HAMLIB']['rig_model']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['rig_model']+""" selected>"""+config['HAMLIB']['rig_model']+"""</option>""")
|
||||||
|
for c in rig_models:
|
||||||
|
self.write("""<option value="""+c+""">"""+c+"""</option>""")
|
||||||
|
self.write("""</select> Hamlib trx model.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB serial port:<select name="HAMLIB.rig_pathname">""")
|
||||||
|
if(config['HAMLIB']['rig_pathname']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['rig_pathname']+""" selected>"""+config['HAMLIB']['rig_pathname']+"""</option>""")
|
||||||
|
for c in comports:
|
||||||
|
self.write("""<option value="""+str(c.device)+""">"""+str(c.device)+"""</option>""")
|
||||||
|
self.write("""</select> Serial port of the CAT interface.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB radio rate:<select name="HAMLIB.rig_rate">""")
|
||||||
|
if(config['HAMLIB']['rig_rate']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['rig_rate']+""" selected>"""+config['HAMLIB']['rig_rate']+"""</option>""")
|
||||||
|
self.write("""<option value=230400>230400</option>""")
|
||||||
|
self.write("""<option value=115200>115200</option>""")
|
||||||
|
self.write("""<option value=57600>57600</option>""")
|
||||||
|
self.write("""<option value=38400>38400</option>""")
|
||||||
|
self.write("""<option value=19200>19200</option>""")
|
||||||
|
self.write("""<option value=9600>9600</option>""")
|
||||||
|
self.write("""<option value=4800>4800</option>""")
|
||||||
|
self.write("""<option value=2400>2400</option>""")
|
||||||
|
self.write("""<option value=1200>1200</option>""")
|
||||||
|
self.write("""<option value=600>600</option>""")
|
||||||
|
self.write("""<option value=300>300</option>""")
|
||||||
|
self.write("""<option value=150>150</option>""")
|
||||||
|
self.write("""</select> Serial port baud rate.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB auto tx poweroff:<select name="HAMLIB.trxautopower">""")
|
||||||
|
if(config['HAMLIB']['trxautopower']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['trxautopower']+""" selected>"""+config['HAMLIB']['trxautopower']+"""</option>""")
|
||||||
|
self.write("""<option value=\"True\">True</option>""")
|
||||||
|
self.write("""<option value=\"False\">False</option>""")
|
||||||
|
self.write("""</select> Set to auto power off the trx when it's not in use<br/><br/>""")
|
||||||
|
|
||||||
|
CDVALUE=""
|
||||||
|
if(config['HAMLIB']['data_bits']!=""):
|
||||||
|
CDVALUE=config['HAMLIB']['data_bits']
|
||||||
|
self.write("""HAMLIB serial data bits:<input type="text" name="HAMLIB.data_bits" value="""+CDVALUE+"""> Leave blank to use the HAMIB default value.<br/><br/>""")
|
||||||
|
|
||||||
|
CDVALUE=""
|
||||||
|
if(config['HAMLIB']['stop_bits']!=""):
|
||||||
|
CDVALUE=config['HAMLIB']['stop_bits']
|
||||||
|
self.write("""HAMLIB serial stop bits:<input type="text" name="HAMLIB.stop_bits" value="""+CDVALUE+"""> Leave blank to use the HAMIB default value.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB serial parity:<select name="HAMLIB.serial_parity">""")
|
||||||
|
if(config['HAMLIB']['serial_parity']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['serial_parity']+""" selected>"""+config['HAMLIB']['serial_parity']+"""</option>""")
|
||||||
|
self.write("""<option value=\"\"></option>""")
|
||||||
|
self.write("""<option value=\"None\">None</option>""")
|
||||||
|
self.write("""<option value=\"Odd\">Odd</option>""")
|
||||||
|
self.write("""<option value=\"Even\">Even</option>""")
|
||||||
|
self.write("""<option value=\"Mark\">Mark</option>""")
|
||||||
|
self.write("""<option value=\"Space\">Space</option>""")
|
||||||
|
self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB serial handshake:<select name="HAMLIB.serial_handshake">""")
|
||||||
|
if(config['HAMLIB']['serial_handshake']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['serial_handshake']+""" selected>"""+config['HAMLIB']['serial_handshake']+"""</option>""")
|
||||||
|
self.write("""<option value=\"\"></option>""")
|
||||||
|
self.write("""<option value=\"None\">None</option>""")
|
||||||
|
self.write("""<option value=\"XONXOFF\">XONXOFF</option>""")
|
||||||
|
self.write("""<option value=\"Hardware\">Hardware</option>""")
|
||||||
|
self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB dtr state:<select name="HAMLIB.dtr_state">""")
|
||||||
|
if(config['HAMLIB']['dtr_state']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['dtr_state']+""" selected>"""+config['HAMLIB']['dtr_state']+"""</option>""")
|
||||||
|
self.write("""<option value=\"\"></option>""")
|
||||||
|
self.write("""<option value=\"ON\">ON</option>""")
|
||||||
|
self.write("""<option value=\"OFF\">OFF</option>""")
|
||||||
|
self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB rts state:<select name="HAMLIB.rts_state">""")
|
||||||
|
if(config['HAMLIB']['rts_state']!=""):
|
||||||
|
self.write("""<option value="""+config['HAMLIB']['rts_state']+""" selected>"""+config['HAMLIB']['rts_state']+"""</option>""")
|
||||||
|
self.write("""<option value=\"\"></option>""")
|
||||||
|
self.write("""<option value=\"ON\">ON</option>""")
|
||||||
|
self.write("""<option value=\"OFF\">OFF</option>""")
|
||||||
|
self.write("""</select> Leave blank to use the HAMIB default value.<br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""[PANADAPTER]<br/><br/>""")
|
||||||
|
self.write("""PANADAPTER FI frequency (hz):<input type="text" name="PANADAPTER.center_freq" value="""+config['PANADAPTER']['center_freq']+"""><br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""HAMLIB radio rate (samples/s):<select name="PANADAPTER.sample_rate">""")
|
||||||
|
if(config['PANADAPTER']['sample_rate']!=""):
|
||||||
|
self.write("""<option value="""+config['PANADAPTER']['sample_rate']+""" selected>"""+config['PANADAPTER']['sample_rate']+"""</option>""")
|
||||||
|
self.write("""<option value=3200000>3200000</option>""")
|
||||||
|
self.write("""<option value=2880000>2880000</option>""")
|
||||||
|
self.write("""<option value=2400000>2400000</option>""")
|
||||||
|
self.write("""<option value=1800000>1800000</option>""")
|
||||||
|
self.write("""<option value=1440000>1440000</option>""")
|
||||||
|
self.write("""<option value=1200000>1200000</option>""")
|
||||||
|
self.write("""<option value=1020000>1020000</option>""")
|
||||||
|
self.write("""<option value=960000>960000</option>""")
|
||||||
|
self.write("""</select><br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""PANADAPTER frequency correction (ppm):<input type="text" name="PANADAPTER.freq_correction" value="""+config['PANADAPTER']['freq_correction']+"""><br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""PANADAPTER initial gain:<input type="text" name="PANADAPTER.gain" value="""+config['PANADAPTER']['gain']+"""><br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""PANADAPTER windowing:<select name="PANADAPTER.fft_window">""")
|
||||||
|
if(config['PANADAPTER']['fft_window']!=""):
|
||||||
|
self.write("""<option value="""+config['PANADAPTER']['fft_window']+""" selected>"""+config['PANADAPTER']['fft_window']+"""</option>""")
|
||||||
|
self.write("""<option value="bartlett">bartlett</option>""")
|
||||||
|
self.write("""<option value="blackman">blackman</option>""")
|
||||||
|
self.write("""<option value="hamming">hamming</option>""")
|
||||||
|
self.write("""<option value="hanning">hanning</option>""")
|
||||||
|
self.write("""</select><br/><br/>""")
|
||||||
|
|
||||||
|
self.write("""<input type="submit" value="Save & Restart server"><br/><br/></form>Possible problem:"""+e+"""</html>""")
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
|
||||||
|
if bool(config['SERVER']['auth']) and not self.current_user:
|
||||||
|
self.redirect("/login")
|
||||||
|
return
|
||||||
|
|
||||||
|
for x in self.request.arguments:
|
||||||
|
(s,o)=x.split(".")
|
||||||
|
v=self.get_argument(x)
|
||||||
|
print(s,o,v)
|
||||||
|
if config.has_option(s,o):
|
||||||
|
config[s][o]=v
|
||||||
|
with open('UHRR.conf', 'w') as configfile:
|
||||||
|
config.write(configfile)
|
||||||
|
self.write("""<html><head><script>window.setTimeout(function() {window.location.href = 'https://'+window.location.hostname+':'+ '"""+config['SERVER']['port']+"""';}, 10000);</script><head><body>You will be redirected automatically. Please wait...<br><img width="40px" height=40px" src="../img/spinner.gif"></body></html>""")
|
||||||
|
self.flush()
|
||||||
|
time.sleep(2)
|
||||||
|
os.system("sleep 2;./UHRR &")
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
############ Login ##############
|
||||||
|
class AuthLoginHandler(BaseHandler):
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
if not bool(config['SERVER']['auth']):
|
||||||
|
self.redirect("/")
|
||||||
|
return
|
||||||
|
self.write('<html><body><form action="/login" method="post">'
|
||||||
|
'CallSign: <input type="text" name="name"></br>'
|
||||||
|
'Password: <input type="password" name="passwd"></br>'
|
||||||
|
'<input type="submit" value="Sign in">'
|
||||||
|
'</form></body></html>')
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
if self.get_argument("name") != "" and self.get_argument("passwd") != "":
|
||||||
|
if self.bind(self.get_argument("name"),self.get_argument("passwd")):
|
||||||
|
self.set_secure_cookie("user", self.get_argument("name"))
|
||||||
|
self.set_cookie("callsign", self.get_argument("name"))
|
||||||
|
self.set_cookie("autha", "1")
|
||||||
|
else:
|
||||||
|
writte_log("Auth error for CallSign:"+str(self.get_argument("name")))
|
||||||
|
self.redirect("/")
|
||||||
|
|
||||||
|
def bind(self,user="",password=""):
|
||||||
|
retval = False
|
||||||
|
if (user!="" and password!=""):
|
||||||
|
if config['SERVER']['auth'].find("FILE") != -1: #test with users db file
|
||||||
|
f = open(config['SERVER']['db_users_file'], "r")
|
||||||
|
for x in f:
|
||||||
|
if x[0]!="#":
|
||||||
|
db=x.strip('\n').split(" ")
|
||||||
|
if db[0] == user and db[1]== password:
|
||||||
|
retval = True
|
||||||
|
break
|
||||||
|
if not retval and config['SERVER']['auth'].find("PAM") != -1:#test with pam module
|
||||||
|
if config['SERVER']['pam_account'].find(user) != -1:
|
||||||
|
import pam
|
||||||
|
retval = pam.authenticate(user, password)
|
||||||
|
return retval
|
||||||
|
|
||||||
|
class AuthLogoutHandler(BaseHandler):
|
||||||
|
def get(self):
|
||||||
|
self.clear_cookie("user")
|
||||||
|
self.clear_cookie("autha")
|
||||||
|
self.redirect(self.get_argument("next", "/"))
|
||||||
|
|
||||||
|
############ Main ##############
|
||||||
|
class MainHandler(BaseHandler):
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
print("Tornado current user:"+str(self.current_user))
|
||||||
|
if bool(config['SERVER']['auth']) and not self.current_user:
|
||||||
|
self.redirect("/login")
|
||||||
|
return
|
||||||
|
self.application.settings.get("compiled_template_cache", False)
|
||||||
|
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||||
|
self.render("www/index.html")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
try:
|
||||||
|
if is_rtlsdr_present:
|
||||||
|
threadFFT = loadFFTdata()
|
||||||
|
threadFFT.start()
|
||||||
|
|
||||||
|
threadloadWavdata = loadWavdata()
|
||||||
|
threadloadWavdata.start()
|
||||||
|
|
||||||
|
CTRX = TRXRIG()
|
||||||
|
|
||||||
|
threadticksTRXRIG = ticksTRXRIG()
|
||||||
|
threadticksTRXRIG.start()
|
||||||
|
|
||||||
|
if(config['HAMLIB']['trxautopower']=="True"):
|
||||||
|
threadsurveilTRX = threadtimeoutTRXshutdown()
|
||||||
|
threadsurveilTRX.start()
|
||||||
|
|
||||||
|
|
||||||
|
app = tornado.web.Application([
|
||||||
|
(r'/login', AuthLoginHandler),
|
||||||
|
(r'/logout', AuthLogoutHandler),
|
||||||
|
(r'/WSaudioRX', WS_AudioRXHandler),
|
||||||
|
(r'/WSaudioTX', WS_AudioTXHandler),
|
||||||
|
(r'/WSCTRX', WS_ControlTRX),
|
||||||
|
(r'/WSpanFFT', WS_panFFTHandler),
|
||||||
|
(r'/(panfft.*)', tornado.web.StaticFileHandler, { 'path' : './www/panadapter' }),
|
||||||
|
(r'/CONFIG', ConfigHandler),
|
||||||
|
(r'/', MainHandler),
|
||||||
|
(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
|
||||||
|
],debug=bool(config['SERVER']['debug']), websocket_ping_interval=10, cookie_secret=config['SERVER']['cookie_secret'])
|
||||||
|
except:
|
||||||
|
e = str(sys.exc_info())
|
||||||
|
print(e)
|
||||||
|
app = tornado.web.Application([
|
||||||
|
(r'/CONFIG', ConfigHandler),
|
||||||
|
(r'/', ConfigHandler),
|
||||||
|
(r'/(.*)', tornado.web.StaticFileHandler, { 'path' : './www' })
|
||||||
|
],debug=bool(config['SERVER']['debug']))
|
||||||
|
|
||||||
|
http_server = tornado.httpserver.HTTPServer(app, ssl_options={
|
||||||
|
"certfile": os.path.join(config['SERVER']['certfile']),
|
||||||
|
"keyfile": os.path.join(config['SERVER']['keyfile']),
|
||||||
|
})
|
||||||
|
http_server.listen(int(config['SERVER']['port']))
|
||||||
|
print('HTTP server started.')
|
||||||
|
tornado.ioloop.IOLoop.instance().start()
|
||||||
|
|
39
UHRR.conf
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
[SERVER]
|
||||||
|
port = 8888
|
||||||
|
certfile = UHRH.crt
|
||||||
|
keyfile = UHRH.key
|
||||||
|
auth =
|
||||||
|
cookie_secret = L8LwECiNRxq2N0N2eGxx9MZlrpmuMEimlydNX/vt1LM=
|
||||||
|
db_users_file = UHRR_users.db
|
||||||
|
pam_account = pi
|
||||||
|
log_file = UHRR.log
|
||||||
|
debug = True
|
||||||
|
|
||||||
|
[CTRL]
|
||||||
|
interval_smeter_update = 0.5
|
||||||
|
debug = True
|
||||||
|
|
||||||
|
[AUDIO]
|
||||||
|
outputdevice = plughw:CARD=mchf,DEV=0
|
||||||
|
inputdevice = plughw:CARD=mchf,DEV=0
|
||||||
|
|
||||||
|
[HAMLIB]
|
||||||
|
rig_pathname = /dev/ttyACM0
|
||||||
|
retry = 5
|
||||||
|
rig_model = FT817
|
||||||
|
trxautopower = True
|
||||||
|
rig_rate = 38400
|
||||||
|
data_bits =
|
||||||
|
stop_bits =
|
||||||
|
serial_parity =
|
||||||
|
serial_handshake =
|
||||||
|
dtr_state =
|
||||||
|
rts_state =
|
||||||
|
|
||||||
|
[PANADAPTER]
|
||||||
|
sample_rate = 960000
|
||||||
|
center_freq = 68330000
|
||||||
|
freq_correction = 1
|
||||||
|
gain = 10
|
||||||
|
fft_window = hamming
|
||||||
|
|
4
UHRR_users.db
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#one line per account like :
|
||||||
|
#1AAW Paul!
|
||||||
|
#F4HTB test
|
||||||
|
F4IYT matrix
|
3
go.sh
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
in/bash
|
||||||
|
PYTHONPATH=/usr/local/lib/python3.7/site-packages:$PYTHONPATH ./UHRR
|
||||||
|
exit 0
|
18
install.sh
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/bash
|
||||||
|
sudo apt-get install -y git python3 python3-pip python3-numpy python3-tornado python3-serial python3-pyaudio rtl-sdr
|
||||||
|
sudo pip3 install pyalsaaudio pam pyrtlsdr
|
||||||
|
sudo apt-get autoremove -y --purge python3-libhamlib2
|
||||||
|
sudo apt-get install -y autoconf automake libtool swig
|
||||||
|
cd ~/
|
||||||
|
git clone https://github.com/Hamlib/Hamlib.git
|
||||||
|
cd Hamlib/
|
||||||
|
./bootstrap
|
||||||
|
./configure --with-python-binding PYTHON=$(which python3)
|
||||||
|
make all && sudo make install && cd bindings && make && sudo make install && sudo ldconfig
|
||||||
|
sudo reboot
|
||||||
|
|
||||||
|
# RUN pour test
|
||||||
|
#PYTHONPATH=/usr/local/lib/python3.7/site-packages:$PYTHONPATH ./UHRR
|
||||||
|
|
||||||
|
|
||||||
|
# si bug HamLib sudo apt-get install python3-libhamlib2
|
24
opus/LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2012, SvartalF
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the SvartalF nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
1
opus/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Python bindings to the libopus, IETF low-delay audio codec"""
|
BIN
opus/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
opus/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
opus/__pycache__/decoder.cpython-310.pyc
Normal file
BIN
opus/__pycache__/decoder.cpython-37.pyc
Normal file
BIN
opus/__pycache__/exceptions.cpython-310.pyc
Normal file
BIN
opus/__pycache__/exceptions.cpython-37.pyc
Normal file
9
opus/api/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import ctypes
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
|
||||||
|
libopus = ctypes.CDLL(find_library('opus'))
|
||||||
|
|
||||||
|
c_int_pointer = ctypes.POINTER(ctypes.c_int)
|
||||||
|
c_int16_pointer = ctypes.POINTER(ctypes.c_int16)
|
||||||
|
c_float_pointer = ctypes.POINTER(ctypes.c_float)
|
BIN
opus/api/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
opus/api/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
opus/api/__pycache__/constants.cpython-310.pyc
Normal file
BIN
opus/api/__pycache__/constants.cpython-37.pyc
Normal file
BIN
opus/api/__pycache__/ctl.cpython-310.pyc
Normal file
BIN
opus/api/__pycache__/ctl.cpython-37.pyc
Normal file
BIN
opus/api/__pycache__/decoder.cpython-310.pyc
Normal file
BIN
opus/api/__pycache__/decoder.cpython-37.pyc
Normal file
BIN
opus/api/__pycache__/info.cpython-310.pyc
Normal file
BIN
opus/api/__pycache__/info.cpython-37.pyc
Normal file
71
opus/api/constants.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Matches to `opus_defines.h`"""
|
||||||
|
|
||||||
|
# No Error
|
||||||
|
OK = 0
|
||||||
|
|
||||||
|
# One or more invalid/out of range arguments
|
||||||
|
BAD_ARG = -1
|
||||||
|
|
||||||
|
# The mode struct passed is invalid
|
||||||
|
BUFFER_TOO_SMALL = -2
|
||||||
|
|
||||||
|
# The compressed data passed is corrupted
|
||||||
|
INVALID_PACKET = -4
|
||||||
|
|
||||||
|
# Invalid/unsupported request number
|
||||||
|
UNIMPLEMENTED = -5
|
||||||
|
|
||||||
|
|
||||||
|
# Pre-defined values for CTL interface
|
||||||
|
|
||||||
|
APPLICATION_VOIP = 2048
|
||||||
|
APPLICATION_AUDIO = 2049
|
||||||
|
APPLICATION_RESTRICTED_LOWDELAY = 2051
|
||||||
|
|
||||||
|
SIGNAL_MUSIC = 3002
|
||||||
|
|
||||||
|
# Values for the various encoder CTLs
|
||||||
|
|
||||||
|
SET_APPLICATION_REQUEST = 4000
|
||||||
|
GET_APPLICATION_REQUEST = 4001
|
||||||
|
SET_BITRATE_REQUEST = 4002
|
||||||
|
GET_BITRATE_REQUEST = 4003
|
||||||
|
SET_MAX_BANDWIDTH_REQUEST = 4004
|
||||||
|
GET_MAX_BANDWIDTH_REQUEST = 4005
|
||||||
|
SET_VBR_REQUEST = 4006
|
||||||
|
GET_VBR_REQUEST = 4007
|
||||||
|
SET_BANDWIDTH_REQUEST = 4008
|
||||||
|
GET_BANDWIDTH_REQUEST = 4009
|
||||||
|
SET_COMPLEXITY_REQUEST = 4010
|
||||||
|
GET_COMPLEXITY_REQUEST = 4011
|
||||||
|
SET_INBAND_FEC_REQUEST = 4012
|
||||||
|
GET_INBAND_FEC_REQUEST = 4013
|
||||||
|
SET_PACKET_LOSS_PERC_REQUEST = 4014
|
||||||
|
GET_PACKET_LOSS_PERC_REQUEST = 4015
|
||||||
|
SET_DTX_REQUEST = 4016
|
||||||
|
GET_DTX_REQUEST = 4017
|
||||||
|
SET_VBR_CONSTRAINT_REQUEST = 4020
|
||||||
|
GET_VBR_CONSTRAINT_REQUEST = 4021
|
||||||
|
SET_FORCE_CHANNELS_REQUEST = 4022
|
||||||
|
GET_FORCE_CHANNELS_REQUEST = 4023
|
||||||
|
SET_SIGNAL_REQUEST = 4024
|
||||||
|
GET_SIGNAL_REQUEST = 4025
|
||||||
|
GET_LOOKAHEAD_REQUEST = 4027
|
||||||
|
RESET_STATE = 4028
|
||||||
|
GET_SAMPLE_RATE_REQUEST = 4029
|
||||||
|
GET_FINAL_RANGE_REQUEST = 4031
|
||||||
|
GET_PITCH_REQUEST = 4033
|
||||||
|
SET_GAIN_REQUEST = 4034
|
||||||
|
GET_GAIN_REQUEST = 4045
|
||||||
|
SET_LSB_DEPTH_REQUEST = 4036
|
||||||
|
GET_LSB_DEPTH_REQUEST = 4037
|
||||||
|
|
||||||
|
AUTO = -1000
|
||||||
|
|
||||||
|
BANDWIDTH_NARROWBAND = 1101
|
||||||
|
BANDWIDTH_MEDIUMBAND = 1102
|
||||||
|
BANDWIDTH_WIDEBAND = 1103
|
||||||
|
BANDWIDTH_SUPERWIDEBAND = 1104
|
||||||
|
BANDWIDTH_FULLBAND = 1105
|
173
opus/api/ctl.py
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
"""CTL macros rewritten to Python
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
from opus.api import decoder, ctl
|
||||||
|
|
||||||
|
dec = decoder.create(48000, 2)
|
||||||
|
decoder.ctl(dec, ctl.set_gain, -15)
|
||||||
|
gain_value = decoder.ctl(dec, ctl.get_gain)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
from opus.api import constants
|
||||||
|
from opus.exceptions import OpusError
|
||||||
|
|
||||||
|
|
||||||
|
def query(request):
|
||||||
|
"""Query encoder/decoder with a request value"""
|
||||||
|
|
||||||
|
def inner(func, obj):
|
||||||
|
result_code = func(obj, request)
|
||||||
|
|
||||||
|
if result_code is not constants.OK:
|
||||||
|
raise OpusError(result_code)
|
||||||
|
|
||||||
|
return result_code
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
def get(request, result_type):
|
||||||
|
"""Get CTL value from a encoder/decoder"""
|
||||||
|
|
||||||
|
def inner(func, obj):
|
||||||
|
result = result_type()
|
||||||
|
result_code = func(obj, request, ctypes.byref(result))
|
||||||
|
|
||||||
|
if result_code is not constants.OK:
|
||||||
|
raise OpusError(result_code)
|
||||||
|
|
||||||
|
return result.value
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
def set(request):
|
||||||
|
"""Set new CTL value to a encoder/decoder"""
|
||||||
|
|
||||||
|
def inner(func, obj, value):
|
||||||
|
result_code = func(obj, request, value)
|
||||||
|
if result_code is not constants.OK:
|
||||||
|
raise OpusError(result_code)
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generic CTLs
|
||||||
|
#
|
||||||
|
|
||||||
|
# Resets the codec state to be equivalent to a freshly initialized state
|
||||||
|
reset_state = query(constants.RESET_STATE)
|
||||||
|
|
||||||
|
# Gets the final state of the codec's entropy coder
|
||||||
|
get_final_range = get(constants.GET_FINAL_RANGE_REQUEST, ctypes.c_uint)
|
||||||
|
|
||||||
|
# Gets the encoder's configured bandpass or the decoder's last bandpass
|
||||||
|
get_bandwidth = get(constants.GET_BANDWIDTH_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Gets the pitch of the last decoded frame, if available
|
||||||
|
get_pitch = get(constants.GET_PITCH_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the depth of signal being encoded
|
||||||
|
set_lsb_depth = set(constants.SET_LSB_DEPTH_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's configured signal depth
|
||||||
|
get_lsb_depth = get(constants.GET_LSB_DEPTH_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Decoder related CTLs
|
||||||
|
#
|
||||||
|
|
||||||
|
# Gets the decoder's configured gain adjustment
|
||||||
|
get_gain = get(constants.GET_GAIN_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures decoder gain adjustment
|
||||||
|
set_gain = set(constants.SET_GAIN_REQUEST)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Encoder related CTLs
|
||||||
|
#
|
||||||
|
|
||||||
|
# Configures the encoder's computational complexity
|
||||||
|
set_complexity = set(constants.SET_COMPLEXITY_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's complexity configuration
|
||||||
|
get_complexity = get(constants.GET_COMPLEXITY_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the bitrate in the encoder
|
||||||
|
set_bitrate = set(constants.SET_BITRATE_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's bitrate configuration
|
||||||
|
get_bitrate = get(constants.GET_BITRATE_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Enables or disables variable bitrate (VBR) in the encoder
|
||||||
|
set_vbr = set(constants.SET_VBR_REQUEST)
|
||||||
|
|
||||||
|
# Determine if variable bitrate (VBR) is enabled in the encoder
|
||||||
|
get_vbr = get(constants.GET_VBR_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Enables or disables constrained VBR in the encoder
|
||||||
|
set_vbr_constraint = set(constants.SET_VBR_CONSTRAINT_REQUEST)
|
||||||
|
|
||||||
|
# Determine if constrained VBR is enabled in the encoder
|
||||||
|
get_vbr_constraint = get(constants.GET_VBR_CONSTRAINT_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures mono/stereo forcing in the encoder
|
||||||
|
set_force_channels = set(constants.SET_FORCE_CHANNELS_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's forced channel configuration
|
||||||
|
get_force_channels = get(constants.GET_FORCE_CHANNELS_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the maximum bandpass that the encoder will select automatically
|
||||||
|
set_max_bandwidth = set(constants.SET_MAX_BANDWIDTH_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's configured maximum allowed bandpass
|
||||||
|
get_max_bandwidth = get(constants.GET_MAX_BANDWIDTH_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Sets the encoder's bandpass to a specific value
|
||||||
|
set_bandwidth = set(constants.SET_BANDWIDTH_REQUEST)
|
||||||
|
|
||||||
|
# Configures the type of signal being encoded
|
||||||
|
set_signal = set(constants.SET_SIGNAL_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's configured signal type
|
||||||
|
get_signal = get(constants.GET_SIGNAL_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the encoder's intended application
|
||||||
|
set_application = set(constants.SET_APPLICATION_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's configured application
|
||||||
|
get_application = get(constants.GET_APPLICATION_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Gets the sampling rate the encoder or decoder was initialized with
|
||||||
|
get_sample_rate = get(constants.GET_SAMPLE_RATE_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Gets the total samples of delay added by the entire codec
|
||||||
|
get_lookahead = get(constants.GET_LOOKAHEAD_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the encoder's use of inband forward error correction (FEC)
|
||||||
|
set_inband_fec = set(constants.SET_INBAND_FEC_REQUEST)
|
||||||
|
|
||||||
|
# Gets encoder's configured use of inband forward error correction
|
||||||
|
get_inband_fec = get(constants.GET_INBAND_FEC_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the encoder's expected packet loss percentage
|
||||||
|
set_packet_loss_perc = set(constants.SET_PACKET_LOSS_PERC_REQUEST)
|
||||||
|
|
||||||
|
# Gets the encoder's configured packet loss percentage
|
||||||
|
get_packet_loss_perc = get(constants.GET_PACKET_LOSS_PERC_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
# Configures the encoder's use of discontinuous transmission (DTX)
|
||||||
|
set_dtx = set(constants.SET_DTX_REQUEST)
|
||||||
|
|
||||||
|
# Gets encoder's configured use of discontinuous transmission
|
||||||
|
get_dtx = get(constants.GET_DTX_REQUEST, ctypes.c_int)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Other stuff
|
||||||
|
#
|
||||||
|
|
||||||
|
unimplemented = query(constants.UNIMPLEMENTED)
|
187
opus/api/decoder.py
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import array
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
from opus.api import libopus, c_int_pointer, c_int16_pointer, c_float_pointer
|
||||||
|
from opus.exceptions import OpusError
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(ctypes.Structure):
|
||||||
|
"""Opus decoder state.
|
||||||
|
|
||||||
|
This contains the complete state of an Opus decoder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
DecoderPointer = ctypes.POINTER(Decoder)
|
||||||
|
|
||||||
|
|
||||||
|
get_size = libopus.opus_decoder_get_size
|
||||||
|
get_size.argtypes = (ctypes.c_int,)
|
||||||
|
get_size.restype = ctypes.c_int
|
||||||
|
get_size.__doc__ = 'Gets the size of an OpusDecoder structure'
|
||||||
|
|
||||||
|
|
||||||
|
_create = libopus.opus_decoder_create
|
||||||
|
_create.argtypes = (ctypes.c_int, ctypes.c_int, c_int_pointer)
|
||||||
|
_create.restype = DecoderPointer
|
||||||
|
|
||||||
|
|
||||||
|
def create(fs, channels):
|
||||||
|
"""Allocates and initializes a decoder state"""
|
||||||
|
|
||||||
|
result_code = ctypes.c_int()
|
||||||
|
|
||||||
|
result = _create(fs, channels, ctypes.byref(result_code))
|
||||||
|
if result_code.value is not 0:
|
||||||
|
raise OpusError(result_code.value)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_packet_get_bandwidth = libopus.opus_packet_get_bandwidth
|
||||||
|
_packet_get_bandwidth.argtypes = (ctypes.c_char_p,)
|
||||||
|
_packet_get_bandwidth.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def packet_get_bandwidth(data):
|
||||||
|
"""Gets the bandwidth of an Opus packet."""
|
||||||
|
|
||||||
|
data_pointer = ctypes.c_char_p(data)
|
||||||
|
|
||||||
|
result = _packet_get_bandwidth(data_pointer)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_packet_get_nb_channels = libopus.opus_packet_get_nb_channels
|
||||||
|
_packet_get_nb_channels.argtypes = (ctypes.c_char_p,)
|
||||||
|
_packet_get_nb_channels.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def packet_get_nb_channels(data):
|
||||||
|
"""Gets the number of channels from an Opus packet"""
|
||||||
|
|
||||||
|
data_pointer = ctypes.c_char_p(data)
|
||||||
|
|
||||||
|
result = _packet_get_nb_channels(data_pointer)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_packet_get_nb_frames = libopus.opus_packet_get_nb_frames
|
||||||
|
_packet_get_nb_frames.argtypes = (ctypes.c_char_p, ctypes.c_int)
|
||||||
|
_packet_get_nb_frames.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def packet_get_nb_frames(data, length=None):
|
||||||
|
"""Gets the number of frames in an Opus packet"""
|
||||||
|
|
||||||
|
data_pointer = ctypes.c_char_p(data)
|
||||||
|
if length is None:
|
||||||
|
length = len(data)
|
||||||
|
|
||||||
|
result = _packet_get_nb_frames(data_pointer, ctypes.c_int(length))
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_packet_get_samples_per_frame = libopus.opus_packet_get_samples_per_frame
|
||||||
|
_packet_get_samples_per_frame.argtypes = (ctypes.c_char_p, ctypes.c_int)
|
||||||
|
_packet_get_samples_per_frame.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def packet_get_samples_per_frame(data, fs):
|
||||||
|
"""Gets the number of samples per frame from an Opus packet"""
|
||||||
|
|
||||||
|
data_pointer = ctypes.c_char_p(data)
|
||||||
|
|
||||||
|
result = _packet_get_nb_frames(data_pointer, ctypes.c_int(fs))
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_get_nb_samples = libopus.opus_decoder_get_nb_samples
|
||||||
|
_get_nb_samples.argtypes = (DecoderPointer, ctypes.c_char_p, ctypes.c_int32)
|
||||||
|
_get_nb_samples.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def get_nb_samples(decoder, packet, length):
|
||||||
|
result = _get_nb_samples(decoder, packet, length)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_decode = libopus.opus_decode
|
||||||
|
_decode.argtypes = (DecoderPointer, ctypes.c_char_p, ctypes.c_int32, c_int16_pointer, ctypes.c_int, ctypes.c_int)
|
||||||
|
_decode.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def decode(decoder, data, length, frame_size, decode_fec, channels=2):
|
||||||
|
"""Decode an Opus frame
|
||||||
|
|
||||||
|
Unlike the `opus_decode` function , this function takes an additional parameter `channels`,
|
||||||
|
which indicates the number of channels in the frame
|
||||||
|
"""
|
||||||
|
|
||||||
|
pcm_size = frame_size * channels * ctypes.sizeof(ctypes.c_int16)
|
||||||
|
pcm = (ctypes.c_int16 * pcm_size)()
|
||||||
|
pcm_pointer = ctypes.cast(pcm, c_int16_pointer)
|
||||||
|
|
||||||
|
# Converting from a boolean to int
|
||||||
|
decode_fec = int(bool(decode_fec))
|
||||||
|
|
||||||
|
result = _decode(decoder, data, length, pcm_pointer, frame_size, decode_fec)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return array.array('h', pcm[ :result * channels ]).tostring()
|
||||||
|
|
||||||
|
|
||||||
|
_decode_float = libopus.opus_decode_float
|
||||||
|
_decode_float.argtypes = (DecoderPointer, ctypes.c_char_p, ctypes.c_int32, c_float_pointer, ctypes.c_int, ctypes.c_int)
|
||||||
|
_decode_float.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def decode_float(decoder, data, length, frame_size, decode_fec, channels=2):
|
||||||
|
pcm_size = frame_size * channels * ctypes.sizeof(ctypes.c_float)
|
||||||
|
pcm = (ctypes.c_float * pcm_size)()
|
||||||
|
pcm_pointer = ctypes.cast(pcm, c_float_pointer)
|
||||||
|
|
||||||
|
# Converting from a boolean to int
|
||||||
|
decode_fec = int(bool(decode_fec))
|
||||||
|
|
||||||
|
result = _decode_float(decoder, data, length, pcm_pointer, frame_size, decode_fec)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return array.array('f', pcm[ : result * channels ]).tostring()
|
||||||
|
|
||||||
|
|
||||||
|
_ctl = libopus.opus_decoder_ctl
|
||||||
|
_ctl.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def ctl(decoder, request, value=None):
|
||||||
|
if value is not None:
|
||||||
|
return request(_ctl, decoder, value)
|
||||||
|
|
||||||
|
return request(_ctl, decoder)
|
||||||
|
|
||||||
|
|
||||||
|
destroy = libopus.opus_decoder_destroy
|
||||||
|
destroy.argtypes = (DecoderPointer,)
|
||||||
|
destroy.restype = None
|
||||||
|
destroy.__doc__ = 'Frees an OpusDecoder allocated by opus_decoder_create()'
|
105
opus/api/encoder.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import array
|
||||||
|
|
||||||
|
from opus.api import constants, libopus, c_int_pointer, c_int16_pointer, c_float_pointer
|
||||||
|
from opus.exceptions import OpusError
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(ctypes.Structure):
|
||||||
|
"""Opus encoder state.
|
||||||
|
|
||||||
|
This contains the complete state of an Opus encoder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
EncoderPointer = ctypes.POINTER(Encoder)
|
||||||
|
|
||||||
|
|
||||||
|
_get_size = libopus.opus_encoder_get_size
|
||||||
|
_get_size.argtypes = (ctypes.c_int,)
|
||||||
|
_get_size.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def get_size(channels):
|
||||||
|
"""Gets the size of an OpusEncoder structure."""
|
||||||
|
|
||||||
|
if not channels in (1, 2):
|
||||||
|
raise ValueError('Wrong channels value. Must be equal to 1 or 2')
|
||||||
|
|
||||||
|
return _get_size(channels)
|
||||||
|
|
||||||
|
|
||||||
|
_create = libopus.opus_encoder_create
|
||||||
|
_create.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int, c_int_pointer)
|
||||||
|
_create.restype = EncoderPointer
|
||||||
|
|
||||||
|
|
||||||
|
def create(fs, channels, application):
|
||||||
|
"""Allocates and initializes an encoder state."""
|
||||||
|
|
||||||
|
result_code = ctypes.c_int()
|
||||||
|
|
||||||
|
result = _create(fs, channels, application, ctypes.byref(result_code))
|
||||||
|
if result_code.value is not constants.OK:
|
||||||
|
raise OpusError(result_code.value)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_ctl = libopus.opus_encoder_ctl
|
||||||
|
_ctl.restype = ctypes.c_int
|
||||||
|
|
||||||
|
|
||||||
|
def ctl(encoder, request, value=None):
|
||||||
|
if value is not None:
|
||||||
|
return request(_ctl, encoder, value)
|
||||||
|
|
||||||
|
return request(_ctl, encoder)
|
||||||
|
|
||||||
|
|
||||||
|
_encode = libopus.opus_encode
|
||||||
|
_encode.argtypes = (EncoderPointer, c_int16_pointer, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32)
|
||||||
|
_encode.restype = ctypes.c_int32
|
||||||
|
|
||||||
|
|
||||||
|
def encode(encoder, pcm, frame_size, max_data_bytes):
|
||||||
|
"""Encodes an Opus frame
|
||||||
|
|
||||||
|
Returns string output payload
|
||||||
|
"""
|
||||||
|
|
||||||
|
pcm = ctypes.cast(pcm, c_int16_pointer)
|
||||||
|
data = (ctypes.c_char * max_data_bytes)()
|
||||||
|
|
||||||
|
result = _encode(encoder, pcm, frame_size, data, max_data_bytes)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return array.array('c', data[:result]).tostring()
|
||||||
|
|
||||||
|
|
||||||
|
_encode_float = libopus.opus_encode_float
|
||||||
|
_encode_float.argtypes = (EncoderPointer, c_float_pointer, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32)
|
||||||
|
_encode_float.restype = ctypes.c_int32
|
||||||
|
|
||||||
|
|
||||||
|
def encode_float(encoder, pcm, frame_size, max_data_bytes):
|
||||||
|
"""Encodes an Opus frame from floating point input"""
|
||||||
|
|
||||||
|
pcm = ctypes.cast(pcm, c_float_pointer)
|
||||||
|
data = (ctypes.c_char * max_data_bytes)()
|
||||||
|
|
||||||
|
result = _encode_float(encoder, pcm, frame_size, data, max_data_bytes)
|
||||||
|
if result < 0:
|
||||||
|
raise OpusError(result)
|
||||||
|
|
||||||
|
return array.array('c', data[:result]).tostring()
|
||||||
|
|
||||||
|
|
||||||
|
destroy = libopus.opus_encoder_destroy
|
||||||
|
destroy.argtypes = (EncoderPointer,)
|
||||||
|
destroy.restype = None
|
||||||
|
destroy.__doc__ = "Frees an OpusEncoder allocated by opus_encoder_create()"
|
17
opus/api/info.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
from opus.api import libopus
|
||||||
|
|
||||||
|
|
||||||
|
strerror = libopus.opus_strerror
|
||||||
|
strerror.argtypes = (ctypes.c_int,)
|
||||||
|
strerror.restype = ctypes.c_char_p
|
||||||
|
strerror.__doc__ = '''Converts an opus error code into a human readable string'''
|
||||||
|
|
||||||
|
|
||||||
|
get_version_string = libopus.opus_get_version_string
|
||||||
|
get_version_string.argtypes = None
|
||||||
|
get_version_string.restype = ctypes.c_char_p
|
||||||
|
get_version_string.__doc__ = 'Gets the libopus version string'
|
59
opus/decoder.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
"""High-level interface to a opus.api.decoder functions"""
|
||||||
|
|
||||||
|
from opus.api import decoder, ctl
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(object):
|
||||||
|
|
||||||
|
def __init__(self, fs, channels):
|
||||||
|
"""
|
||||||
|
Parameters:
|
||||||
|
fs : sampling rate
|
||||||
|
channels : number of channels
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._fs = fs
|
||||||
|
self._channels = channels
|
||||||
|
self._state = decoder.create(fs, channels)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, '_state'):
|
||||||
|
# Destroying state only if __init__ completed successfully
|
||||||
|
decoder.destroy(self._state)
|
||||||
|
|
||||||
|
def reset_state(self):
|
||||||
|
"""Resets the codec state to be equivalent to a freshly initialized state"""
|
||||||
|
|
||||||
|
decoder.ctl(self._state, ctl.reset_state)
|
||||||
|
|
||||||
|
def decode(self, data, frame_size, decode_fec=False):
|
||||||
|
return decoder.decode(self._state, data, len(data), frame_size, decode_fec, channels=self._channels)
|
||||||
|
|
||||||
|
def decode_float(self, data, frame_size, decode_fec=False):
|
||||||
|
return decoder.decode_float(self._state, data, len(data), frame_size, decode_fec, channels=self._channels)
|
||||||
|
|
||||||
|
# CTL interfaces
|
||||||
|
|
||||||
|
_get_final_range = lambda self: decoder.ctl(self._state, ctl.get_final_range)
|
||||||
|
|
||||||
|
final_range = property(_get_final_range)
|
||||||
|
|
||||||
|
_get_bandwidth = lambda self: decoder.ctl(self._state, ctl.get_bandwidth)
|
||||||
|
|
||||||
|
bandwidth = property(_get_bandwidth)
|
||||||
|
|
||||||
|
_get_pitch = lambda self: decoder.ctl(self._state, ctl.get_pitch)
|
||||||
|
|
||||||
|
pitch = property(_get_pitch)
|
||||||
|
|
||||||
|
_get_lsb_depth = lambda self: decoder.ctl(self._state, ctl.get_lsb_depth)
|
||||||
|
|
||||||
|
_set_lsb_depth = lambda self, x: decoder.ctl(self._state, ctl.set_lsb_depth, x)
|
||||||
|
|
||||||
|
lsb_depth = property(_get_lsb_depth, _set_lsb_depth)
|
||||||
|
|
||||||
|
_get_gain = lambda self: decoder.ctl(self._state, ctl.get_gain)
|
||||||
|
|
||||||
|
_set_gain = lambda self, x: decoder.ctl(self._state, ctl.set_gain, x)
|
||||||
|
|
||||||
|
gain = property(_get_gain, _set_gain)
|
143
opus/encoder.py
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
"""High-level interface to a opus.api.encoder functions"""
|
||||||
|
|
||||||
|
from opus.api import encoder, ctl, constants
|
||||||
|
|
||||||
|
APPLICATION_TYPES_MAP = {
|
||||||
|
'voip': constants.APPLICATION_VOIP,
|
||||||
|
'audio': constants.APPLICATION_AUDIO,
|
||||||
|
'restricted_lowdelay': constants.APPLICATION_RESTRICTED_LOWDELAY,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(object):
|
||||||
|
|
||||||
|
def __init__(self, fs, channels, application):
|
||||||
|
"""
|
||||||
|
Parameters:
|
||||||
|
fs : sampling rate
|
||||||
|
channels : number of channels
|
||||||
|
"""
|
||||||
|
|
||||||
|
if application in APPLICATION_TYPES_MAP.keys():
|
||||||
|
application = APPLICATION_TYPES_MAP[application]
|
||||||
|
elif application in APPLICATION_TYPES_MAP.values():
|
||||||
|
pass # Nothing to do here
|
||||||
|
else:
|
||||||
|
raise ValueError("`application` value must be in 'voip', 'audio' or 'restricted_lowdelay'")
|
||||||
|
|
||||||
|
self._fs = fs
|
||||||
|
self._channels = channels
|
||||||
|
self._application = application
|
||||||
|
self._state = encoder.create(fs, channels, application)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, '_state'):
|
||||||
|
# Destroying state only if __init__ completed successfully
|
||||||
|
encoder.destroy(self._state)
|
||||||
|
|
||||||
|
def reset_state(self):
|
||||||
|
"""Resets the codec state to be equivalent to a freshly initialized state"""
|
||||||
|
|
||||||
|
encoder.ctl(self._state, ctl.reset_state)
|
||||||
|
|
||||||
|
def encode(self, data, frame_size):
|
||||||
|
return encoder.encode(self._state, data, frame_size, len(data))
|
||||||
|
|
||||||
|
def encode_float(self, data, frame_size, decode_fec=False):
|
||||||
|
return encoder.encode_float(self._state, data, frame_size, len(data))
|
||||||
|
|
||||||
|
# CTL interfaces
|
||||||
|
|
||||||
|
_get_final_range = lambda self: encoder.ctl(self._state, ctl.get_final_range)
|
||||||
|
|
||||||
|
final_range = property(_get_final_range)
|
||||||
|
|
||||||
|
_get_bandwidth = lambda self: encoder.ctl(self._state, ctl.get_bandwidth)
|
||||||
|
|
||||||
|
bandwidth = property(_get_bandwidth)
|
||||||
|
|
||||||
|
_get_pitch = lambda self: encoder.ctl(self._state, ctl.get_pitch)
|
||||||
|
|
||||||
|
pitch = property(_get_pitch)
|
||||||
|
|
||||||
|
_get_lsb_depth = lambda self: encoder.ctl(self._state, ctl.get_lsb_depth)
|
||||||
|
|
||||||
|
_set_lsb_depth = lambda self, x: encoder.ctl(self._state, ctl.set_lsb_depth, x)
|
||||||
|
|
||||||
|
lsb_depth = property(_get_lsb_depth, _set_lsb_depth)
|
||||||
|
|
||||||
|
_get_complexity = lambda self: encoder.ctl(self._state, ctl.get_complexity)
|
||||||
|
|
||||||
|
_set_complexity = lambda self, x: encoder.ctl(self._state, ctl.set_complexity, x)
|
||||||
|
|
||||||
|
complexity = property(_get_complexity, _set_complexity)
|
||||||
|
|
||||||
|
_get_bitrate = lambda self: encoder.ctl(self._state, ctl.get_bitrate)
|
||||||
|
|
||||||
|
_set_bitrate = lambda self, x: encoder.ctl(self._state, ctl.set_bitrate, x)
|
||||||
|
|
||||||
|
bitrate = property(_get_bitrate, _set_bitrate)
|
||||||
|
|
||||||
|
_get_vbr = lambda self: encoder.ctl(self._state, ctl.get_vbr)
|
||||||
|
|
||||||
|
_set_vbr = lambda self, x: encoder.ctl(self._state, ctl.set_vbr, x)
|
||||||
|
|
||||||
|
vbr = property(_get_vbr, _set_vbr)
|
||||||
|
|
||||||
|
_get_vbr_constraint = lambda self: encoder.ctl(self._state, ctl.get_vbr_constraint)
|
||||||
|
|
||||||
|
_set_vbr_constraint = lambda self, x: encoder.ctl(self._state, ctl.set_vbr_constraint, x)
|
||||||
|
|
||||||
|
vbr_constraint = property(_get_vbr_constraint, _set_vbr_constraint)
|
||||||
|
|
||||||
|
_get_force_channels = lambda self: encoder.ctl(self._state, ctl.get_force_channels)
|
||||||
|
|
||||||
|
_set_force_channels = lambda self, x: encoder.ctl(self._state, ctl.set_force_channels, x)
|
||||||
|
|
||||||
|
force_channels = property(_get_force_channels, _set_force_channels)
|
||||||
|
|
||||||
|
_get_max_bandwidth = lambda self: encoder.ctl(self._state, ctl.get_max_bandwidth)
|
||||||
|
|
||||||
|
_set_max_bandwidth = lambda self, x: encoder.ctl(self._state, ctl.set_max_bandwidth, x)
|
||||||
|
|
||||||
|
max_bandwidth = property(_get_max_bandwidth, _set_max_bandwidth)
|
||||||
|
|
||||||
|
_set_bandwidth = lambda self, x: encoder.ctl(self._state, ctl.set_bandwidth, x)
|
||||||
|
|
||||||
|
bandwidth = property(None, _set_bandwidth)
|
||||||
|
|
||||||
|
_get_signal = lambda self: encoder.ctl(self._state, ctl.get_signal)
|
||||||
|
|
||||||
|
_set_signal = lambda self, x: encoder.ctl(self._state, ctl.set_signal, x)
|
||||||
|
|
||||||
|
signal = property(_get_signal, _set_signal)
|
||||||
|
|
||||||
|
_get_application = lambda self: encoder.ctl(self._state, ctl.get_application)
|
||||||
|
|
||||||
|
_set_application = lambda self, x: encoder.ctl(self._state, ctl.set_application, x)
|
||||||
|
|
||||||
|
application = property(_get_application, _set_application)
|
||||||
|
|
||||||
|
_get_sample_rate = lambda self: encoder.ctl(self._state, ctl.get_sample_rate)
|
||||||
|
|
||||||
|
sample_rate = property(_get_sample_rate)
|
||||||
|
|
||||||
|
_get_lookahead = lambda self: encoder.ctl(self._state, ctl.get_lookahead)
|
||||||
|
|
||||||
|
lookahead = property(_get_lookahead)
|
||||||
|
|
||||||
|
_get_inband_fec = lambda self: encoder.ctl(self._state, ctl.get_inband_fec)
|
||||||
|
|
||||||
|
_set_inband_fec = lambda self, x: encoder.ctl(self._state, ctl.set_inband_fec)
|
||||||
|
|
||||||
|
inband_fec = property(_get_inband_fec, _set_inband_fec)
|
||||||
|
|
||||||
|
_get_packet_loss_perc = lambda self: encoder.ctl(self._state, ctl.get_packet_loss_perc)
|
||||||
|
|
||||||
|
_set_packet_loss_perc = lambda self, x: encoder.ctl(self._state, ctl.set_packet_loss_perc, x)
|
||||||
|
|
||||||
|
packet_loss_perc = property(_get_packet_loss_perc, _set_packet_loss_perc)
|
||||||
|
|
||||||
|
_get_dtx = lambda self: encoder.ctl(self._state, ctl.get_dtx)
|
||||||
|
|
||||||
|
_set_dtx = lambda self, x: encoder.ctl(self._state, ctl.get_dtx, x)
|
10
opus/exceptions.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from opus.api.info import strerror
|
||||||
|
|
||||||
|
|
||||||
|
class OpusError(Exception):
|
||||||
|
|
||||||
|
def __init__(self, code):
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return strerror(self.code)
|
1267
www/controls.js
vendored
Normal file
BIN
www/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
www/favicon.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
www/img/config.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
www/img/critsgreen.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
www/img/critsgrey.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
www/img/critsred.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
www/img/critsyellow.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
www/img/logout.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
www/img/panfft.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
www/img/poweroff.png
Normal file
After Width: | Height: | Size: 207 KiB |
BIN
www/img/poweron.png
Normal file
After Width: | Height: | Size: 224 KiB |
BIN
www/img/smeter.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
www/img/spinner.gif
Normal file
After Width: | Height: | Size: 55 KiB |
200
www/index.html
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Universal Hamradio Remote by F4HTB</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
</head>
|
||||||
|
<body onload="bodyload();">
|
||||||
|
|
||||||
|
<a href="#fermer" id="ombre-body"></a>
|
||||||
|
<div id="pop-upspinner">
|
||||||
|
<img alt="" src="img/spinner.gif">
|
||||||
|
<p id="socketsate"><center>Wait For connection....</center></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-princ">
|
||||||
|
<img onclick="powertogle();" id="button_power" alt="Click to connect or disconnect interface" src="img/poweroff.png">
|
||||||
|
|
||||||
|
<form id="RX-GAIN_control">
|
||||||
|
AF GAIN:<input oninput="AudioRX_SetGAIN();setCookie('C_af', this.value, 180);" onchange="AudioRX_SetGAIN();setCookie('C_af', this.value, 180);" value="250" step="5" type="range" id="C_af" name="volume" min="0" max="1000">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="Rxmeters">
|
||||||
|
<div id="Rxinstant">
|
||||||
|
<div class="label">RX volume:</div>
|
||||||
|
<meter low="10" high="70" max="100" value="0"></meter>
|
||||||
|
<div class="value"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="TX-GAIN_control">
|
||||||
|
MIC GAIN:<input oninput="AudioTX_SetGAIN(this.value/100);setCookie('C_mg', this.value, 180);" onchange="AudioTX_SetGAIN(this.value/100);setCookie('C_mg', this.value, 180);" value="50" step="5" type="range" id="C_mg" name="volume" min="0" max="200">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="Txmeters">
|
||||||
|
<div id="Txinstant">
|
||||||
|
<div class="label">TX volume:</div>
|
||||||
|
<meter low="10" high="50" max="100" value="0"></meter>
|
||||||
|
<div class="value"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-freq">
|
||||||
|
<ul id="freq_but">
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="cmhz" v=100000000>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="dmhz" v=10000000>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="umhz" v=1000000>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();"> </li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="ckhz" v=100000>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="dkhz" v=10000>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="ukhz" v=1000>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();"> </li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="chz" v=100>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="dhz" v=10>▲</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="uhz" v=1>▲</li>
|
||||||
|
</ul>
|
||||||
|
<ul ondblclick="changeinputfreqstyle(event);document.getElementById('freq_disp_input_text').focus();document.getElementById('freq_disp_input_text').select();" id="freq_disp" onwheel="freq_digit_scroll()">
|
||||||
|
<input onkeypress="changeinputfreqstyle(event);" onkeydown="validateNumber(event);"/ id="freq_disp_input_text" type="text" minlength="4" maxlength="8" size="10">
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="cmhz" v=100000000>0</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="dmhz" v=10000000>0</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="umhz" v=1000000>0</li>
|
||||||
|
<li class="freq_digit">.</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="ckhz" v=100000>0</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="dkhz" v=10000>0</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="ukhz" v=1000>0</li>
|
||||||
|
<li class="freq_digit">.</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="chz" v=100>0</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="dhz" v=10>0</li>
|
||||||
|
<li class="freq_digit" onmouseenter="select_digit();" onmouseleave="clear_select_digit();" id="uhz" v=1>0</li>
|
||||||
|
</ul>
|
||||||
|
<ul id="freq_but">
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="cmhz" v=-100000000>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="dmhz" v=-10000000>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="umhz" v=-1000000>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();"> </li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="ckhz" v=-100000>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="dkhz" v=-10000>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="ukhz" v=-1000>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" > </li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="chz" v=-100>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="dhz" v=-10>▼</li>
|
||||||
|
<li onmousedown="button_pressed();rotatefreq();" onmouseup="button_unpressed();" class="button_unpressed" digit="uhz" v=-1>▼</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-bandshortcut">
|
||||||
|
<ul id="band_but">
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="001845500">160m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="003692500">80m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="007092500">40m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="010140000">30m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="014127500">20m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="018132500">17m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="021287500">15m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="027085000">11m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="024952500">12m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="028362500">10m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="050202500">6m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="070202500">4m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="144300000">2m</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="432200000">70cm</li>
|
||||||
|
<li onmousedown="button_pressed();recall_hambands();" onmouseup="button_unpressed();" class="button_unpressed" v="010000000">WWW</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-filtershortcut">
|
||||||
|
<ul id="band_but">
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_pressed" lichecked="" fq="0" fg="0" ft="highshelf" frq="22000">None</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="0" fg="-20" ft="highshelf" frq="4400">LP 4.4k</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="0" fg="-20" ft="highshelf" frq="3300">LP 3.3k</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="0" fg="-20" ft="highshelf" frq="2700">LP 2.7k</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="0" fg="-20" ft="highshelf" frq="2100">LP 2.1k</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="0" fg="-20" ft="highshelf" frq="1000">LP 1.0k</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="50" fg="-100" ft="bandpass" frq="300">BP 300Hz</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="50" fg="-100" ft="bandpass" frq="500">BP 500Hz</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="50" fg="-100" ft="bandpass" frq="800">BP 800Hz</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" fq="50" fg="-100" ft="bandpass" frq="1000">BP 1kHz</li>
|
||||||
|
<li onclick="if(poweron)togle_li();setaudiofilter();" class="button_unpressed" id="custom_filter_click" fq="0" fg="0" ft="highshelf" frq="22000">BP click</li>
|
||||||
|
<li onclick="if(poweron)togle_li();document.getElementById('div-customfilter').style.display = 'block';" class="button_unpressed" fq="0" fg="0" ft="highshelf" frq="22000">Custom</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-customfilter">
|
||||||
|
filter_type:<select id="customfilter_T">
|
||||||
|
<option >--Please choose a filter type--</option>
|
||||||
|
<option value="lowpass">lowpass</option>
|
||||||
|
<option value="highpass">highpass</option>
|
||||||
|
<option value="bandpass">bandpass</option>
|
||||||
|
<option value="lowshelf">lowshelf</option>
|
||||||
|
<option value="highshelf">highshelf</option>
|
||||||
|
<option value="peaking">peaking</option>
|
||||||
|
<option value="notch">notch</option>
|
||||||
|
<option value="allpass">allpass</option>
|
||||||
|
</select><br>
|
||||||
|
Freq:<input type="text" id="customfilter_F" value="click to FFT to set">hz<br>
|
||||||
|
Q Factor:<input type ="text" id="customfilter_Q" value="50"><br>
|
||||||
|
Gain:<input type ="text" id="customfilter_G" value="50"><br>
|
||||||
|
<input value="ok" type ="button" onclick="document.getElementById('div-customfilter').style.display = 'none';setcustomaudiofilter();">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<form id="SQUELCH_control">
|
||||||
|
SQL:<input oninput="if(poweron)drawRXSmeter();setCookie('SQUELCH', this.value, 180);" onchange="if(poweron)drawRXSmeter();setCookie('SQUELCH', this.value, 180);" value="0" step="5" type="range" id="SQUELCH" name="SQUELCH" min="0" max="100">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form id="personalfrequency">
|
||||||
|
<label>Personal Freqs</label>
|
||||||
|
<select id="selectpersonalfrequency">
|
||||||
|
</select>
|
||||||
|
<label onclick="recall_freqfromcokkies();">recall</label>
|
||||||
|
<label onclick="delete_freqfromcokkies();"> delete</label>
|
||||||
|
<label onclick="save_freqtocokkies();"> save</label>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<canvas class="visualizer" id="canBFSPC" width="1024" ></canvas>
|
||||||
|
<canvas class="visualizer" id="canBFFFT" width="2048" ></canvas>
|
||||||
|
<input class="slider-wrapper slider-wrapper_floor" value="0" step="5" type="range" id="canBFFFT_scale_floor" min="-100" max="100">
|
||||||
|
<input class="slider-wrapper slider-wrapper_multdb" value="0" step="5" type="range" id="canBFFFT_scale_multdb" min="-100" max="100">
|
||||||
|
<input class="slider-wrapper slider-wrapper_start" value="0" step="5" type="range" id="canBFFFT_scale_start" min="-200" max="200">
|
||||||
|
<input class="slider-wrapper slider-wrapper_multhz" value="0" step="5" type="range" id="canBFFFT_scale_multhz" min="-200" max="200">
|
||||||
|
<label id="canvasBFFFT_coord"></label>
|
||||||
|
|
||||||
|
<label id="callsign"></label>
|
||||||
|
|
||||||
|
<div id="div-conf"><a href="/CONFIG" target="_UHRRconfig"><img src="img/config.png"></a></div>
|
||||||
|
<div id="div-panfft"><a onclick="panfft = window.open( this.href, 'UHRRpanfft', 'width=1000, height=1000, menubar=no, toolbar=no, location=no, resizable=yes, scrollbars=no, status=no, dependent=yes'); return false;" href="/panfft.html" target="_UHRRpanfft"><img src="img/panfft.png"></a></div>
|
||||||
|
|
||||||
|
<canvas id="canRXsmeter" width=250 height=50 ></canvas>
|
||||||
|
|
||||||
|
<div id="div-smeterdigitRX">S9+40dB</div>
|
||||||
|
<div id="div-mode_menu">
|
||||||
|
<ul>
|
||||||
|
<li onclick="togle_li();sendTRXmode();" class="button_mode button_pressed" lichecked="">USB</li>
|
||||||
|
<li onclick="togle_li();sendTRXmode();" class="button_mode button_unpressed">LSB</li>
|
||||||
|
<li onclick="togle_li();sendTRXmode();" class="button_mode button_unpressed">CW</li>
|
||||||
|
<li onclick="togle_li();sendTRXmode();" class="button_mode button_unpressed">AM</li>
|
||||||
|
<li onclick="togle_li();sendTRXmode();" class="button_mode button_unpressed">FM</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-TX">
|
||||||
|
<span onmousedown="TXtogle('True');" onmouseup="TXtogle('False');" class="button_unpressed" id="TX-record">TX</span>
|
||||||
|
<span onclick="TXtogle();" class="button_unpressed" id="TX-record-lock">TX Lock</span>
|
||||||
|
<form id="TX-record_record_opus">
|
||||||
|
<input type="checkbox" id="encode" class=none alt="Encode TX with opus codec">Encode TX</input><br>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-scoketscontrols">
|
||||||
|
<p id="indwsAudioTX"><img src="img/critsred.png">wsTX</p>
|
||||||
|
<p id="indwsAudioRX"><img src="img/critsred.png">wsRX</p>
|
||||||
|
<p id="indwsControlTRX"><img src="img/critsred.png">wsCtrl</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-latencymeter">latency:∞</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="controls.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
78
www/panadapter/panfft.css
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
body {
|
||||||
|
color: white;
|
||||||
|
font:normal bold 14px tahoma;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
padding:0px;
|
||||||
|
margin:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-sp
|
||||||
|
{
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:45%;
|
||||||
|
padding:0px;
|
||||||
|
margin:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cansp{
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
float:center;
|
||||||
|
background-color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-wf
|
||||||
|
{
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:45%;
|
||||||
|
padding:0px;
|
||||||
|
margin:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canwf{
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
background:#00007f;
|
||||||
|
background-color:bleu;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-ctrl
|
||||||
|
{
|
||||||
|
display: inline-flex;
|
||||||
|
background-color:#171717;
|
||||||
|
position:fixed;
|
||||||
|
width:100%;
|
||||||
|
height:10%;
|
||||||
|
padding:0px;
|
||||||
|
margin:0px;
|
||||||
|
left: 0%;
|
||||||
|
align-items: center; /* Vertical */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ctrl_visual{
|
||||||
|
margin: 0px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-scoketscontrols
|
||||||
|
{
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-scoketscontrols > img
|
||||||
|
{
|
||||||
|
margin-right:5px;
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
32
www/panadapter/panfft.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>FFT Universal Hamradio Remote by F4HTB</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="panfft.css">
|
||||||
|
</head>
|
||||||
|
<body onload="bodyload();">
|
||||||
|
<div id="div-princ">
|
||||||
|
|
||||||
|
<div id="div-sp">
|
||||||
|
<canvas id="cansp" class="cansp" width="4096" height="256"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-wf">
|
||||||
|
<canvas id="canwf" class="canwf" width="4096" height="256"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-ctrl">
|
||||||
|
Center Frequency:<div class="ctrl_visual" id="div-CenterFrequency"></div>|
|
||||||
|
SampleRate:<div class="ctrl_visual" id="div-SampleRate"></div>|
|
||||||
|
FFT resolution:<div class="ctrl_visual" id="div-FFtResolution"></div>|
|
||||||
|
Mouse Frequency:<div class="ctrl_visual" id="div-mouseFrequency"></div>|
|
||||||
|
Windows Zoom:<div class="ctrl_visual" id="div-WindowsZoom">100%</div>|
|
||||||
|
Spectrogram dynamic: <input oninput="set_FFT_Viso_Dynamic(this.value);" onchange="set_FFT_Viso_Dynamic(this.value);" value="60" step="5" type="range" name="FFT_Viso_Dynamic" min="20" max="120">
|
||||||
|
Spectrogram min: <input oninput="set_FFT_Viso_min(this.value);" onchange="set_FFT_Viso_min(this.value);" value="-180" step="5" type="range" name="FFT_Viso_min" min="-200" max="30">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="div-scoketscontrols"><img src="img/critsred.png">wsFFT</div>
|
||||||
|
</div>
|
||||||
|
<script src="panfft.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
245
www/panadapter/panfft.js
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
var canvas_width_SP=document.getElementById("cansp").width;
|
||||||
|
var canvas_height_SP=document.getElementById("cansp").height;
|
||||||
|
var canvas_width_WF=document.getElementById("canwf").width;
|
||||||
|
var canvas_height_WF=document.getElementById("canwf").height;
|
||||||
|
var wshFFT = "";
|
||||||
|
var zoom_FFT=100;
|
||||||
|
var samplerate = "960000";
|
||||||
|
var FFTSIZE = 4096;
|
||||||
|
var localcenterfrequency=0;
|
||||||
|
var freqmouse=0;
|
||||||
|
|
||||||
|
var FFT_Viso_Dynamic=50
|
||||||
|
var FFT_Viso_min=-180
|
||||||
|
|
||||||
|
|
||||||
|
const visual_CenterFrequency = document.getElementById("div-CenterFrequency");
|
||||||
|
const visual_SampleRate = document.getElementById("div-SampleRate");
|
||||||
|
const visual_FFtResolution = document.getElementById("div-FFtResolution");
|
||||||
|
const visual_freq = document.getElementById("div-mouseFrequency");
|
||||||
|
const visual_WindowsZoom = document.getElementById("div-WindowsZoom");
|
||||||
|
|
||||||
|
const canvasSP = document.getElementById("cansp");
|
||||||
|
const canvasWF = document.getElementById("canwf")
|
||||||
|
|
||||||
|
function bodyload(){
|
||||||
|
initFFT();
|
||||||
|
startFFT();
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_FFT_Viso_Dynamic(v){FFT_Viso_Dynamic=v;}
|
||||||
|
function set_FFT_Viso_min(v){FFT_Viso_min=v;}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function startFFT(){
|
||||||
|
document.getElementById("div-scoketscontrols").innerHTML='<img src="img/critsgrey.png">wsFFT';
|
||||||
|
wshFFT = new WebSocket( 'wss://' + window.location.href.split( '/' )[2] + '/WSpanFFT' );
|
||||||
|
wshFFT.onopen = appendwshFFTOpen;
|
||||||
|
wshFFT.onmessage = init_showFFT;
|
||||||
|
wshFFT.onerror = appendwshFFTError;
|
||||||
|
wshFFT.onclose = appendwshFFTclose;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendwshFFTclose(){
|
||||||
|
document.getElementById("div-scoketscontrols").innerHTML='<img src="img/critsred.png">wsFFT';
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendwshFFTOpen(){
|
||||||
|
document.getElementById("div-scoketscontrols").innerHTML='<img src="img/critsgreen.png">wsFFT';
|
||||||
|
wshFFT.send("init");
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendwshFFTError(err){
|
||||||
|
document.getElementById("div-scoketscontrols").innerHTML='<img src="img/critsred.png">wsFFT';
|
||||||
|
wshFFT.close();
|
||||||
|
startFFT();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopFFT(){
|
||||||
|
wshFFT.close();
|
||||||
|
initFFT();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initFFT(){
|
||||||
|
initCanvas("cansp");
|
||||||
|
initCanvas("canwf");
|
||||||
|
}
|
||||||
|
|
||||||
|
var globmsg = "";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function init_showFFT( msg ){
|
||||||
|
datas = msg.data.split(':');
|
||||||
|
if(datas[0] == "fftsr"){
|
||||||
|
samplerate=datas[1];
|
||||||
|
}
|
||||||
|
else if(datas[0] == "fftsz"){
|
||||||
|
FFTSIZE=parseInt(datas[1]);
|
||||||
|
canvasSP.width=parseInt(FFTSIZE);
|
||||||
|
canvasWF.width=parseInt(FFTSIZE);
|
||||||
|
var canvas_width_SP=document.getElementById("cansp").width;
|
||||||
|
var canvas_height_SP=document.getElementById("cansp").height;
|
||||||
|
var canvas_width_WF=document.getElementById("canwf").width;
|
||||||
|
var canvas_height_WF=document.getElementById("canwf").height;
|
||||||
|
}
|
||||||
|
else if(datas[0] == "fftst"){
|
||||||
|
visual_SampleRate.innerHTML = samplerate+"sps";
|
||||||
|
visual_FFtResolution.innerHTML=samplerate/FFTSIZE+"hz/px";
|
||||||
|
wshFFT.send("ready");
|
||||||
|
window.opener.ControlTRX_getFreq();
|
||||||
|
wshFFT.binaryType = 'arraybuffer';
|
||||||
|
wshFFT.onmessage = showFFT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFFT( msg ){
|
||||||
|
var buffer = new Uint8Array(msg.data);
|
||||||
|
let FFTdata = buffer.subarray(0, FFTSIZE);
|
||||||
|
FFTdata_scale_min = ((buffer[FFTSIZE] << 8) + (buffer[FFTSIZE+1]))-65280;
|
||||||
|
FFTdata_scale_max= ((buffer[FFTSIZE+2] << 8) + (buffer[FFTSIZE+3]))-65280;
|
||||||
|
|
||||||
|
min_Factor=FFTdata_scale_min-FFT_Viso_min;
|
||||||
|
max_Factor=(FFTdata_scale_max-FFTdata_scale_min)/FFT_Viso_Dynamic;
|
||||||
|
|
||||||
|
SetImageDataWF(FFTdata,min_Factor,max_Factor);
|
||||||
|
SetImageDataSP(FFTdata,min_Factor,max_Factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const ctxSP = canvasSP.getContext("2d");// imgObjSP = ctxSP.createImageData(canvasSP.width, canvas_height);
|
||||||
|
const midle_SP = canvas_width_SP*2;
|
||||||
|
|
||||||
|
function SetImageDataSP(datas,scale_min,scale_max) {
|
||||||
|
imgObjSP = new ImageData(canvasSP.width, canvas_height_SP);
|
||||||
|
let i = 0;
|
||||||
|
for(let Line = 0; Line < canvas_height_SP; Line++){
|
||||||
|
i = 4*(Line * canvas_width_SP);
|
||||||
|
y = canvas_height_SP - Line;
|
||||||
|
for (let px = 0; px < canvas_width_SP; px++) {
|
||||||
|
let dat = (datas[px]*scale_max + scale_min);
|
||||||
|
if(y <= dat){
|
||||||
|
//imgObjSP.data[i] = 0; // red
|
||||||
|
imgObjSP.data[++i] = 215; // green
|
||||||
|
imgObjSP.data[++i] = 233; // blue
|
||||||
|
imgObjSP.data[++i] = 255; // alpha
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
else{i += 4 ;}
|
||||||
|
}
|
||||||
|
i -= midle_SP ;
|
||||||
|
//imgObjSP.data[i] = 0; // red
|
||||||
|
imgObjSP.data[++i] = 255; // green
|
||||||
|
imgObjSP.data[++i] = 0; // blue
|
||||||
|
imgObjSP.data[++i] = 255; // alpha
|
||||||
|
}
|
||||||
|
ctxSP.putImageData(imgObjSP, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctxWF = canvasWF.getContext("2d"),imgObjWF = ctxWF.createImageData(canvasWF.width, 1);
|
||||||
|
//const colMap = [[0,0,0,255],[40,0,0,255],[56,0,4,255],[61,0,9,255],[64,0,12,255],[66,0,14,255],[69,0,17,255],[73,0,20,255],[74,0,22,255],[78,0,25,255],[79,0,27,255],[83,0,30,255],[85,0,31,255],[86,0,33,255],[90,0,36,255],[91,0,38,255],[93,0,39,255],[95,0,41,255],[96,0,43,255],[100,0,46,255],[102,0,47,255],[103,0,49,255],[105,0,51,255],[107,0,52,255],[108,0,54,255],[110,0,55,255],[112,0,57,255],[112,0,57,255],[113,0,58,255],[115,0,60,255],[117,0,62,255],[119,0,63,255],[120,0,65,255],[122,0,66,255],[124,0,68,255],[125,0,70,255],[127,0,71,255],[129,0,73,255],[129,0,73,255],[130,0,74,255],[132,0,76,255],[134,0,78,255],[136,0,79,255],[137,0,81,255],[139,0,82,255],[141,0,84,255],[142,0,86,255],[144,0,87,255],[146,0,89,255],[147,0,90,255],[149,0,92,255],[151,0,94,255],[151,0,94,255],[153,0,95,255],[154,0,97,255],[156,0,98,255],[158,0,100,255],[159,0,102,255],[161,0,103,255],[163,0,105,255],[164,0,106,255],[166,0,108,255],[168,0,109,255],[170,0,111,255],[171,0,113,255],[173,0,114,255],[175,0,116,255],[176,0,117,255],[178,0,119,255],[180,0,121,255],[180,0,121,255],[181,0,122,255],[183,0,124,255],[185,0,125,255],[187,0,127,255],[188,0,129,255],[190,0,130,255],[192,0,132,255],[193,0,133,255],[195,0,135,255],[197,0,137,255],[198,0,138,255],[200,0,140,255],[202,0,141,255],[204,0,143,255],[204,0,143,255],[205,0,145,255],[207,0,146,255],[209,0,148,255],[210,0,149,255],[212,0,151,255],[214,0,153,255],[215,0,154,255],[217,0,156,255],[219,0,157,255],[221,0,159,255],[222,0,160,255],[222,0,160,255],[224,0,162,255],[226,0,164,255],[227,0,165,255],[229,0,167,255],[231,0,168,255],[232,0,170,255],[234,0,172,255],[236,0,173,255],[238,0,175,255],[238,0,175,255],[239,0,176,255],[241,0,178,255],[243,0,180,255],[244,0,181,255],[246,0,183,255],[248,2,184,255],[249,4,186,255],[249,4,186,255],[249,4,186,255],[251,6,188,255],[251,6,188,255],[253,9,189,255],[253,9,189,255],[255,11,191,255],[255,11,191,255],[255,13,192,255],[255,13,192,255],[255,13,192,255],[255,16,194,255],[255,18,196,255],[255,20,197,255],[255,20,197,255],[255,23,199,255],[255,25,200,255],[255,27,202,255],[255,30,204,255],[255,32,205,255],[255,34,207,255],[255,37,208,255],[255,37,208,255],[255,39,210,255],[255,41,211,255],[255,44,213,255],[255,46,215,255],[255,48,216,255],[255,51,218,255],[255,53,219,255],[255,53,219,255],[255,55,221,255],[255,57,223,255],[255,60,224,255],[255,62,226,255],[255,64,227,255],[255,67,229,255],[255,67,229,255],[255,69,231,255],[255,71,232,255],[255,74,234,255],[255,76,235,255],[255,78,237,255],[255,81,239,255],[255,81,239,255],[255,83,240,255],[255,85,242,255],[255,88,243,255],[255,90,245,255],[255,92,247,255],[255,95,248,255],[255,95,248,255],[255,97,250,255],[255,99,251,255],[255,102,253,255],[255,104,255,255],[255,106,255,255],[255,106,255,255],[255,108,255,255],[255,111,255,255],[255,113,255,255],[255,115,255,255],[255,115,255,255],[255,118,255,255],[255,120,255,255],[255,122,255,255],[255,122,255,255],[255,125,255,255],[255,127,255,255],[255,129,255,255],[255,129,255,255],[255,132,255,255],[255,134,255,255],[255,136,255,255],[255,136,255,255],[255,139,255,255],[255,141,255,255],[255,143,255,255],[255,143,255,255],[255,146,255,255],[255,148,255,255],[255,150,255,255],[255,150,255,255],[255,153,255,255],[255,155,255,255],[255,155,255,255],[255,157,255,255],[255,159,255,255],[255,159,255,255],[255,162,255,255],[255,164,255,255],[255,164,255,255],[255,166,255,255],[255,169,255,255],[255,171,255,255],[255,171,255,255],[255,173,255,255],[255,176,255,255],[255,176,255,255],[255,178,255,255],[255,180,255,255],[255,180,255,255],[255,183,255,255],[255,185,255,255],[255,185,255,255],[255,187,255,255],[255,190,255,255],[255,190,255,255],[255,192,255,255],[255,194,255,255],[255,197,255,255],[255,197,255,255],[255,199,255,255],[255,201,255,255],[255,204,255,255],[255,204,255,255],[255,206,255,255],[255,208,255,255],[255,210,255,255],[255,210,255,255],[255,213,255,255],[255,215,255,255],[255,217,255,255],[255,217,255,255],[255,220,255,255],[255,222,255,255],[255,224,255,255],[255,227,255,255],[255,229,255,255],[255,229,255,255],[255,231,255,255],[255,234,255,255],[255,236,255,255],[255,238,255,255],[255,241,255,255],[255,243,255,255],[255,243,255,255],[255,245,255,255],[255,248,255,255],[255,250,255,255],[255,255,255,255]];
|
||||||
|
const colMap = [[0,0,127,255],[0,0,131,255],[0,0,135,255],[0,0,139,255],[0,0,143,255],[0,0,147,255],[0,0,151,255],[0,0,155,255],[0,0,159,255],[0,0,163,255],[0,0,167,255],[0,0,171,255],[0,0,175,255],[0,0,179,255],[0,0,183,255],[0,0,187,255],[0,0,191,255],[0,0,195,255],[0,0,199,255],[0,0,203,255],[0,0,207,255],[0,0,211,255],[0,0,215,255],[0,0,219,255],[0,0,223,255],[0,0,227,255],[0,0,231,255],[0,0,235,255],[0,0,239,255],[0,0,243,255],[0,0,247,255],[0,0,251,255],[0,0,255,255],[0,4,255,255],[0,8,255,255],[0,12,255,255],[0,16,255,255],[0,20,255,255],[0,24,255,255],[0,28,255,255],[0,32,255,255],[0,36,255,255],[0,40,255,255],[0,44,255,255],[0,48,255,255],[0,52,255,255],[0,56,255,255],[0,60,255,255],[0,64,255,255],[0,68,255,255],[0,72,255,255],[0,76,255,255],[0,80,255,255],[0,84,255,255],[0,88,255,255],[0,92,255,255],[0,96,255,255],[0,100,255,255],[0,104,255,255],[0,108,255,255],[0,112,255,255],[0,116,255,255],[0,120,255,255],[0,124,255,255],[0,128,255,255],[0,132,255,255],[0,136,255,255],[0,140,255,255],[0,144,255,255],[0,148,255,255],[0,152,255,255],[0,156,255,255],[0,160,255,255],[0,164,255,255],[0,168,255,255],[0,172,255,255],[0,176,255,255],[0,180,255,255],[0,184,255,255],[0,188,255,255],[0,192,255,255],[0,196,255,255],[0,200,255,255],[0,204,255,255],[0,208,255,255],[0,212,255,255],[0,216,255,255],[0,220,255,255],[0,224,255,255],[0,228,255,255],[0,232,255,255],[0,236,255,255],[0,240,255,255],[0,244,255,255],[0,248,255,255],[0,252,255,255],[1,255,253,255],[5,255,249,255],[9,255,245,255],[13,255,241,255],[17,255,237,255],[21,255,233,255],[25,255,229,255],[29,255,225,255],[33,255,221,255],[37,255,217,255],[41,255,213,255],[45,255,209,255],[49,255,205,255],[53,255,201,255],[57,255,197,255],[61,255,193,255],[65,255,189,255],[69,255,185,255],[73,255,181,255],[77,255,177,255],[81,255,173,255],[85,255,169,255],[89,255,165,255],[93,255,161,255],[97,255,157,255],[101,255,153,255],[105,255,149,255],[109,255,145,255],[113,255,141,255],[117,255,137,255],[121,255,133,255],[125,255,129,255],[129,255,125,255],[133,255,121,255],[137,255,117,255],[141,255,113,255],[145,255,109,255],[149,255,105,255],[153,255,101,255],[157,255,97,255],[161,255,93,255],[165,255,89,255],[169,255,85,255],[173,255,81,255],[177,255,77,255],[181,255,73,255],[185,255,69,255],[189,255,65,255],[193,255,61,255],[197,255,57,255],[201,255,53,255],[205,255,49,255],[209,255,45,255],[213,255,41,255],[217,255,37,255],[221,255,33,255],[225,255,29,255],[229,255,25,255],[233,255,21,255],[237,255,17,255],[241,255,13,255],[245,255,9,255],[249,255,5,255],[253,255,1,255],[255,252,0,255],[255,248,0,255],[255,244,0,255],[255,240,0,255],[255,236,0,255],[255,232,0,255],[255,228,0,255],[255,224,0,255],[255,220,0,255],[255,216,0,255],[255,212,0,255],[255,208,0,255],[255,204,0,255],[255,200,0,255],[255,196,0,255],[255,192,0,255],[255,188,0,255],[255,184,0,255],[255,180,0,255],[255,176,0,255],[255,172,0,255],[255,168,0,255],[255,164,0,255],[255,160,0,255],[255,156,0,255],[255,152,0,255],[255,148,0,255],[255,144,0,255],[255,140,0,255],[255,136,0,255],[255,132,0,255],[255,128,0,255],[255,124,0,255],[255,120,0,255],[255,116,0,255],[255,112,0,255],[255,108,0,255],[255,104,0,255],[255,100,0,255],[255,96,0,255],[255,92,0,255],[255,88,0,255],[255,84,0,255],[255,80,0,255],[255,76,0,255],[255,72,0,255],[255,68,0,255],[255,64,0,255],[255,60,0,255],[255,56,0,255],[255,52,0,255],[255,48,0,255],[255,44,0,255],[255,40,0,255],[255,36,0,255],[255,32,0,255],[255,28,0,255],[255,24,0,255],[255,20,0,255],[255,16,0,255],[255,12,0,255],[255,8,0,255],[255,4,0,255],[255,0,0,255],[251,0,0,255],[247,0,0,255],[243,0,0,255],[239,0,0,255],[235,0,0,255],[231,0,0,255],[227,0,0,255],[223,0,0,255],[219,0,0,255],[215,0,0,255],[211,0,0,255],[207,0,0,255],[203,0,0,255],[199,0,0,255],[195,0,0,255],[191,0,0,255],[187,0,0,255],[183,0,0,255],[179,0,0,255],[175,0,0,255],[171,0,0,255],[167,0,0,255],[163,0,0,255],[159,0,0,255],[155,0,0,255],[151,0,0,255],[147,0,0,255],[143,0,0,255],[139,0,0,255],[135,0,0,255],[131,0,0,255],[127,0,0,255]];
|
||||||
|
const midle_WF = canvas_width_WF*2;
|
||||||
|
|
||||||
|
function SetImageDataWF(datas,scale_min,scale_max) {
|
||||||
|
var canvasBuffer = document.createElement("canvas");
|
||||||
|
canvasBuffer.width = canvas_width_WF;
|
||||||
|
canvasBuffer.height = canvas_height_WF;
|
||||||
|
var ctxBuffer = canvasBuffer.getContext("2d");
|
||||||
|
|
||||||
|
ctxBuffer.clearRect(0,0,canvas_width_WF,canvas_height_WF); //clear buffer
|
||||||
|
ctxBuffer.drawImage(canvasWF,0,0); //store display data in buffer
|
||||||
|
ctxWF.clearRect(0,0,canvas_width_WF,canvas_height_WF); //clear display
|
||||||
|
ctxWF.drawImage(canvasBuffer,0,1); //copy buffer to display
|
||||||
|
|
||||||
|
|
||||||
|
var px=0;
|
||||||
|
var i=0;
|
||||||
|
for (px = 0; px < canvas_width_WF; px++) {
|
||||||
|
i = 4*px;
|
||||||
|
|
||||||
|
let dat = Math.floor(datas[px]*scale_max + scale_min);
|
||||||
|
|
||||||
|
if(dat<0){dat=0;}
|
||||||
|
if(dat>255){dat=255;}
|
||||||
|
|
||||||
|
// let rgba = colMap[datas[px]]; // lookup color rgba values
|
||||||
|
imgObjWF.data[i] = colMap[dat][0]; // red
|
||||||
|
imgObjWF.data[i+1] = colMap[dat][1]; // green
|
||||||
|
imgObjWF.data[i+2] = colMap[dat][2]; // blue
|
||||||
|
imgObjWF.data[i+3] = colMap[dat][3]; // alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
imgObjWF.data[midle_WF] = 0;
|
||||||
|
imgObjWF.data[midle_WF+1] = 255;
|
||||||
|
imgObjWF.data[midle_WF+2] = 0;
|
||||||
|
imgObjWF.data[midle_WF+3] = 255;
|
||||||
|
|
||||||
|
ctxWF.putImageData(imgObjWF, 0, 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function initCanvas(cvsIDwf) {
|
||||||
|
var canvas = document.getElementById(cvsIDwf);
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
canvas.addEventListener('wheel', (event) => {
|
||||||
|
set_FFT_zoom();
|
||||||
|
event.stopImmediatePropagation(); // WORKED!!
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', showOnmouseInfo, false);
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseout', function(event) {visual_freq.innerHTML="∞hz";}, false);
|
||||||
|
|
||||||
|
canvas.addEventListener('click', function() {window.opener.sendTRXfreq(freqmouse);}, false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showOnmouseInfo(event) {
|
||||||
|
var rect = this.getBoundingClientRect()
|
||||||
|
var scaleX = this.width / rect.width; // relationship bitmap vs. element for X
|
||||||
|
var hzperpixel=samplerate/this.width;
|
||||||
|
// console.log(this.width);
|
||||||
|
// console.log(rect.width);
|
||||||
|
|
||||||
|
hz=((window.scrollX+event.clientX)*scaleX*hzperpixel)-(samplerate/2);
|
||||||
|
freqmouse=window.opener.TRXfrequency+hz;
|
||||||
|
//var scale_hz = Math.exp(parseInt(document.getElementById("canBFFFT_scale_multhz").value)/100);
|
||||||
|
// var start = (parseInt(document.getElementById("canBFFFT_scale_start").value)*Audio_analyser.frequencyBinCount/100)*scale_hz;
|
||||||
|
|
||||||
|
// scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
|
||||||
|
// var scale_mult = Math.exp(parseInt(document.getElementById("canBFFFT_scale_multdb").value)/100);
|
||||||
|
// var scale_floor = parseInt(document.getElementById("canBFFFT_scale_floor").value);
|
||||||
|
|
||||||
|
// console.log(parseInt(((((evt.clientX - rect.left)/(scale_hz*scale_hz) * scaleX ) - (start/scale_hz))* (AudioRX_sampleRate/2))/canvasBFFFT.width) + 'hz ,-' + parseInt(((evt.clientY - rect.top) * scaleY)/(scale_mult) + (scale_floor))+'dB');
|
||||||
|
//console.log(scaleX);
|
||||||
|
//console.log(window.opener.TRXfrequency);
|
||||||
|
//console.log(event.clientX);
|
||||||
|
visual_freq.innerHTML=Math.floor(freqmouse)+"hz";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function set_FFT_zoom() {
|
||||||
|
if(event.deltaY>0){
|
||||||
|
if(zoom_FFT >= 150){zoom_FFT/=1.5;}
|
||||||
|
}else{
|
||||||
|
zoom_FFT*=1.5;
|
||||||
|
}
|
||||||
|
visual_WindowsZoom.innerHTML=canvasWF.style.width = canvasSP.style.width = zoom_FFT + "%";
|
||||||
|
window.scroll((window.screen.width*(zoom_FFT/100)/2)-window.screen.width/2, 0);
|
||||||
|
visual_FFtResolution.innerHTML=Math.floor(samplerate/FFTSIZE/(zoom_FFT/100))+"hz/px";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setcenterfrequency (freq){
|
||||||
|
localcenterfrequency=freq;
|
||||||
|
visual_CenterFrequency.innerHTML=localcenterfrequency+"hz";;
|
||||||
|
}
|
639
www/style.css
Normal file
|
@ -0,0 +1,639 @@
|
||||||
|
body {
|
||||||
|
color: white;
|
||||||
|
font:normal bold 14px tahoma;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
/* overflow: hidden; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#ombre-body{
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.7);
|
||||||
|
z-index: 1000;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pop-upspinner{
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
left: calc(50% - 100px);
|
||||||
|
top: 50%;
|
||||||
|
z-index: 2000;
|
||||||
|
cursor: default;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-princ {
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 1800px;
|
||||||
|
height: 860px;
|
||||||
|
background-color:#171717;
|
||||||
|
box-shadow:5px 5px 5px 5px rgba(0, 0, 0, 0.5),0px 0px 0px 0px rgba(255, 255, 255, 0.5) inset;
|
||||||
|
border-radius: 30px 30px 30px 30px ;
|
||||||
|
position:relative
|
||||||
|
}
|
||||||
|
|
||||||
|
#button_power
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left:105px;
|
||||||
|
top:105px;
|
||||||
|
width:150px;
|
||||||
|
height:150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_pressed{
|
||||||
|
box-shadow:0px 2px 2px 0px rgba(0, 0, 0, 0.5) inset,0px 2px 2px 0px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_unpressed{
|
||||||
|
box-shadow:0px 2px 2px 0px rgba(0, 0, 0, 0.5),0px 2px 2px 0px rgba(255, 255, 255, 0.5) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_white{
|
||||||
|
color: #e0e0e0;
|
||||||
|
background: #a5cd4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_green {
|
||||||
|
color: #7FFF00;
|
||||||
|
text-shadow: 0 0 10px #6a6767, 0 0 20px #444, 0 0 30px #515151, 0 0 40px #FFFAFC, 0 0 70px #3AFF11, 0 0 80px #45FF11, 0 0 100px #4F4F4F, 0 0 150px #18FF11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_red {
|
||||||
|
color: #ff3a3a;
|
||||||
|
background: #a5cd4e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_blue {
|
||||||
|
color: #176fff;
|
||||||
|
background: #70c9e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#callsign{
|
||||||
|
position:absolute;
|
||||||
|
left: 1450px;
|
||||||
|
top:20px;
|
||||||
|
height:80px;
|
||||||
|
width:300px;
|
||||||
|
font: normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#callsign > a > img{
|
||||||
|
height:25px;
|
||||||
|
width:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#personalfrequency
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 1450px;
|
||||||
|
top:420px;
|
||||||
|
height:110px;
|
||||||
|
width:300px;
|
||||||
|
font: normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#personalfrequency > select
|
||||||
|
{
|
||||||
|
width:300px;
|
||||||
|
font: normal bold 25px tahoma;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-bandshortcut
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 1450px;
|
||||||
|
top:250px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-bandshortcut > ul
|
||||||
|
{
|
||||||
|
padding:0;
|
||||||
|
margin:0px;
|
||||||
|
width:300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-bandshortcut > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
width:32%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-customfilter
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 425px;
|
||||||
|
top:600px;
|
||||||
|
width: 450px;
|
||||||
|
height:200px;
|
||||||
|
display:none;
|
||||||
|
z-index:100;
|
||||||
|
background-color:#171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-filtershortcut
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 425px;
|
||||||
|
top:600px;
|
||||||
|
width: 450px;
|
||||||
|
height:200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-filtershortcut > ul
|
||||||
|
{
|
||||||
|
padding:0;
|
||||||
|
width: 450px;
|
||||||
|
height:200px;
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-filtershortcut > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
width:32%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#div-freq
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 425px;
|
||||||
|
top:80px;
|
||||||
|
height: 200px;
|
||||||
|
width:450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-freq > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
font:normal bold 37px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
width:8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_disp{
|
||||||
|
border-radius: 5px 5px 5px 5px ;
|
||||||
|
box-shadow:0px 2px 2px 0px rgba(0, 0, 0, 0.5) inset,0px 2px 2px 0px rgba(255, 255, 255, 0.5);
|
||||||
|
background-color: black;
|
||||||
|
width:450px;
|
||||||
|
padding-top:5px;
|
||||||
|
padding-left:0px;
|
||||||
|
margin-top:10px;
|
||||||
|
margin-bottom:10px;
|
||||||
|
height: 60px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_disp > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
font:normal bold 14px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_disp > ul > li:before{
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_disp_input_text{
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 100%;
|
||||||
|
width:100%;
|
||||||
|
font:normal bold 45px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_but{
|
||||||
|
width:450px;
|
||||||
|
margin:0;
|
||||||
|
padding-left:0px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_but > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
font:normal bold 14px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
margin-top:5px;
|
||||||
|
margin-bottom:5px;
|
||||||
|
width:8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#freq_but > ul > li:before{
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 100%;
|
||||||
|
margin-top:5px;
|
||||||
|
margin-bottom:5px;
|
||||||
|
width:8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-scoketscontrols
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 1100px;
|
||||||
|
top:800px;
|
||||||
|
width: 580px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-scoketscontrols > p
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
margin-right:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-scoketscontrols > p > img
|
||||||
|
{
|
||||||
|
margin-right:5px;
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-conf
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 1750px;
|
||||||
|
top:20px;
|
||||||
|
width: 580px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-conf > a > img
|
||||||
|
{
|
||||||
|
margin-right:5px;
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-panfft
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 1750px;
|
||||||
|
top:55px;
|
||||||
|
width: 580px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-panfft > a > img
|
||||||
|
{
|
||||||
|
margin-right:5px;
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-latencymeter
|
||||||
|
{
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
position:absolute;
|
||||||
|
left:1600px;
|
||||||
|
top:825px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-latencymeter > img
|
||||||
|
{
|
||||||
|
margin-right:5px;
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#RX-GAIN_control
|
||||||
|
{
|
||||||
|
width:300px;
|
||||||
|
height:140px;
|
||||||
|
position:absolute;
|
||||||
|
left:50px;
|
||||||
|
top:300px;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#Txmeters > div {
|
||||||
|
text-align:center;
|
||||||
|
width:300px;
|
||||||
|
position:absolute;
|
||||||
|
left: 50px;
|
||||||
|
top:600px;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#Txmeters div.label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#Txmeters div.value {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#Rxmeters > div {
|
||||||
|
text-align:center;
|
||||||
|
width:300px;
|
||||||
|
position:absolute;
|
||||||
|
left: 50px;
|
||||||
|
top:380px;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#Rxmeters div.label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#Rxmeters div.value {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#TX-GAIN_control
|
||||||
|
{
|
||||||
|
width:300px;
|
||||||
|
height:140px;
|
||||||
|
position:absolute;
|
||||||
|
left:50px;
|
||||||
|
top:530px;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-TX{
|
||||||
|
position:absolute;
|
||||||
|
left:1450px;
|
||||||
|
top:590px;
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
#TX-record
|
||||||
|
{
|
||||||
|
text-align:center;
|
||||||
|
font:normal bold 90px tahoma;
|
||||||
|
height:140px;
|
||||||
|
width:100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#TX-record_record_opus
|
||||||
|
{
|
||||||
|
font:normal bold 10px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#TX-record-lock
|
||||||
|
{
|
||||||
|
text-align:center;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
height:50px;
|
||||||
|
width:100%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#record_opus
|
||||||
|
{
|
||||||
|
text-align:center;
|
||||||
|
width:200px;
|
||||||
|
height:170px;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canBFSPC{
|
||||||
|
position:absolute;
|
||||||
|
left:425px;
|
||||||
|
top:320px;
|
||||||
|
width:450px;
|
||||||
|
height:250px;
|
||||||
|
background:black;
|
||||||
|
background-color:black;
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
box-shadow:0px 2px 2px 0px rgba(0, 0, 0, 0.5) inset,0px 2px 2px 0px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#canBFFFT{
|
||||||
|
position:absolute;
|
||||||
|
left:950px;
|
||||||
|
top:320px;
|
||||||
|
width:450px;
|
||||||
|
height:250px;
|
||||||
|
background:black;
|
||||||
|
background-color:black;
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
box-shadow:0px 2px 2px 0px rgba(0, 0, 0, 1) inset,0px 2px 2px 0px rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
.slider-wrapper{
|
||||||
|
-webkit-appearance: none;
|
||||||
|
height: 0px;
|
||||||
|
border-radius: 5px;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
-webkit-transition: .2s;
|
||||||
|
transition: opacity .2s;
|
||||||
|
background: #FF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper:hover::-moz-range-thumb{
|
||||||
|
opacity: 1;
|
||||||
|
height: 10px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper::-webkit-slider-thumb{
|
||||||
|
background: #FF0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper::-moz-range-thumb{
|
||||||
|
background: #FF0000;
|
||||||
|
height: 4px;
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper_floor{
|
||||||
|
width: 250px;
|
||||||
|
height: 0px;
|
||||||
|
margin: 0;
|
||||||
|
transform-origin: 75px 75px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
position:absolute;
|
||||||
|
left: 940px;
|
||||||
|
top: 420px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper_multdb{
|
||||||
|
width: 250px;
|
||||||
|
height: 0px;
|
||||||
|
margin: 0;
|
||||||
|
transform-origin: 75px 75px;
|
||||||
|
transform: rotate(+90deg);
|
||||||
|
position:absolute;
|
||||||
|
left: 1260px;
|
||||||
|
top: 320px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper_start{
|
||||||
|
width: 450px;
|
||||||
|
height: 0px;
|
||||||
|
margin: 0;
|
||||||
|
transform-origin: 75px 75px;
|
||||||
|
transform: rotate(+180deg);
|
||||||
|
position:absolute;
|
||||||
|
left: 1250px;
|
||||||
|
top: 430px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-wrapper_multhz{
|
||||||
|
width: 450px;
|
||||||
|
height: 0px;
|
||||||
|
margin: 0;
|
||||||
|
position:absolute;
|
||||||
|
left: 950px;
|
||||||
|
top: 310px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvasBFFFT_coord{
|
||||||
|
position:absolute;
|
||||||
|
top:600px;
|
||||||
|
left:950px;
|
||||||
|
z-index: 4;
|
||||||
|
display: none;
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-mode_menu
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left: 950px;
|
||||||
|
top:80px;
|
||||||
|
width: 450px;
|
||||||
|
height:200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-mode_menu > ul
|
||||||
|
{
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
width:450px;
|
||||||
|
height:200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-mode_menu > ul > li {
|
||||||
|
display: inline-block;
|
||||||
|
font: normal bold 45px tahoma;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-mode_menu > ul > li:before{
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_mode{
|
||||||
|
width:222px;
|
||||||
|
height: 65px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#canRXsmeter{
|
||||||
|
position:absolute;
|
||||||
|
left:1475px;
|
||||||
|
top:80px;
|
||||||
|
width:250px;
|
||||||
|
height:50px;
|
||||||
|
background:black;
|
||||||
|
background-color:black;
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
box-shadow:0px 2px 2px 0px rgba(0, 0, 0, 0.5) inset,0px 2px 2px 0px rgba(255, 255, 255, 0.5);
|
||||||
|
background: url(img/smeter.png) no-repeat center center
|
||||||
|
}
|
||||||
|
|
||||||
|
#div-smeterdigitRX
|
||||||
|
{
|
||||||
|
position:absolute;
|
||||||
|
left:1475px;
|
||||||
|
top:140px;
|
||||||
|
text-align:center;
|
||||||
|
font: normal bold 25px tahoma;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#SQUELCH_control
|
||||||
|
{
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
|
width:300px;
|
||||||
|
height:70px;
|
||||||
|
position:absolute;
|
||||||
|
left:1450px;
|
||||||
|
top:180px;
|
||||||
|
font:normal bold 25px tahoma;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @group Blink */
|
||||||
|
.blink {
|
||||||
|
-webkit-animation: blink .75s linear infinite;
|
||||||
|
-moz-animation: blink .75s linear infinite;
|
||||||
|
-ms-animation: blink .75s linear infinite;
|
||||||
|
-o-animation: blink .75s linear infinite;
|
||||||
|
animation: blink .75s linear infinite;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
50.01% { opacity: 0; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
50.01% { opacity: 0; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
@-ms-keyframes blink {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
50.01% { opacity: 0; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
@-o-keyframes blink {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
50.01% { opacity: 0; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
50.01% { opacity: 0; }
|
||||||
|
100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
/* @end */
|