mirror of
https://github.com/Pecusx/piradio-mini.git
synced 2026-05-20 22:33:44 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
Raspberry Pi Internet Radio
|
||||||
|
---------------------------
|
||||||
|
Author : Bob Rathbone
|
||||||
|
Site : http://www.bobrathbone.com
|
||||||
|
Email : bob@bobrathbone.com
|
||||||
|
|
||||||
|
This program uses Music Player Daemon 'mpd', its client 'mpc' and the python-mpd library
|
||||||
|
See http://www.musicpd.org/
|
||||||
|
Use "apt-get install mpd mpc python-mpd" to install the library
|
||||||
|
Modified to use python-mpd library. See https://pypi.python.org/pypi/python-mpd/
|
||||||
|
|
||||||
|
Version 5.1 release notes
|
||||||
|
-------------------------
|
||||||
|
Version 5.1 addresses issues found in 5.0 (Major release) and general improvements
|
||||||
|
1) Resolved the radio crashing if a playlist created without a .m3u extension
|
||||||
|
2) Resolved the radio crashing if invalid RSS file /var/lib/radiod/rss
|
||||||
|
3) Amended test_i3c_lcd test program to support PCF8475 backpack
|
||||||
|
4) Amended test_ada_lcd.py test program to gracefully exit with Ctrl-C key
|
||||||
|
5) Alternative rotary class configurable. New rotary_class=standard/alternative added to radiod.conf
|
||||||
|
6) Missing nodaemon option added to rradiobp.py
|
||||||
|
7) Reboot problem solved (systemd bug solved by installing svinit-core)
|
||||||
|
8) Incorrect status display cured as above
|
||||||
|
|
||||||
|
Version 5.0 release notes
|
||||||
|
-------------------------
|
||||||
|
Version 5.0 is a major release of the radio software specifically for Raspian Jessie
|
||||||
|
Backwards compatibility with Raspian Wheezy is not guaranteed.
|
||||||
|
Version 4.7 is still available for Raspbian Wheezy but is no longer maintained.
|
||||||
|
|
||||||
|
The following changes have been implemented
|
||||||
|
1) New language class to handle languages other than English (New language class)
|
||||||
|
2) Problem with radio station jumping on un-mute solved
|
||||||
|
3) Espeak implemented for people with impaired vision or blindness (New espeak class)
|
||||||
|
4) Full IR Remote Control functionality
|
||||||
|
5) New create_m3u.py program to M3U playlists (pls files are no longer used or supported)
|
||||||
|
6) MPD version 0.19 now suported (was version 0.16)
|
||||||
|
7) Version 1 Rasberry Pi boards are no longer supported
|
||||||
|
8) Due to the very limited and poor functionality Podcasts are no longer supported
|
||||||
|
9) All programs are now installed in /usr/share/radio instead of /home/pi/radio
|
||||||
|
10) Volume range now configurable 10, 20, 25 50 and 100
|
||||||
|
11) Support for HifFerry DAC and DAC+
|
||||||
|
12) Separate option for setting alarm hours and minutes
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
You are strongly advised to install the latest Debian Jessie on a new SD card and
|
||||||
|
then to install MPD and the radio software.
|
||||||
|
Attempting a apt-get dist-upgrade does not do a full Jessie upgrade and may give uncertain results
|
||||||
|
|
||||||
|
Manual
|
||||||
|
------
|
||||||
|
Download from http://www.bobrathbone.com/raspberrypi/Raspberry%20PI%20Radio.pdf
|
||||||
|
|
||||||
|
Github
|
||||||
|
------
|
||||||
|
Source available from Github
|
||||||
|
See https://github.com/bobrathbone/piradio
|
||||||
|
|
||||||
|
Licenses
|
||||||
|
--------
|
||||||
|
GNU General Public License. See http://www.gnu.org/licenses/gpl.html
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE. See http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
Disclaimer
|
||||||
|
----------
|
||||||
|
Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
The authors shall not be liable for any loss or damage however caused.
|
||||||
|
|
||||||
|
|
||||||
Executable
+517
@@ -0,0 +1,517 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi Internet Radio Class
|
||||||
|
# $Id: config_class.py,v 1.32 2016/07/23 13:21:28 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This class reads the /etc/radiod.conf file for configuration parameters
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import ConfigParser
|
||||||
|
from log_class import Log
|
||||||
|
|
||||||
|
# System files
|
||||||
|
ConfigFile = "/etc/radiod.conf"
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
config = ConfigParser.ConfigParser()
|
||||||
|
|
||||||
|
|
||||||
|
class Configuration:
|
||||||
|
# Input source
|
||||||
|
RADIO = 0
|
||||||
|
PLAYER = 1
|
||||||
|
LIST = 0
|
||||||
|
STREAM = 1
|
||||||
|
|
||||||
|
# Rotary class selection
|
||||||
|
STANDARD = 0 # Select rotary_class.py
|
||||||
|
ALTERNATIVE = 1 # Select rotary_class_alternative.py
|
||||||
|
|
||||||
|
# Configuration parameters
|
||||||
|
mpdport = 6600 # MPD port number
|
||||||
|
dateFormat = "%H:%M %d/%m/%Y" # Date format
|
||||||
|
volume_range = 100 # Volume range 10 to 100
|
||||||
|
volume_increment = 1 # Volume increment 1 to 10
|
||||||
|
display_playlist_number = False # Two line displays only, display station(n)
|
||||||
|
source = RADIO # Source RADIO or Player
|
||||||
|
stationNamesSource = LIST # Station names from playlist names or STREAM
|
||||||
|
rotary_class = STANDARD # Rotary class STANDARD or ALTERNATIVE
|
||||||
|
|
||||||
|
# Remote control parameters
|
||||||
|
remote_led = 0 # Remote Control activity LED 0 = No LED
|
||||||
|
remote_control_host = 'localhost' # Remote control to radio communication port
|
||||||
|
remote_listen_host = 'localhost' # Remote control to radio communication port
|
||||||
|
remote_control_port = 5100 # Remote control to radio communication port
|
||||||
|
|
||||||
|
ADAFRUIT = 1 # I2C backpack type AdaFruit
|
||||||
|
PCF8574 = 2 # I2C backpack type PCF8574
|
||||||
|
i2c_backpack = ADAFRUIT
|
||||||
|
i2c_address = 0x00 # Use defaults or use setting in radiod.conf
|
||||||
|
backpack_names = [ 'UNKWOWN','ADAFRUIT','PCF8574']
|
||||||
|
speech = False # Speech on for visually impaired or blind persons
|
||||||
|
isVerbose = False # Extra speech verbosity
|
||||||
|
speech_volume = 80 # Percentage speech volume
|
||||||
|
use_playlist_extensions = False # MPD 0.15 requires playlist.<ext>
|
||||||
|
rss = False # RSS in standby mode (Pecus)
|
||||||
|
bright = True # LCD high brightness (Pecus)
|
||||||
|
media_update = False # always update media library (Pecus)
|
||||||
|
pandora_available = False # is Pandora accout available ? (Pecus)
|
||||||
|
|
||||||
|
# Colours for Adafruit LCD
|
||||||
|
color = { 'OFF': 0x0, 'RED' : 0x1, 'GREEN' : 0x2, 'YELLOW' : 0x3,
|
||||||
|
'BLUE' : 0x4, 'VIOLET' : 0x5, 'TEAL' : 0x6, 'WHITE' : 0x7 }
|
||||||
|
|
||||||
|
colorName = { 0: 'Off', 1 : 'Red', 2 : 'Green', 3 : 'Yellow',
|
||||||
|
4 : 'Blue', 5 : 'Violet', 6 : 'Teal', 7 : 'White' }
|
||||||
|
|
||||||
|
colors = { 'bg_color' : 0x0,
|
||||||
|
'mute_color' : 0x0,
|
||||||
|
'shutdown_color' : 0x0,
|
||||||
|
'error_color' : 0x0,
|
||||||
|
'search_color' : 0x0,
|
||||||
|
'source_color' : 0x0,
|
||||||
|
'info_color' : 0x0,
|
||||||
|
'menu_color' : 0x0,
|
||||||
|
'sleep_color': 0x0 }
|
||||||
|
|
||||||
|
# List of loaded options for display
|
||||||
|
configOptions = {}
|
||||||
|
|
||||||
|
# Other definitions
|
||||||
|
UP = 0
|
||||||
|
DOWN = 1
|
||||||
|
|
||||||
|
# GPIOs for switches and rotary encoder configuration
|
||||||
|
switches = { "menu_switch": 25,
|
||||||
|
"mute_switch": 4,
|
||||||
|
"left_switch": 14,
|
||||||
|
"right_switch": 15,
|
||||||
|
"up_switch": 17,
|
||||||
|
"down_switch": 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
# GPIOs for LCD connections
|
||||||
|
lcdconnects = {
|
||||||
|
"lcd_enable": 8,
|
||||||
|
"lcd_select": 7,
|
||||||
|
"lcd_data4": 27,
|
||||||
|
"lcd_data5": 22,
|
||||||
|
"lcd_data6": 23,
|
||||||
|
"lcd_data7": 24,
|
||||||
|
"lcd_bri": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialisation routine
|
||||||
|
def __init__(self):
|
||||||
|
log.init('radio')
|
||||||
|
if not os.path.isfile(ConfigFile) or os.path.getsize(ConfigFile) == 0:
|
||||||
|
log.message("Missing configuration file " + ConfigFile, log.ERROR)
|
||||||
|
else:
|
||||||
|
self.getConfig()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get configuration options from /etc/radiod.conf
|
||||||
|
def getConfig(self):
|
||||||
|
section = 'RADIOD'
|
||||||
|
|
||||||
|
# Get options
|
||||||
|
config.read(ConfigFile)
|
||||||
|
try:
|
||||||
|
options = config.options(section)
|
||||||
|
for option in options:
|
||||||
|
parameter = config.get(section,option)
|
||||||
|
msg = "Config option: " + option + " = " + parameter
|
||||||
|
|
||||||
|
self.configOptions[option] = parameter
|
||||||
|
|
||||||
|
if option == 'loglevel':
|
||||||
|
next
|
||||||
|
|
||||||
|
elif option == 'volume_range':
|
||||||
|
range = int(parameter)
|
||||||
|
if range < 10:
|
||||||
|
range = 10
|
||||||
|
if range > 100:
|
||||||
|
range = 100
|
||||||
|
self.volume_range = range
|
||||||
|
self.volume_increment = int(100/range)
|
||||||
|
|
||||||
|
elif option == 'remote_led':
|
||||||
|
self.remote_led = int(parameter)
|
||||||
|
|
||||||
|
elif option == 'remote_control_host':
|
||||||
|
self.remote_control_host = parameter
|
||||||
|
|
||||||
|
elif option == 'remote_control_port':
|
||||||
|
self.remote_control_port = int(parameter)
|
||||||
|
|
||||||
|
elif option == 'remote_listen_host':
|
||||||
|
self.remote_listen_host = parameter
|
||||||
|
|
||||||
|
elif option == 'mpdport':
|
||||||
|
self.mpdport = int(parameter)
|
||||||
|
|
||||||
|
elif option == 'dateformat':
|
||||||
|
self.dateFormat = parameter
|
||||||
|
|
||||||
|
elif option == 'display_playlist_number':
|
||||||
|
if parameter == 'yes':
|
||||||
|
self.display_playlist_number = True
|
||||||
|
|
||||||
|
elif option == 'startup':
|
||||||
|
if parameter == 'MEDIA':
|
||||||
|
self.source = self.PLAYER
|
||||||
|
|
||||||
|
elif option == 'station_names':
|
||||||
|
if parameter == 'stream':
|
||||||
|
self.stationNamesSource = self.STREAM
|
||||||
|
else:
|
||||||
|
self.stationNamesSource = self.LIST
|
||||||
|
|
||||||
|
elif option == 'i2c_backpack':
|
||||||
|
if parameter == 'PCF8574':
|
||||||
|
self.i2c_backpack = self.PCF8574
|
||||||
|
else:
|
||||||
|
self.i2c_backpack = self.ADAFRUIT
|
||||||
|
|
||||||
|
elif option == 'i2c_address':
|
||||||
|
value = int(parameter,16)
|
||||||
|
if parameter > 0x00:
|
||||||
|
self.i2c_address = value
|
||||||
|
|
||||||
|
elif 'color' in option:
|
||||||
|
try:
|
||||||
|
self.colors[option] = self.color[parameter]
|
||||||
|
except:
|
||||||
|
log.message("Invalid option " + option + ' ' + parameter, log.ERROR)
|
||||||
|
|
||||||
|
elif option == 'speech':
|
||||||
|
if parameter == 'yes':
|
||||||
|
self.speech = True
|
||||||
|
else:
|
||||||
|
self.speech = False
|
||||||
|
|
||||||
|
elif option == 'verbose':
|
||||||
|
if parameter == 'yes':
|
||||||
|
self.isVerbose = True
|
||||||
|
else:
|
||||||
|
self.isVerbose = False
|
||||||
|
|
||||||
|
elif option == 'speech_volume':
|
||||||
|
self.speech_volume = int(parameter)
|
||||||
|
|
||||||
|
elif option == 'use_playlist_extensions':
|
||||||
|
if parameter == 'yes':
|
||||||
|
self.use_playlist_extensions = True
|
||||||
|
else:
|
||||||
|
self.use_playlist_extensions = False
|
||||||
|
|
||||||
|
elif '_switch' in option:
|
||||||
|
switch = int(parameter)
|
||||||
|
try:
|
||||||
|
self.switches[option] = switch
|
||||||
|
except:
|
||||||
|
msg = "Invalid switch parameter " + option
|
||||||
|
log.message(msg,log.ERROR)
|
||||||
|
|
||||||
|
elif 'lcd_' in option:
|
||||||
|
lcdconnect = int(parameter)
|
||||||
|
try:
|
||||||
|
self.lcdconnects[option] = lcdconnect
|
||||||
|
except:
|
||||||
|
msg = "Invalid LCD connect parameter " + option
|
||||||
|
log.message(msg,log.ERROR)
|
||||||
|
|
||||||
|
elif option == 'rotary_class':
|
||||||
|
if parameter == 'standard':
|
||||||
|
self.rotary_class = self.STANDARD
|
||||||
|
else:
|
||||||
|
self.rotary_class = self.ALTERNATIVE
|
||||||
|
|
||||||
|
elif option == 'rss': # option for RSS in standby mode (Pecus)
|
||||||
|
if parameter == 'yes': # (Pecus)
|
||||||
|
self.rss = True # (Pecus)
|
||||||
|
else: # (Pecus)
|
||||||
|
self.rss = False # (Pecus)
|
||||||
|
|
||||||
|
elif option == 'bright': # option for high brightness of LCD (Pecus)
|
||||||
|
if parameter == 'yes': # (Pecus)
|
||||||
|
self.bright = True # (Pecus)
|
||||||
|
else: # (Pecus)
|
||||||
|
self.bright = False # (Pecus)
|
||||||
|
|
||||||
|
elif option == 'media_update': # option for update media library (Pecus)
|
||||||
|
if parameter == 'yes': # (Pecus)
|
||||||
|
self.media_update = True # (Pecus)
|
||||||
|
else: # (Pecus)
|
||||||
|
self.media_update = False # (Pecus)
|
||||||
|
|
||||||
|
elif option == 'pandora_available': # option for Pandora availablity (Pecus)
|
||||||
|
if parameter == 'yes': # (Pecus)
|
||||||
|
self.pandora_available = True # (Pecus)
|
||||||
|
else: # (Pecus)
|
||||||
|
self.pandora_available = False # (Pecus)
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = "Invalid option " + option + ' in section ' \
|
||||||
|
+ section + ' in ' + ConfigFile
|
||||||
|
log.message(msg,log.ERROR)
|
||||||
|
|
||||||
|
except ConfigParser.NoSectionError:
|
||||||
|
msg = ConfigParser.NoSectionError(section),'in',ConfigFile
|
||||||
|
log.message(msg,log.ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get routines
|
||||||
|
|
||||||
|
# Get I2C backpack type
|
||||||
|
def getBackPackType(self):
|
||||||
|
return self.i2c_backpack
|
||||||
|
|
||||||
|
# Get I2C backpack address
|
||||||
|
def getI2Caddress(self):
|
||||||
|
return self.i2c_address
|
||||||
|
|
||||||
|
# Get I2C backpack name
|
||||||
|
def getBackPackName(self):
|
||||||
|
return self.backpack_names[self.i2c_backpack]
|
||||||
|
|
||||||
|
# Get the volume range
|
||||||
|
def getVolumeRange(self):
|
||||||
|
return self.volume_range
|
||||||
|
|
||||||
|
# Get the volume increment
|
||||||
|
def getVolumeIncrement(self):
|
||||||
|
return self.volume_increment
|
||||||
|
|
||||||
|
# Get the remote control activity LED number
|
||||||
|
def getRemoteLed(self):
|
||||||
|
return self.remote_led
|
||||||
|
|
||||||
|
# Get the remote Host default localhost
|
||||||
|
def getRemoteUdpHost(self):
|
||||||
|
return self.remote_control_host
|
||||||
|
|
||||||
|
# Get the UDP server listener IP Host default localhost
|
||||||
|
# or 0.0.0.0 for all interfaces
|
||||||
|
def getRemoteListenHost(self):
|
||||||
|
return self.remote_listen_host
|
||||||
|
|
||||||
|
# Get the remote Port default 5100
|
||||||
|
def getRemoteUdpPort(self):
|
||||||
|
return self.remote_control_port
|
||||||
|
|
||||||
|
# Get the mpdport
|
||||||
|
def getMpdPort(self):
|
||||||
|
return self.mpdport
|
||||||
|
|
||||||
|
# Get the date format
|
||||||
|
def getDateFormat(self):
|
||||||
|
return self.dateFormat
|
||||||
|
|
||||||
|
# Get display playlist number (Two line displays only)
|
||||||
|
def getDisplayPlaylistNumber(self):
|
||||||
|
return self.display_playlist_number
|
||||||
|
|
||||||
|
# Get the startup source 0=RADIO or 1=MEDIA
|
||||||
|
def getSource(self):
|
||||||
|
return self.source
|
||||||
|
|
||||||
|
# Get the startup source name RADIO MEDIA
|
||||||
|
def getSourceName(self):
|
||||||
|
source_name = "MEDIA"
|
||||||
|
if self.getSource() < 1:
|
||||||
|
source_name = "RADIO"
|
||||||
|
return source_name
|
||||||
|
|
||||||
|
# Get the background color (Integer)
|
||||||
|
def getBackColor(self,sColor):
|
||||||
|
color = 0x0
|
||||||
|
# Get the remote Port default 5100
|
||||||
|
def getRemoteUdpPort(self):
|
||||||
|
return self.remote_control_port
|
||||||
|
|
||||||
|
# Get the mpdport
|
||||||
|
def getMpdPort(self):
|
||||||
|
return self.mpdport
|
||||||
|
|
||||||
|
# Get the date format
|
||||||
|
def getDateFormat(self):
|
||||||
|
return self.dateFormat
|
||||||
|
|
||||||
|
# Get display playlist number (Two line displays only)
|
||||||
|
def getDisplayPlaylistNumber(self):
|
||||||
|
return self.display_playlist_number
|
||||||
|
|
||||||
|
# Get the startup source 0=RADIO or 1=MEDIA
|
||||||
|
def getSource(self):
|
||||||
|
return self.source
|
||||||
|
|
||||||
|
# Get the startup source name RADIO MEDIA
|
||||||
|
def getSourceName(self):
|
||||||
|
source_name = "MEDIA"
|
||||||
|
if self.getSource() < 1:
|
||||||
|
source_name = "RADIO"
|
||||||
|
return source_name
|
||||||
|
|
||||||
|
# Get the background color (Integer)
|
||||||
|
def getBackColor(self,sColor):
|
||||||
|
color = 0x0
|
||||||
|
try:
|
||||||
|
color = self.colors[sColor]
|
||||||
|
except:
|
||||||
|
log.message("Invalid option " + sColor, log.ERROR)
|
||||||
|
return color
|
||||||
|
|
||||||
|
# Cycle background colors
|
||||||
|
def cycleColor(self,direction):
|
||||||
|
color = self.getBackColor('bg_color')
|
||||||
|
|
||||||
|
if direction == self.UP:
|
||||||
|
color += 1
|
||||||
|
else:
|
||||||
|
color -= 1
|
||||||
|
|
||||||
|
if color < 0:
|
||||||
|
color = 0x7
|
||||||
|
elif color > 0x7:
|
||||||
|
color = 0x0
|
||||||
|
|
||||||
|
self.colors['bg_color'] = color
|
||||||
|
return color
|
||||||
|
|
||||||
|
|
||||||
|
# Get the background colour string name
|
||||||
|
def getBackColorName(self,iColor):
|
||||||
|
sColor = 'None'
|
||||||
|
try:
|
||||||
|
sColor = self.colorName[iColor]
|
||||||
|
except:
|
||||||
|
log.message("Invalid option " + int(iColor), log.ERROR)
|
||||||
|
return sColor
|
||||||
|
|
||||||
|
# Get speech
|
||||||
|
def getSpeech(self):
|
||||||
|
return self.speech
|
||||||
|
|
||||||
|
# Get verbose
|
||||||
|
def verbose(self):
|
||||||
|
return self.isVerbose
|
||||||
|
|
||||||
|
# Get RSS mode (Pecus)
|
||||||
|
def getRss(self): # (Pecus)
|
||||||
|
return self.rss # (Pecus)
|
||||||
|
|
||||||
|
# Get LCD brightness mode (Pecus)
|
||||||
|
def getBright(self): # (Pecus)
|
||||||
|
return self.bright # (Pecus)
|
||||||
|
|
||||||
|
# Get always media update flag (Pecus)
|
||||||
|
def getAlwaysUpdate(self): # (Pecus)
|
||||||
|
return self.media_update # (Pecus)
|
||||||
|
|
||||||
|
# Get pandora flag (Pecus)
|
||||||
|
def getPandoraAvailable(self): # (Pecus)
|
||||||
|
return self.pandora_available # (Pecus)
|
||||||
|
|
||||||
|
# Get speech volume % of normal volume level
|
||||||
|
def getSpeechVolume(self):
|
||||||
|
return self.speech_volume
|
||||||
|
|
||||||
|
# Get playlist extensions used
|
||||||
|
def getPlaylistExtensions(self):
|
||||||
|
return self.use_playlist_extensions
|
||||||
|
|
||||||
|
# Return the station name source (Stream or playlist)
|
||||||
|
def getStationNamesSource(self):
|
||||||
|
return self.stationNamesSource
|
||||||
|
|
||||||
|
# Display parameters
|
||||||
|
def display(self):
|
||||||
|
for option in self.configOptions:
|
||||||
|
param = self.configOptions[option]
|
||||||
|
if option != 'None':
|
||||||
|
log.message(option + " = " + param, log.DEBUG)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Return the ID of the rotary class to be used STANDARD or ALTERNATIVE
|
||||||
|
def getRotaryClass(self):
|
||||||
|
return self.rotary_class
|
||||||
|
|
||||||
|
# Returns the switch GPIO configuration by label
|
||||||
|
def getSwitchGpio(self,label):
|
||||||
|
switch = -1
|
||||||
|
try:
|
||||||
|
switch = self.switches[label]
|
||||||
|
except:
|
||||||
|
msg = "Invalid switch label " + label
|
||||||
|
log.message(msg, log.ERROR)
|
||||||
|
return switch
|
||||||
|
|
||||||
|
# Returns the LCD GPIO configuration by label
|
||||||
|
def getLcdGpio(self,label):
|
||||||
|
switch = -1
|
||||||
|
try:
|
||||||
|
lcdconnect = self.lcdconnects[label]
|
||||||
|
except:
|
||||||
|
msg = "Invalid LCD connection label " + label
|
||||||
|
log.message(msg, log.ERROR)
|
||||||
|
return lcdconnect
|
||||||
|
|
||||||
|
# End Configuration of class
|
||||||
|
|
||||||
|
# Test Configuration class
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
config = Configuration()
|
||||||
|
print "Configuration file", ConfigFile
|
||||||
|
print "Volume range:", config.getVolumeRange()
|
||||||
|
print "Volume increment:", config.getVolumeIncrement()
|
||||||
|
print "Mpd port:", config.getMpdPort()
|
||||||
|
print "Remote LED:", config.getRemoteLed()
|
||||||
|
print "Remote LED port:", config.getRemoteUdpPort()
|
||||||
|
print "Date format:", config.getDateFormat()
|
||||||
|
print "Display playlist number:", config.getDisplayPlaylistNumber()
|
||||||
|
print "Source:", config.getSource(), config.getSourceName()
|
||||||
|
print "Background colour number:", config.getBackColor('bg_color')
|
||||||
|
print "Background colour:", config.getBackColorName(config.getBackColor('bg_color'))
|
||||||
|
print "Speech:", config.getSpeech()
|
||||||
|
print "Speech volume:", str(config.getSpeechVolume()) + '%'
|
||||||
|
print "Verbose:", config.verbose()
|
||||||
|
print "RRS in standby:", config.getRss() # (Pecus)
|
||||||
|
print "LCD high brightness:", config.getBright() # (Pecus)
|
||||||
|
print "Always update media library:", config.getAlwaysUpdate() # (Pecus)
|
||||||
|
print "Pandora account available:", config.getPandoraAvailable() # (Pecus)
|
||||||
|
if config.getStationNamesSource() is 1:
|
||||||
|
sSource = "STREAM"
|
||||||
|
else:
|
||||||
|
sSource = "LIST"
|
||||||
|
print "Station names source:",sSource
|
||||||
|
print "Use playlist extensions:", config.getPlaylistExtensions()
|
||||||
|
|
||||||
|
for switch in config.switches:
|
||||||
|
print switch, config.getSwitchGpio(switch)
|
||||||
|
|
||||||
|
for lcdconnect in sorted(config.lcdconnects):
|
||||||
|
print lcdconnect, config.getLcdGpio(lcdconnect)
|
||||||
|
|
||||||
|
rclass = ['Standard', 'Alternative']
|
||||||
|
rotary_class = config.getRotaryClass()
|
||||||
|
print "Rotary class:", rotary_class, rclass[rotary_class]
|
||||||
|
print "Backpack type:", config.getBackPackType(), config.getBackPackName()
|
||||||
|
print "I2C address:", hex(config.getI2Caddress())
|
||||||
|
|
||||||
|
# End of file
|
||||||
|
|
||||||
Binary file not shown.
Executable
+536
@@ -0,0 +1,536 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Raspberry Pi Internet Radio playlist utility
|
||||||
|
# $Id: create_m3u.py,v 1.6 2016/04/29 17:22:50 bob Exp $
|
||||||
|
#
|
||||||
|
# Create playlist files from the following url formats
|
||||||
|
# iPhone stream files (.asx)
|
||||||
|
# Playlist files (.pls)
|
||||||
|
# Extended M3U files (.m3u)
|
||||||
|
# Direct media streams (no extension)
|
||||||
|
#
|
||||||
|
# See Raspberry PI Radio constructors manual for instructions
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Web site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import urllib2
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
|
||||||
|
# Output errors to STDERR
|
||||||
|
stderr = sys.stderr.write;
|
||||||
|
|
||||||
|
# File locations
|
||||||
|
PlsDirectory = '/var/lib/mpd/playlists/'
|
||||||
|
RadioDir = '/usr/share/radio/'
|
||||||
|
RadioLibDir = '/var/lib/radiod/'
|
||||||
|
StationList = RadioLibDir + 'stationlist'
|
||||||
|
DistFile = RadioDir + 'station.urls'
|
||||||
|
TempDir = '/tmp/radio_stream_files/'
|
||||||
|
PlaylistsDir = RadioDir + 'playlists/'
|
||||||
|
|
||||||
|
duplicateCount = 0
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def execCommand(cmd):
|
||||||
|
p = os.popen(cmd)
|
||||||
|
return p.readline().rstrip('\n')
|
||||||
|
|
||||||
|
# Create the initial list of files
|
||||||
|
def createList():
|
||||||
|
if not os.path.isfile(StationList):
|
||||||
|
print 'Creating ' + StationList + '\n'
|
||||||
|
execCommand ("mkdir -p " + RadioLibDir )
|
||||||
|
print ("cp " + DistFile + ' ' + StationList )
|
||||||
|
execCommand ("cp " + DistFile + ' ' + StationList)
|
||||||
|
print
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create M3U output from the title and URL
|
||||||
|
def createM3uOutput(title,url,filenumber):
|
||||||
|
lines = []
|
||||||
|
title = title.rstrip()
|
||||||
|
url = url.rstrip()
|
||||||
|
lines.append('#EXTM3U')
|
||||||
|
lines.append('#EXTINF:-1,%s' % title)
|
||||||
|
lines.append(url + '#' + title)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
# Create M3U or M3U file
|
||||||
|
def createM3uFile(filename,output,nlines):
|
||||||
|
global duplicateCount
|
||||||
|
uniqueCount = 1
|
||||||
|
if len(filename) < 1:
|
||||||
|
filename = 'unknown'
|
||||||
|
outfile = TempDir + filename + '.m3u'
|
||||||
|
|
||||||
|
# Create unique files
|
||||||
|
exists = True
|
||||||
|
while exists:
|
||||||
|
if os.path.exists(outfile):
|
||||||
|
print "Warning: " + outfile + ' already exists'
|
||||||
|
outfile = TempDir + filename + '[' + str(uniqueCount) + '].m3u'
|
||||||
|
uniqueCount += 1
|
||||||
|
duplicateCount += 1
|
||||||
|
else:
|
||||||
|
exists = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
print 'Creating ' + outfile + '\n'
|
||||||
|
outfile = open(outfile,'w')
|
||||||
|
for item in output:
|
||||||
|
outstr = item.encode('utf8', 'replace')
|
||||||
|
outfile.write(outstr + "\n")
|
||||||
|
|
||||||
|
outfile.close()
|
||||||
|
except:
|
||||||
|
print "Failed to create",outfile
|
||||||
|
return
|
||||||
|
|
||||||
|
# Beautify HTML convert tags to lower case
|
||||||
|
def parseHTML(data):
|
||||||
|
lcdata = ''
|
||||||
|
for line in data:
|
||||||
|
lcline = ''
|
||||||
|
line = line.rstrip()
|
||||||
|
line = line.lstrip()
|
||||||
|
line.replace('href =', 'href=')
|
||||||
|
length = len(line)
|
||||||
|
|
||||||
|
if length < 1:
|
||||||
|
continue
|
||||||
|
tag1right = line.find('>')
|
||||||
|
|
||||||
|
if tag1right > 1:
|
||||||
|
start_tag = line[0:tag1right+1]
|
||||||
|
lcline = lcline + start_tag.lower()
|
||||||
|
|
||||||
|
tag2left = line.find('<', tag1right+1)
|
||||||
|
|
||||||
|
if tag2left > 1:
|
||||||
|
end_tag = line[tag2left:length]
|
||||||
|
parameter = line[tag1right+1:tag2left]
|
||||||
|
lcline = lcline + parameter + end_tag.lower()
|
||||||
|
lcdata = lcdata + lcline
|
||||||
|
return lcdata
|
||||||
|
|
||||||
|
# Get XML/HTML parameter
|
||||||
|
def getParameter(line):
|
||||||
|
tag1right = line.find('>')
|
||||||
|
tag2left = line.find('<', tag1right+1)
|
||||||
|
parameter = line[tag1right+1:tag2left]
|
||||||
|
return parameter
|
||||||
|
|
||||||
|
|
||||||
|
# Create a M3U file from an ASX(XML) file
|
||||||
|
def parseAsx(title,url,data,filenumber):
|
||||||
|
global errorCount
|
||||||
|
global warningCount
|
||||||
|
lcdata = parseHTML(data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
dom = parseString(lcdata)
|
||||||
|
except Exception,e:
|
||||||
|
print "Error:",e
|
||||||
|
print "Error: Could not parse XML data from,", url + '\n'
|
||||||
|
errorCount += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# If title undefined in the station list get it from the file
|
||||||
|
if len(title) < 1:
|
||||||
|
titleTag = dom.getElementsByTagName('title')[0].toxml()
|
||||||
|
title = getParameter(titleTag)
|
||||||
|
|
||||||
|
except:
|
||||||
|
print "Warning: Title not found in", url
|
||||||
|
pass
|
||||||
|
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
urlTag = dom.getElementsByTagName('ref')[0].toxml()
|
||||||
|
url = urlTag.replace('<ref href=\"','').replace('\"/>','')
|
||||||
|
urls = url.split('?')
|
||||||
|
url = urls[0]
|
||||||
|
title = title.rstrip()
|
||||||
|
url = url.rstrip()
|
||||||
|
print 'Title:',title
|
||||||
|
m3ufile = title.replace(' ','_')
|
||||||
|
output = createM3uOutput(title,url,filenumber)
|
||||||
|
except IndexError,e:
|
||||||
|
print "Error:",e
|
||||||
|
print "Error parsing", url
|
||||||
|
errorCount += 1
|
||||||
|
return "# DOM Error"
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
# Create filename from URL
|
||||||
|
def createFileName(title,url):
|
||||||
|
if len(title) > 0:
|
||||||
|
name = title
|
||||||
|
name = name.replace('.',' ')
|
||||||
|
name = name.replace(' ','_')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
urlparts = url.rsplit('/',1)
|
||||||
|
site = urlparts[0]
|
||||||
|
siteparts = site.split('/')
|
||||||
|
name = siteparts[2]
|
||||||
|
siteparts = name.split(':')
|
||||||
|
name = siteparts[0]
|
||||||
|
except:
|
||||||
|
name = url
|
||||||
|
name = name.replace('www.','')
|
||||||
|
name = name.replace('.com','')
|
||||||
|
name = name.replace('.','_')
|
||||||
|
|
||||||
|
name = name.replace('__','_')
|
||||||
|
return name
|
||||||
|
|
||||||
|
# Create default title
|
||||||
|
def createTitle(url):
|
||||||
|
urlparts = url.rsplit('/',1)
|
||||||
|
site = urlparts[0]
|
||||||
|
siteparts = site.split('/')
|
||||||
|
name = siteparts[2]
|
||||||
|
siteparts = name.split(':')
|
||||||
|
title = siteparts[0]
|
||||||
|
return title
|
||||||
|
|
||||||
|
# Direct radio stream (MP3 AAC etc)
|
||||||
|
def parseDirect(title,url,filenumber):
|
||||||
|
url = url.replace('(stream)', '')
|
||||||
|
if len(title) < 1:
|
||||||
|
title = createTitle(url)
|
||||||
|
print "Title:",title
|
||||||
|
output = createM3uOutput(title,url,filenumber)
|
||||||
|
return output
|
||||||
|
|
||||||
|
# Create M3U file from PLS in the temporary directory
|
||||||
|
def parsePls(title,url,lines,filenumber):
|
||||||
|
plstitle = ''
|
||||||
|
plsurl = ''
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('Title1='):
|
||||||
|
titleline = line.split('=')
|
||||||
|
plstitle = titleline[1]
|
||||||
|
|
||||||
|
if line.startswith('File1='):
|
||||||
|
fileline = line.split('=')
|
||||||
|
plsurl = fileline[1]
|
||||||
|
|
||||||
|
# If title undefined in the station list get it from the file
|
||||||
|
if len(title) < 1:
|
||||||
|
if len(plstitle) > 1:
|
||||||
|
title = plstitle
|
||||||
|
else:
|
||||||
|
title = createTitle(url)
|
||||||
|
m3ufile = createFileName(title,url)
|
||||||
|
|
||||||
|
print 'Title:',title
|
||||||
|
m3ufile = title.replace(' ','_')
|
||||||
|
output = createM3uOutput(title,plsurl,filenumber)
|
||||||
|
return output
|
||||||
|
|
||||||
|
# Parse M3U file to PLS output
|
||||||
|
def parseM3u(title,lines,filenumber):
|
||||||
|
info = 'Unknown'
|
||||||
|
output = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.replace('\r','')
|
||||||
|
line = line.replace('\n','')
|
||||||
|
if line.startswith('http:'):
|
||||||
|
url = line
|
||||||
|
elif line.startswith('#EXTINF:'):
|
||||||
|
info = line
|
||||||
|
|
||||||
|
if len(title) < 1:
|
||||||
|
title = info
|
||||||
|
|
||||||
|
if len(title) < 1:
|
||||||
|
filename = createFileName(title,url)
|
||||||
|
else:
|
||||||
|
filename = title.replace(' ','_')
|
||||||
|
|
||||||
|
print 'Title:',title
|
||||||
|
output.append('#EXTM3U')
|
||||||
|
output.append('#EXTINF:-1,%s'% title)
|
||||||
|
output.append('%s#%s'% (url,title))
|
||||||
|
return output
|
||||||
|
|
||||||
|
# Usage message
|
||||||
|
def usage():
|
||||||
|
stderr("\nUsage: %s [--delete_old] [--no_delete] [--help]\n" % sys.argv[0])
|
||||||
|
stderr("\tWhere: --delete_old Delete old playlists\n")
|
||||||
|
stderr("\t --no_delete Don't delete old playlists\n")
|
||||||
|
stderr("\t --help Display help message\n\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Station definition help message
|
||||||
|
def format():
|
||||||
|
stderr ("Start a playlist with the name between brackets. For example:\n")
|
||||||
|
stderr ("(BBC Radio Stations)\n")
|
||||||
|
stderr ("This will create a playlist called BBC_Radio_Stations.m3u\n")
|
||||||
|
stderr ("\nThe above is followed by station definitions which take the following format:\n")
|
||||||
|
stderr ("\t[title] http://<url>\n")
|
||||||
|
stderr ("\tExample:\n")
|
||||||
|
stderr ("\t[BBC Radio 3] http://bbc.co.uk/radio/listen/live/r3.asx\n\n")
|
||||||
|
stderr ("End a playlist by inserting a blank line at the end of the list of stations\n")
|
||||||
|
stderr ("or start a new playlist definition.\n\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Start of MAIN script
|
||||||
|
|
||||||
|
if os.getuid() != 0:
|
||||||
|
print "This program can only be run as root user or using sudo"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
deleteOld = False
|
||||||
|
noDelete = False
|
||||||
|
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
stderr("\nError: you may not define more than one parameter at a time\n")
|
||||||
|
usage()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
param = sys.argv[1]
|
||||||
|
if param == '--delete_old':
|
||||||
|
deleteOld = True
|
||||||
|
|
||||||
|
elif param == '--no_delete':
|
||||||
|
noDelete = True
|
||||||
|
|
||||||
|
elif param == '--help':
|
||||||
|
usage()
|
||||||
|
format()
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
stderr("Invalid parameter %s\n" % param)
|
||||||
|
usage()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create station URL list
|
||||||
|
createList()
|
||||||
|
|
||||||
|
# Temporary directory - if it exists then delete all pls files from it
|
||||||
|
execCommand ("mkdir -p " + TempDir )
|
||||||
|
execCommand ("rm -f " + TempDir + '*' )
|
||||||
|
|
||||||
|
# Open the list of URLs
|
||||||
|
print "Creating M3U files from", StationList + '\n'
|
||||||
|
|
||||||
|
lineCount = 0 # Line being processed (Including comments)
|
||||||
|
errorCount = 0 # Errors
|
||||||
|
duplicateCount = 0 # Duplicate file names
|
||||||
|
warningCount = 0 # Warnings
|
||||||
|
processedCount = 0 # Processed station count
|
||||||
|
|
||||||
|
# Copy user stream files to temporary directory
|
||||||
|
|
||||||
|
if os.path.exists(PlaylistsDir + '*.m3u'):
|
||||||
|
print "Copying user M3U files from " + PlaylistsDir + " to " + TempDir + '\n'
|
||||||
|
execCommand ("cp -f " + PlaylistsDir + '*.m3u ' + TempDir )
|
||||||
|
|
||||||
|
# Playlist file name
|
||||||
|
filename = ''
|
||||||
|
m3u_output = []
|
||||||
|
filenumber = 1
|
||||||
|
writeFile = False
|
||||||
|
url = ''
|
||||||
|
|
||||||
|
# Main processing loop
|
||||||
|
for line in open(StationList,'r'):
|
||||||
|
lineCount += 1
|
||||||
|
lines = []
|
||||||
|
newplaylist = ''
|
||||||
|
|
||||||
|
# Set url types to False
|
||||||
|
isASX = False
|
||||||
|
isM3U = False
|
||||||
|
isPLS = False
|
||||||
|
|
||||||
|
# Skip commented out or blank lines
|
||||||
|
line = line.rstrip() # Remove line feed
|
||||||
|
if line[:1] == '#':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle playlist definition in () brackets
|
||||||
|
elif line[:1] == '(':
|
||||||
|
newplaylist = line.strip('(') # Remove left bracket
|
||||||
|
newplaylist = newplaylist.strip(')') # Remove right bracket
|
||||||
|
playlistname = newplaylist
|
||||||
|
newplaylist = newplaylist.replace(' ', '_')
|
||||||
|
|
||||||
|
if len(filename) > 0:
|
||||||
|
writeFile = True
|
||||||
|
else:
|
||||||
|
print "Playlist:", playlistname
|
||||||
|
filename = newplaylist
|
||||||
|
filenumber = 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(line) < 1 or writeFile:
|
||||||
|
if len(filename) < 1 and len(url) > 0:
|
||||||
|
filename = createFileName(title,url)
|
||||||
|
if len(filename) > 0 and len(m3u_output) > 0:
|
||||||
|
createM3uFile(filename,m3u_output,filenumber-1)
|
||||||
|
filenumber = 1
|
||||||
|
m3u_output = []
|
||||||
|
filename = ''
|
||||||
|
url = ''
|
||||||
|
|
||||||
|
if len(newplaylist) > 0:
|
||||||
|
filename = newplaylist
|
||||||
|
continue
|
||||||
|
|
||||||
|
if writeFile and len(line) > 0:
|
||||||
|
writeFile = False
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# Check start of title defined
|
||||||
|
elif line[:1] != '[':
|
||||||
|
stderr("Error: Missing left bracket [ in line %s in %s\n" % (lineCount,StationList))
|
||||||
|
format()
|
||||||
|
errorCount += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
processedCount += 1
|
||||||
|
line = line.lstrip('[')
|
||||||
|
|
||||||
|
# Get title and URL parts
|
||||||
|
line = line.strip()
|
||||||
|
lineparts = line.split(']')
|
||||||
|
|
||||||
|
# Should be 2 parts (title and url)
|
||||||
|
if len(lineparts) != 2:
|
||||||
|
stderr("Error: Missing right bracket [ in line %s in %s\n" % (lineCount,StationList))
|
||||||
|
format()
|
||||||
|
errorCount += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get title and URL from station definition
|
||||||
|
title = lineparts[0].lstrip()
|
||||||
|
url = lineparts[1].lstrip()
|
||||||
|
|
||||||
|
# Get the published URL and determine its type
|
||||||
|
print 'Processing line ' + str(lineCount) + ': ' + url
|
||||||
|
|
||||||
|
# Extended M3U (MPEG 3 URL) format
|
||||||
|
if url.endswith('.m3u'):
|
||||||
|
isM3U = True
|
||||||
|
|
||||||
|
# Advanced Stream Redirector (ASX)
|
||||||
|
elif url.endswith('.asx'):
|
||||||
|
isASX = True
|
||||||
|
|
||||||
|
# Playlist format
|
||||||
|
elif url.endswith('.pls'):
|
||||||
|
isPLS = True
|
||||||
|
|
||||||
|
# Advanced Audio Coding stream (Don't retrieve any URL)
|
||||||
|
else:
|
||||||
|
# Remove redundant (stream) parameter
|
||||||
|
url = url.replace('(stream)', '')
|
||||||
|
m3u_output += parseDirect(title,url,filenumber)
|
||||||
|
if len(filename) < 1:
|
||||||
|
filename = createFileName(title,url)
|
||||||
|
writeFile = True
|
||||||
|
filenumber += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the published URL to the stream file
|
||||||
|
try:
|
||||||
|
file = urllib2.urlopen(url)
|
||||||
|
data = file.read()
|
||||||
|
file.close()
|
||||||
|
# Creat list from data
|
||||||
|
lines = data.split('\n')
|
||||||
|
firstline = lines[0].rstrip()
|
||||||
|
except:
|
||||||
|
print "Error: Failed to retrieve ",title, url
|
||||||
|
errorCount += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# process lines accoording to URL type
|
||||||
|
if isPLS:
|
||||||
|
m3u_output += parsePls(title,url,lines,filenumber)
|
||||||
|
elif isM3U:
|
||||||
|
m3u_output += parseM3u(title,lines,filenumber)
|
||||||
|
elif isASX:
|
||||||
|
if firstline.startswith('<ASX'):
|
||||||
|
m3u_output += parseAsx(title,url,lines,filenumber)
|
||||||
|
else:
|
||||||
|
print url,"didn't return XML data"
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(filename) < 1:
|
||||||
|
filename = createFileName(title,url)
|
||||||
|
writeFile = True
|
||||||
|
|
||||||
|
filenumber += 1
|
||||||
|
|
||||||
|
|
||||||
|
# End of for line
|
||||||
|
|
||||||
|
# Write last file
|
||||||
|
if len(filename) < 1:
|
||||||
|
filename = createFileName(title,url)
|
||||||
|
if len(filename) > 0 and len(m3u_output) > 0:
|
||||||
|
createM3uFile(filename,m3u_output,filenumber-1)
|
||||||
|
|
||||||
|
print ("Processed %s station URLs from %s" % (processedCount,StationList))
|
||||||
|
|
||||||
|
# Copy files from temporary directory to playlist directory
|
||||||
|
oldfiles = len(os.listdir(PlsDirectory))
|
||||||
|
if oldfiles > 0:
|
||||||
|
if not deleteOld and not noDelete:
|
||||||
|
stderr("There are %s old playlist files in the %s directory.\n" % (oldfiles-2,PlsDirectory))
|
||||||
|
stderr("Do you wish to remove the old files y/n: ")
|
||||||
|
answer = raw_input("")
|
||||||
|
if answer == 'y':
|
||||||
|
deleteOld = True
|
||||||
|
|
||||||
|
if deleteOld:
|
||||||
|
stderr ("\nRemoving old playlists from directory %s\n" % PlsDirectory)
|
||||||
|
execCommand ("rm -f " + PlsDirectory + "*.pls" )
|
||||||
|
execCommand ("rm -f " + PlsDirectory + "*.m3u" )
|
||||||
|
else:
|
||||||
|
print "Old playlist files not removed"
|
||||||
|
|
||||||
|
copiedCount = len(os.listdir(TempDir))
|
||||||
|
print "Copying %s new playlist files to directory %s" % (copiedCount,PlsDirectory)
|
||||||
|
execCommand ("cp -f " + TempDir + '* ' + PlsDirectory )
|
||||||
|
|
||||||
|
# Create summary report
|
||||||
|
print "\nNew radio playlist files will be found in " + PlsDirectory
|
||||||
|
|
||||||
|
if errorCount > 0:
|
||||||
|
print str(errorCount) + " error(s)"
|
||||||
|
|
||||||
|
if duplicateCount > 0:
|
||||||
|
print str(duplicateCount) + " duplicate file name(s) found and renamed."
|
||||||
|
|
||||||
|
warningCount += duplicateCount
|
||||||
|
if warningCount > 0:
|
||||||
|
print str(warningCount) + " warning(s)"
|
||||||
|
|
||||||
|
# End of script
|
||||||
|
|
||||||
Executable
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi Internet Radio Configuration dislay
|
||||||
|
# $Id: display_config.py,v 1.3 2016/06/19 12:23:53 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
import commands
|
||||||
|
from time import strftime
|
||||||
|
from radio_class import Radio
|
||||||
|
|
||||||
|
# System files
|
||||||
|
RadioLibDir = "/var/lib/radiod"
|
||||||
|
CurrentStationFile = RadioLibDir + "/current_station"
|
||||||
|
CurrentTrackFile = RadioLibDir + "/current_track"
|
||||||
|
VolumeFile = RadioLibDir + "/volume"
|
||||||
|
TimerFile = RadioLibDir + "/timer"
|
||||||
|
AlarmFile = RadioLibDir + "/alarm"
|
||||||
|
StreamFile = RadioLibDir + "/streaming"
|
||||||
|
LogLevelFile=RadioLibDir + "/loglevel"
|
||||||
|
RssFile = "/var/lib/radiod/rss"
|
||||||
|
|
||||||
|
alarmType = ["off", "on", "repeat", "weekdays"]
|
||||||
|
|
||||||
|
radio = Radio()
|
||||||
|
|
||||||
|
# Exec system command
|
||||||
|
def execCommand(cmd):
|
||||||
|
return commands.getoutput(cmd)
|
||||||
|
|
||||||
|
# Display flags
|
||||||
|
def displayConfig(radio):
|
||||||
|
todaysdate = strftime("%H:%M %d/%m/%Y")
|
||||||
|
print "Radio Configuration " + todaysdate
|
||||||
|
print "Volume " + str(radio.getStoredVolume())
|
||||||
|
print "Timer " + str(radio.getStoredTimer())
|
||||||
|
AlarmString = radio.getStoredAlarm()
|
||||||
|
print "Alarm " + AlarmString
|
||||||
|
(sType,sHours,sMins) = AlarmString.split(':')
|
||||||
|
iAlarmType = int(sType)
|
||||||
|
print "Alarm " + alarmType[iAlarmType] + " [" + str(iAlarmType) + "]"
|
||||||
|
print "Streaming " + execCommand( "cat " + StreamFile) + " (" + str(radio.getStoredStreaming()) + ")"
|
||||||
|
print "Current station " + execCommand( "cat " + CurrentStationFile)
|
||||||
|
print "Current track " + execCommand( "cat " + CurrentTrackFile)
|
||||||
|
print "Log level " + execCommand( "cat " + LogLevelFile)
|
||||||
|
print "RSS " + execCommand( "cat " + RssFile)
|
||||||
|
print "\nMPC status"
|
||||||
|
print execCommand("mpc status")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Main routine
|
||||||
|
displayConfig(radio)
|
||||||
|
|
||||||
|
# End of script
|
||||||
Executable
+53
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Diagnostic to Raspberry Pi Display current stream using MPD library
|
||||||
|
# $Id: display_current.py,v 1.7 2015/01/21 19:44:18 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This program uses Music Player Daemon 'mpd' and the python-mpd library
|
||||||
|
# Use "apt-get install python-mpd" to install the Python MPD library
|
||||||
|
# See http://mpd.wikia.com/wiki/Music_Player_Daemon_Wiki
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
from mpd import MPDClient
|
||||||
|
client = MPDClient() # Create the MPD client
|
||||||
|
|
||||||
|
client.timeout = 10
|
||||||
|
client.idletimeout = None
|
||||||
|
client.connect("localhost", 6600)
|
||||||
|
|
||||||
|
currentsong = client.currentsong()
|
||||||
|
|
||||||
|
print ""
|
||||||
|
if len(currentsong) > 0:
|
||||||
|
for text in currentsong:
|
||||||
|
print text + ": " + str(currentsong.get(text))
|
||||||
|
|
||||||
|
current_id = int(currentsong.get("pos")) + 1
|
||||||
|
print "current_id", current_id
|
||||||
|
else:
|
||||||
|
print "No current song"
|
||||||
|
|
||||||
|
print ""
|
||||||
|
print "Status"
|
||||||
|
status = client.status()
|
||||||
|
for text in status:
|
||||||
|
print text + ": " + str(status.get(text))
|
||||||
|
|
||||||
|
print ""
|
||||||
|
stats = client.stats()
|
||||||
|
for text in stats:
|
||||||
|
print text + ": " + str(stats.get(text))
|
||||||
|
|
||||||
|
print "Bit rate", status.get('bitrate')
|
||||||
|
|
||||||
|
# End of program
|
||||||
|
|
||||||
|
|
||||||
Executable
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
$Id: display_model.py,v 1.7 2016/03/03 16:29:06 bob Exp $
|
||||||
|
|
||||||
|
Author: Chris Hager <chris@linuxuser.at>
|
||||||
|
License: MIT
|
||||||
|
URL: https://github.com/metachris/raspberrypi-utils
|
||||||
|
|
||||||
|
Modified by: Bob Rathbone (bob@bobrathbone.com)
|
||||||
|
Site : http://www.bobrathbone.com
|
||||||
|
|
||||||
|
License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
The authors shall not be liable for any loss or damage however caused.
|
||||||
|
|
||||||
|
This script detects a Raspberry Pi's model, manufacturer and mb ram, based
|
||||||
|
on the cpu revision number. Data source:
|
||||||
|
http://www.raspberrypi.org/phpBB3/viewtopic.php?f=63&t=32733
|
||||||
|
|
||||||
|
You can instantiate the ModelInfo class either with a parameter `rev_hex`
|
||||||
|
(eg. `m = ModelInfo("000f")`), or without a parameter
|
||||||
|
(eg. `m = ModelInfo()`) in which case it will try to detect it via
|
||||||
|
`/proc/cpuinfo`. Accessible attributes:
|
||||||
|
|
||||||
|
|
||||||
|
class ModelInfo:
|
||||||
|
model = '' # 'A' or 'B'
|
||||||
|
revision = '' # '1.0' or '2.0'
|
||||||
|
ram_mb = 0 # integer value representing ram in mb
|
||||||
|
maker = '' # manufacturer (eg. 'Qisda')
|
||||||
|
info = '' # additional info (eg. 'D14' removed)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
model_data = {
|
||||||
|
'2': ('B', '1.0', 256, 'Cambridge', ''),
|
||||||
|
'3': ('B', '1.0', 256, 'Cambridge', 'Fuses mod and D14 removed'),
|
||||||
|
'4': ('B', '2.0', 256, 'Sony', ''),
|
||||||
|
'5': ('B', '2.0', 256, 'Qisda', ''),
|
||||||
|
'6': ('B', '2.0', 256, 'Egoman', ''),
|
||||||
|
'7': ('A', '2.0', 256, 'Egoman', ''),
|
||||||
|
'8': ('A', '2.0', 256, 'Sony', ''),
|
||||||
|
'9': ('A', '2.0', 256, 'Qisda', ''),
|
||||||
|
'd': ('B', '2.0', 512, 'Egoman', ''),
|
||||||
|
'e': ('B', '2.0', 512, 'Sony', ''),
|
||||||
|
'f': ('B', '2.0', 512, 'Qisda', ''),
|
||||||
|
'10': ('B+', '2.0', 512, 'Unknown', ''),
|
||||||
|
'a01041': ('2B', '2.0', 512, 'Farnell and others', ''),
|
||||||
|
'900092': ('Pi Zero', '2.0', 1000, 'Element14', ''),
|
||||||
|
'a02082': ('3B', '2.0', 1000, 'Element14', ''),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ModelInfo(object):
|
||||||
|
"""
|
||||||
|
You can instantiate ModelInfo either with a parameter `rev_hex`
|
||||||
|
(eg. `m = ModelInfo("000f")`), or without a parameter
|
||||||
|
(eg. `m = ModelInfo()`) in which case it will try to detect it via
|
||||||
|
`/proc/cpuinfo`
|
||||||
|
"""
|
||||||
|
model = ''
|
||||||
|
revision = ''
|
||||||
|
ram_mb = 0
|
||||||
|
maker = ''
|
||||||
|
info = ''
|
||||||
|
|
||||||
|
def __init__(self, rev_hex=None):
|
||||||
|
if not rev_hex:
|
||||||
|
with open("/proc/cpuinfo") as f:
|
||||||
|
cpuinfo = f.read()
|
||||||
|
rev_hex = re.search(r"(?<=\nRevision)[ |:|\t]*(\w+)", cpuinfo) \
|
||||||
|
.group(1)
|
||||||
|
|
||||||
|
self.revision_hex = rev_hex[-4:] if rev_hex[:4] == "1000" else rev_hex
|
||||||
|
try:
|
||||||
|
self.model, self.revision, self.ram_mb, self.maker, self.info = \
|
||||||
|
model_data[rev_hex.lstrip("0")]
|
||||||
|
except:
|
||||||
|
print "Unknown model", rev_hex.lstrip("0")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
s = "%s: Model %s, Revision %s, RAM: %s MB, Maker: %s%s" % ( \
|
||||||
|
self.revision_hex, self.model, self.revision, self.ram_mb, \
|
||||||
|
self.maker, ", %s" % self.info if self.info else "")
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
m = ModelInfo()
|
||||||
|
print(m)
|
||||||
|
|
||||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1017 B |
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
$msg = $_GET['key'];
|
||||||
|
if (isset($msg)) {
|
||||||
|
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
||||||
|
$len = strlen($msg);
|
||||||
|
socket_sendto($sock, $msg, $len, 0, 'localhost', 5100);
|
||||||
|
socket_close($sock);
|
||||||
|
}
|
||||||
|
?>
|
||||||
Executable
+75
@@ -0,0 +1,75 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="shortcut icon" href="/PiRadio16.gif" />
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<title>PiRadio mini web interface - config page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>PiRadio</b><div id="config"><a href="index.html">radio</a></div></br>
|
||||||
|
<hr>
|
||||||
|
<?php
|
||||||
|
$msg = $_GET['file'];
|
||||||
|
if (isset($msg)) {
|
||||||
|
if ($msg == "restart") {
|
||||||
|
$end = "xxx";
|
||||||
|
} elseif ($msg == "stations") {
|
||||||
|
echo "Not implemented...";
|
||||||
|
} elseif ($msg == "network") {
|
||||||
|
echo "Not implemented...";
|
||||||
|
} elseif ($msg == "rss") {
|
||||||
|
$rss_link = $_POST["rss_link"];
|
||||||
|
file_put_contents('/var/lib/radiod/rss', $rss_link);
|
||||||
|
chmod("/var/lib/radiod/rss", 0755);
|
||||||
|
$rss_link_new = file_get_contents( "/var/lib/radiod/rss" );
|
||||||
|
echo "New RSS config:<br>";
|
||||||
|
echo "<b>".$rss_link_new."</b>";
|
||||||
|
} elseif ($msg == "radio") {
|
||||||
|
$rss = (isset($_POST['rss'])) ? "rss=yes" : "rss=no";
|
||||||
|
$bright = (isset($_POST['bright'])) ? "bright=yes" : "bright=no";
|
||||||
|
$media_update = (isset($_POST['media_update'])) ? "media_update=yes" : "media_update=no";
|
||||||
|
$pandora_available = (isset($_POST['pandora_available'])) ? "pandora_available=yes" : "pandora_available=no";
|
||||||
|
$piradio = file_get_contents( "/etc/radiod.conf" );
|
||||||
|
$piradio_new = preg_replace("/\nrss *= *.*/", "\n".$rss, $piradio);
|
||||||
|
$piradio_new = preg_replace("/\nbright *= *.*/", "\n".$bright, $piradio_new);
|
||||||
|
$piradio_new = preg_replace("/\nmedia_update *= *.*/", "\n".$media_update, $piradio_new);
|
||||||
|
$piradio_new = preg_replace("/\npandora_available *= *.*/", "\n".$pandora_available, $piradio_new);
|
||||||
|
$piradio_array = parse_ini_string($piradio_new);
|
||||||
|
$rss = ($piradio_array['rss']) ? "yes" : "no";
|
||||||
|
$bright = ($piradio_array['bright']) ? "yes" : "no";
|
||||||
|
$media_update = ($piradio_array['media_update']) ? "yes" : "no";
|
||||||
|
$pandora_available = ($piradio_array['pandora_available']) ? "yes" : "no";
|
||||||
|
echo "New Global PiRadio config:<br>";
|
||||||
|
echo "<b>";
|
||||||
|
echo "RSS in standby: ".$rss."<br>";
|
||||||
|
echo "LCD high brightness: ".$bright."<br>";
|
||||||
|
echo "Always update library: ".$media_update."<br>";
|
||||||
|
echo "Pandora available: ".$pandora_available."<br>";
|
||||||
|
file_put_contents('/etc/radiod.conf', $piradio_new);
|
||||||
|
chmod("/etc/radiod.conf", 0755);
|
||||||
|
} elseif ($msg == "pandora") {
|
||||||
|
$login = 'user = '.$_POST["login"];
|
||||||
|
$password = 'password = '.$_POST["password"];
|
||||||
|
$proxy = 'control_proxy = '.$_POST["proxy"];
|
||||||
|
/* Folder /home/pi/.config/ musi miec uprawnienia 755
|
||||||
|
inaczej nie da sie stad odczytac plik w nim umieszczony */
|
||||||
|
$pandora = file_get_contents( "/home/pi/.config/pianobar/config" );
|
||||||
|
$pandora_new = preg_replace("/\nuser *= *.*/", "\n".$login, $pandora);
|
||||||
|
$pandora_new = preg_replace("/\npassword *= *.*/", "\n".$password, $pandora_new);
|
||||||
|
$pandora_new = preg_replace("/\ncontrol_proxy *= *.*/", "\n".$proxy, $pandora_new);
|
||||||
|
$pandora_array = parse_ini_string($pandora_new);
|
||||||
|
echo "New Pandora config:<br>";
|
||||||
|
echo "<b>";
|
||||||
|
echo "Login: ".$pandora_array['user']."<br>";
|
||||||
|
echo "Password: ".$pandora_array['password']."<br>";
|
||||||
|
echo "Proxy: ".$pandora_array['control_proxy']."</b><br>";
|
||||||
|
file_put_contents('/home/pi/.config/pianobar/config', $pandora_new);
|
||||||
|
chmod("/home/pi/.config/pianobar/config", 0755);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<hr>
|
||||||
|
<a href="config.php?command=restart"><button>PiRadio restart</button></a>
|
||||||
|
<a href="config.php?command=reboot"><button>System reboot</button></a>
|
||||||
|
</body>
|
||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
$msg = $_GET['command'];
|
||||||
|
if (isset($msg)) {
|
||||||
|
if ($msg == "restart") {
|
||||||
|
$end = shell_exec('sudo ./scripts/restart.sh');
|
||||||
|
} elseif ($msg == "reboot") {
|
||||||
|
$end = shell_exec('sudo ./scripts/reboot.sh');
|
||||||
|
/* UWAGA! Kazdy skrypt uruchamiany przez sudo
|
||||||
|
musi byc dopisany w pliku /etc/sudoers */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="shortcut icon" href="/PiRadio16.gif" />
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<title>PiRadio mini web interface - config page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>PiRadio</b><div id="config"><a href="index.html">radio</a></div></br>
|
||||||
|
<hr>
|
||||||
|
Global PiRadio config<br>
|
||||||
|
<form action="changeconf.php?file=radio" method="post">
|
||||||
|
<pre>
|
||||||
|
<?php
|
||||||
|
$piradio = file_get_contents( "/etc/radiod.conf" );
|
||||||
|
$piradio_array = parse_ini_string($piradio);
|
||||||
|
echo "<b>";
|
||||||
|
echo '<input type="checkbox" name="rss" value="rss" ';
|
||||||
|
if ( $piradio_array['rss'] ) {
|
||||||
|
echo 'checked>';
|
||||||
|
} else {
|
||||||
|
echo '>';
|
||||||
|
}
|
||||||
|
echo ' RRS in standby.<br>';
|
||||||
|
echo '<input type="checkbox" name="bright" value="bright" ';
|
||||||
|
if ( $piradio_array['bright'] ) {
|
||||||
|
echo 'checked>';
|
||||||
|
} else {
|
||||||
|
echo '>';
|
||||||
|
}
|
||||||
|
echo ' LCD high brightness.<br>';
|
||||||
|
echo '<input type="checkbox" name="media_update" value="media_update" ';
|
||||||
|
if ( $piradio_array['media_update'] ) {
|
||||||
|
echo 'checked>';
|
||||||
|
} else {
|
||||||
|
echo '>';
|
||||||
|
}
|
||||||
|
echo ' Always update library.<br>';
|
||||||
|
echo '<input type="checkbox" name="pandora_available" value="pandora_available" ';
|
||||||
|
if ( $piradio_array['pandora_available'] ) {
|
||||||
|
echo 'checked>';
|
||||||
|
} else {
|
||||||
|
echo '>';
|
||||||
|
}
|
||||||
|
echo ' Pandora available.';
|
||||||
|
echo '</b><br>';
|
||||||
|
?>
|
||||||
|
<button type="submit" name="submit">Save Global PiRadio config</button>
|
||||||
|
</pre>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
Media network folder config
|
||||||
|
<form action="changeconf.php?file=network" method="post">
|
||||||
|
<?php
|
||||||
|
/* Folder /var/lib/radiod/ musi miec uprawnienia 755
|
||||||
|
inaczej nie da sie stad odczytac plik w nim umieszczony */
|
||||||
|
$netshare = file_get_contents( "/var/lib/radiod/share" );
|
||||||
|
echo "<input type='text' name='media' value='";
|
||||||
|
echo $netshare;
|
||||||
|
echo "' style='width: 100%'>";
|
||||||
|
?>
|
||||||
|
<br><button type="submit" name="submit">Save network folder config</button>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
RSS config
|
||||||
|
<form action="changeconf.php?file=rss" method="post">
|
||||||
|
<?php
|
||||||
|
/* Folder /var/lib/radiod/ musi miec uprawnienia 755
|
||||||
|
inaczej nie da sie stad odczytac plik w nim umieszczony */
|
||||||
|
$rss = file_get_contents( "/var/lib/radiod/rss" );
|
||||||
|
echo "<input type='text' name='rss_link' value='";
|
||||||
|
echo $rss;
|
||||||
|
echo "' style='width: 100%'>";
|
||||||
|
?>
|
||||||
|
<br><button type="submit" name="submit">Save RSS config</button>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
Pandora config
|
||||||
|
<form action="changeconf.php?file=pandora" method="post"><pre>
|
||||||
|
<?php
|
||||||
|
/* Folder /home/pi/.config/ musi miec uprawnienia 755
|
||||||
|
inaczej nie da sie stad odczytac plik w nim umieszczony */
|
||||||
|
$pandora = file_get_contents( "/home/pi/.config/pianobar/config" );
|
||||||
|
$pandora_array = parse_ini_string($pandora);
|
||||||
|
/* print_r($pandora_array); */
|
||||||
|
echo "<b>";
|
||||||
|
echo "Login: <input type='text' name='login' value='".$pandora_array['user']."'><br>";
|
||||||
|
echo "Password: <input type='text' name='password' value='".$pandora_array['password']."'><br>";
|
||||||
|
echo "Proxy: <input type='text' name='proxy' value='".$pandora_array['control_proxy']."'></b><br>";
|
||||||
|
?>
|
||||||
|
<button type="submit" name="submit">Save Pandora config</button>
|
||||||
|
</pre></form>
|
||||||
|
<hr>
|
||||||
|
Radio stations config
|
||||||
|
<form action="changeconf.php?file=stations" method="post">
|
||||||
|
<?php
|
||||||
|
/* Folder /var/lib/radiod/ musi miec uprawnienia 755
|
||||||
|
inaczej nie da sie stad odczytac plik w nim umieszczony */
|
||||||
|
$stations = file_get_contents( "/var/lib/radiod/stationlist" );
|
||||||
|
echo '<textarea rows="20" cols="80" name="stations">';
|
||||||
|
echo $stations;
|
||||||
|
echo '</textarea>';
|
||||||
|
?>
|
||||||
|
<br><button type="submit" name="submit">Save radio stations list</button>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
|
<a href="config.php?command=restart"><button>PiRadio restart</button></a>
|
||||||
|
<a href="config.php?command=reboot"><button>System reboot</button></a>
|
||||||
|
</body>
|
||||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1017 B |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Executable
+112
@@ -0,0 +1,112 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="shortcut icon" href="/PiRadio16.gif" />
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<title>PiRadio mini web interface</title>
|
||||||
|
<script src="jquery-1.12.4.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("#wyswietlacz").load("lcd.php");
|
||||||
|
var refreshId = setInterval(function() {
|
||||||
|
$("#wyswietlacz").load("lcd.php");
|
||||||
|
}, 1000);
|
||||||
|
$.ajaxSetup({ cache: false });
|
||||||
|
$("#player").load("player.php");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>PiRadio</b><div id="config"><a href="config.php">config</a></div><hr>
|
||||||
|
<div id="wyswietlacz">
|
||||||
|
<br><br><br><br>
|
||||||
|
</div>
|
||||||
|
<div id="kursory">
|
||||||
|
<button id="UPbutton">^</button><br>
|
||||||
|
<button id="LEFTbutton"><</button>
|
||||||
|
<button id="OKbutton">OK</button>
|
||||||
|
<button id="RIGHTbutton">></button><br>
|
||||||
|
<button id="DOWNbutton">v</button>
|
||||||
|
</div>
|
||||||
|
<div id="lewebuttony">
|
||||||
|
<button id="SLEEPbutton"> SLEEP </button>
|
||||||
|
<button id="WAKEUPbutton">WAKEUP</button>
|
||||||
|
<br>
|
||||||
|
<button id="CHDOWNbutton">|<< PREV</button>
|
||||||
|
<button id="CHUPbutton">NEXT >>|</button>
|
||||||
|
<br>
|
||||||
|
<button id="RADIObutton"> RADIO </button>
|
||||||
|
<button id="MEDIAbutton">PLAYER</button>
|
||||||
|
<button id="PANDORAbutton">PANDORA</button>
|
||||||
|
</div>
|
||||||
|
<div id="nadole">
|
||||||
|
<div id="player">
|
||||||
|
</div>
|
||||||
|
<button id="VDOWNbutton">VOL-</button>
|
||||||
|
<button id="VUPbutton">VOL+</button>
|
||||||
|
<button id="MUTEbutton">MUTE</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$("#OKbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_OK");
|
||||||
|
});
|
||||||
|
$("#UPbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_UP");
|
||||||
|
});
|
||||||
|
$("#DOWNbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_DOWN");
|
||||||
|
});
|
||||||
|
$("#LEFTbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_LEFT");
|
||||||
|
});
|
||||||
|
$("#RIGHTbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_RIGHT");
|
||||||
|
});
|
||||||
|
$("#SLEEPbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_SLEEP");
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#player").load("player.php");
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
$("#WAKEUPbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_WAKEUP");
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#player").load("player.php");
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
$("#CHUPbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_CHANNELUP");
|
||||||
|
});
|
||||||
|
$("#CHDOWNbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_CHANNELDOWN");
|
||||||
|
});
|
||||||
|
$("#VUPbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_VOLUMEUP");
|
||||||
|
});
|
||||||
|
$("#VDOWNbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_VOLUMEDOWN");
|
||||||
|
});
|
||||||
|
$("#MUTEbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_MUTE");
|
||||||
|
});
|
||||||
|
$("#RADIObutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_RADIO");
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#player").load("player.php");
|
||||||
|
}, 4000);
|
||||||
|
});
|
||||||
|
$("#MEDIAbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_MEDIA");
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#player").load("player.php");
|
||||||
|
}, 5500);
|
||||||
|
});
|
||||||
|
$("#PANDORAbutton").click(function() {
|
||||||
|
$.post("button.php?key=KEY_PANDORA");
|
||||||
|
setTimeout(function(){
|
||||||
|
$("#player").load("player.php");
|
||||||
|
}, 5500);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
<title>Apache2 Debian Default Page: It works</title>
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
* {
|
||||||
|
margin: 0px 0px 0px 0px;
|
||||||
|
padding: 0px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, html {
|
||||||
|
padding: 3px 3px 3px 3px;
|
||||||
|
|
||||||
|
background-color: #D8DBE2;
|
||||||
|
|
||||||
|
font-family: Verdana, sans-serif;
|
||||||
|
font-size: 11pt;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.main_page {
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
|
||||||
|
width: 800px;
|
||||||
|
|
||||||
|
margin-bottom: 3px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 0px 0px 0px 0px;
|
||||||
|
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: #212738;
|
||||||
|
border-style: solid;
|
||||||
|
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.page_header {
|
||||||
|
height: 99px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
background-color: #F5F6F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.page_header span {
|
||||||
|
margin: 15px 0px 0px 50px;
|
||||||
|
|
||||||
|
font-size: 180%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.page_header img {
|
||||||
|
margin: 3px 0px 0px 40px;
|
||||||
|
|
||||||
|
border: 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table_of_contents {
|
||||||
|
clear: left;
|
||||||
|
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
margin: 3px 3px 3px 3px;
|
||||||
|
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table_of_contents_item {
|
||||||
|
clear: left;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin: 4px 0px 0px 0px;
|
||||||
|
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
|
||||||
|
color: #000000;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table_of_contents_item a {
|
||||||
|
margin: 6px 0px 0px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section {
|
||||||
|
margin: 3px 3px 3px 3px;
|
||||||
|
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section_text {
|
||||||
|
padding: 4px 8px 4px 8px;
|
||||||
|
|
||||||
|
color: #000000;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section_text pre {
|
||||||
|
margin: 8px 0px 8px 0px;
|
||||||
|
padding: 8px 8px 8px 8px;
|
||||||
|
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: dotted;
|
||||||
|
border-color: #000000;
|
||||||
|
|
||||||
|
background-color: #F5F6F7;
|
||||||
|
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section_text p {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section_text ul, div.content_section_text li {
|
||||||
|
padding: 4px 8px 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.section_header {
|
||||||
|
padding: 3px 6px 3px 6px;
|
||||||
|
|
||||||
|
background-color: #8E9CB2;
|
||||||
|
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 112%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.section_header_red {
|
||||||
|
background-color: #CD214F;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.section_header_grey {
|
||||||
|
background-color: #9F9386;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating_element {
|
||||||
|
position: relative;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table_of_contents_item a,
|
||||||
|
div.content_section_text a {
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table_of_contents_item a:link,
|
||||||
|
div.table_of_contents_item a:visited,
|
||||||
|
div.table_of_contents_item a:active {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.table_of_contents_item a:hover {
|
||||||
|
background-color: #000000;
|
||||||
|
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section_text a:link,
|
||||||
|
div.content_section_text a:visited,
|
||||||
|
div.content_section_text a:active {
|
||||||
|
background-color: #DCDFE6;
|
||||||
|
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content_section_text a:hover {
|
||||||
|
background-color: #000000;
|
||||||
|
|
||||||
|
color: #DCDFE6;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.validator {
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main_page">
|
||||||
|
<div class="page_header floating_element">
|
||||||
|
<img src="/icons/openlogo-75.png" alt="Debian Logo" class="floating_element"/>
|
||||||
|
<span class="floating_element">
|
||||||
|
Apache2 Debian Default Page
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="table_of_contents floating_element">
|
||||||
|
<div class="section_header section_header_grey">
|
||||||
|
TABLE OF CONTENTS
|
||||||
|
</div>
|
||||||
|
<div class="table_of_contents_item floating_element">
|
||||||
|
<a href="#about">About</a>
|
||||||
|
</div>
|
||||||
|
<div class="table_of_contents_item floating_element">
|
||||||
|
<a href="#changes">Changes</a>
|
||||||
|
</div>
|
||||||
|
<div class="table_of_contents_item floating_element">
|
||||||
|
<a href="#scope">Scope</a>
|
||||||
|
</div>
|
||||||
|
<div class="table_of_contents_item floating_element">
|
||||||
|
<a href="#files">Config files</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
<div class="content_section floating_element">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="section_header section_header_red">
|
||||||
|
<div id="about"></div>
|
||||||
|
It works!
|
||||||
|
</div>
|
||||||
|
<div class="content_section_text">
|
||||||
|
<p>
|
||||||
|
This is the default welcome page used to test the correct
|
||||||
|
operation of the Apache2 server after installation on Debian systems.
|
||||||
|
If you can read this page, it means that the Apache HTTP server installed at
|
||||||
|
this site is working properly. You should <b>replace this file</b> (located at
|
||||||
|
<tt>/var/www/html/index.html</tt>) before continuing to operate your HTTP server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you are a normal user of this web site and don't know what this page is
|
||||||
|
about, this probably means that the site is currently unavailable due to
|
||||||
|
maintenance.
|
||||||
|
If the problem persists, please contact the site's administrator.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section_header">
|
||||||
|
<div id="changes"></div>
|
||||||
|
Configuration Overview
|
||||||
|
</div>
|
||||||
|
<div class="content_section_text">
|
||||||
|
<p>
|
||||||
|
Debian's Apache2 default configuration is different from the
|
||||||
|
upstream default configuration, and split into several files optimized for
|
||||||
|
interaction with Debian tools. The configuration system is
|
||||||
|
<b>fully documented in
|
||||||
|
/usr/share/doc/apache2/README.Debian.gz</b>. Refer to this for the full
|
||||||
|
documentation. Documentation for the web server itself can be
|
||||||
|
found by accessing the <a href="/manual">manual</a> if the <tt>apache2-doc</tt>
|
||||||
|
package was installed on this server.
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The configuration layout for an Apache2 web server installation on Debian systems is as follows:
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
/etc/apache2/
|
||||||
|
|-- apache2.conf
|
||||||
|
| `-- ports.conf
|
||||||
|
|-- mods-enabled
|
||||||
|
| |-- *.load
|
||||||
|
| `-- *.conf
|
||||||
|
|-- conf-enabled
|
||||||
|
| `-- *.conf
|
||||||
|
|-- sites-enabled
|
||||||
|
| `-- *.conf
|
||||||
|
</pre>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<tt>apache2.conf</tt> is the main configuration
|
||||||
|
file. It puts the pieces together by including all remaining configuration
|
||||||
|
files when starting up the web server.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<tt>ports.conf</tt> is always included from the
|
||||||
|
main configuration file. It is used to determine the listening ports for
|
||||||
|
incoming connections, and this file can be customized anytime.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
Configuration files in the <tt>mods-enabled/</tt>,
|
||||||
|
<tt>conf-enabled/</tt> and <tt>sites-enabled/</tt> directories contain
|
||||||
|
particular configuration snippets which manage modules, global configuration
|
||||||
|
fragments, or virtual host configurations, respectively.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
They are activated by symlinking available
|
||||||
|
configuration files from their respective
|
||||||
|
*-available/ counterparts. These should be managed
|
||||||
|
by using our helpers
|
||||||
|
<tt>
|
||||||
|
<a href="http://manpages.debian.org/cgi-bin/man.cgi?query=a2enmod">a2enmod</a>,
|
||||||
|
<a href="http://manpages.debian.org/cgi-bin/man.cgi?query=a2dismod">a2dismod</a>,
|
||||||
|
</tt>
|
||||||
|
<tt>
|
||||||
|
<a href="http://manpages.debian.org/cgi-bin/man.cgi?query=a2ensite">a2ensite</a>,
|
||||||
|
<a href="http://manpages.debian.org/cgi-bin/man.cgi?query=a2dissite">a2dissite</a>,
|
||||||
|
</tt>
|
||||||
|
and
|
||||||
|
<tt>
|
||||||
|
<a href="http://manpages.debian.org/cgi-bin/man.cgi?query=a2enconf">a2enconf</a>,
|
||||||
|
<a href="http://manpages.debian.org/cgi-bin/man.cgi?query=a2disconf">a2disconf</a>
|
||||||
|
</tt>. See their respective man pages for detailed information.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
The binary is called apache2. Due to the use of
|
||||||
|
environment variables, in the default configuration, apache2 needs to be
|
||||||
|
started/stopped with <tt>/etc/init.d/apache2</tt> or <tt>apache2ctl</tt>.
|
||||||
|
<b>Calling <tt>/usr/bin/apache2</tt> directly will not work</b> with the
|
||||||
|
default configuration.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section_header">
|
||||||
|
<div id="docroot"></div>
|
||||||
|
Document Roots
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content_section_text">
|
||||||
|
<p>
|
||||||
|
By default, Debian does not allow access through the web browser to
|
||||||
|
<em>any</em> file apart of those located in <tt>/var/www</tt>,
|
||||||
|
<a href="http://httpd.apache.org/docs/2.4/mod/mod_userdir.html">public_html</a>
|
||||||
|
directories (when enabled) and <tt>/usr/share</tt> (for web
|
||||||
|
applications). If your site is using a web document root
|
||||||
|
located elsewhere (such as in <tt>/srv</tt>) you may need to whitelist your
|
||||||
|
document root directory in <tt>/etc/apache2/apache2.conf</tt>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The default Debian document root is <tt>/var/www/html</tt>. You
|
||||||
|
can make your own virtual hosts under /var/www. This is different
|
||||||
|
to previous releases which provides better security out of the box.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section_header">
|
||||||
|
<div id="bugs"></div>
|
||||||
|
Reporting Problems
|
||||||
|
</div>
|
||||||
|
<div class="content_section_text">
|
||||||
|
<p>
|
||||||
|
Please use the <tt>reportbug</tt> tool to report bugs in the
|
||||||
|
Apache2 package with Debian. However, check <a
|
||||||
|
href="http://bugs.debian.org/cgi-bin/pkgreport.cgi?ordering=normal;archive=0;src=apache2;repeatmerged=0">existing
|
||||||
|
bug reports</a> before reporting a new bug.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please report bugs specific to modules (such as PHP and others)
|
||||||
|
to respective packages, not to the web server itself.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="validator">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
+5
File diff suppressed because one or more lines are too long
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
$line1 = file_get_contents( "/tmp/radiod/line1.txt" );
|
||||||
|
$line2 = file_get_contents( "/tmp/radiod/line2.txt" );
|
||||||
|
$line3 = file_get_contents( "/tmp/radiod/line3.txt" );
|
||||||
|
$line4 = file_get_contents( "/tmp/radiod/line4.txt" );
|
||||||
|
$line1 = str_replace(" "," ",$line1);
|
||||||
|
$line2 = str_replace(" "," ",$line2);
|
||||||
|
$line3 = str_replace(" "," ",$line3);
|
||||||
|
$line4 = str_replace(" "," ",$line4);
|
||||||
|
echo $line1;
|
||||||
|
echo "</br>";
|
||||||
|
echo $line2;
|
||||||
|
echo "</br>";
|
||||||
|
echo $line3;
|
||||||
|
echo "</br>";
|
||||||
|
echo $line4;
|
||||||
|
echo "</br>";
|
||||||
|
?>
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
$line1 = file_get_contents( "/tmp/radiod/line1.txt" );
|
||||||
|
$czyplayer = strpos($line1, "*");
|
||||||
|
if ($czyplayer != false) {
|
||||||
|
list($realHost,)=explode(':',$_SERVER['HTTP_HOST']);
|
||||||
|
echo '<audio controls><source src="http://';
|
||||||
|
echo $realHost;
|
||||||
|
echo ':8001/mpd" type="audio/mp3">Your browser does not support the audio element.</audio>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
reboot
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
service radiod restart
|
||||||
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
body {
|
||||||
|
background-color: lightgrey;
|
||||||
|
max-width: 800px;
|
||||||
|
align: center;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
#nadole {
|
||||||
|
position:fixed;
|
||||||
|
bottom:100px;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#nadole {
|
||||||
|
text-align: center;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
#wyswietlacz {
|
||||||
|
border: 1px solid white;
|
||||||
|
background-color: black;
|
||||||
|
color: yellow;
|
||||||
|
font-family: "Lucida Console", Monaco, monospace;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#lewebuttony {
|
||||||
|
/* float: left; */
|
||||||
|
text-align: center;
|
||||||
|
width: 200;
|
||||||
|
height: 150;
|
||||||
|
}
|
||||||
|
#kursory {
|
||||||
|
float: right;
|
||||||
|
text-align: center;
|
||||||
|
width: 150;
|
||||||
|
height: 125;
|
||||||
|
}
|
||||||
|
#config {
|
||||||
|
float: right;
|
||||||
|
text-align: center;
|
||||||
|
width: 60;
|
||||||
|
height: 20;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
text-decoration: bold;
|
||||||
|
}
|
||||||
|
#config a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: lightgrey;
|
||||||
|
color: black;
|
||||||
|
padding: 7px 12px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
border: 2px solid #555555;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 2px;
|
||||||
|
-webkit-transition-duration: 0.4s; /* Safari */
|
||||||
|
transition-duration: 0.4s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #555555; /* Dark Grey */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# English text for speech
|
||||||
|
main_display: Main display
|
||||||
|
search_menu: Search menu
|
||||||
|
select_source: Select source
|
||||||
|
options_menu: Options menu
|
||||||
|
rss_display: RSS display
|
||||||
|
information: Information display
|
||||||
|
the_time: The time is
|
||||||
|
loading_radio: Loading radio stations
|
||||||
|
loading_media: Loading media library
|
||||||
|
search: Search
|
||||||
|
source_radio: Internet Radio
|
||||||
|
source_media: Music library
|
||||||
|
sleeping: Sleeping
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# Hungarian text for speech
|
||||||
|
main_display: Fõ menü
|
||||||
|
search_menu: Keresés menü
|
||||||
|
select_source: Forrás kiválasztása
|
||||||
|
options_menu: Opciók menüje
|
||||||
|
rss_display: R S S megjelenítése
|
||||||
|
information: Információk kiírása
|
||||||
|
the_time: A pontos idõ
|
||||||
|
loading_radio: Rádió betöltése
|
||||||
|
loading_media: Média könyvtár betöltése
|
||||||
|
search: Keresés
|
||||||
|
source_radio: Internet Radió
|
||||||
|
source_media: Zenei könyvtár
|
||||||
|
sleeping: Alvás
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# Nederlands text for uitspraak
|
||||||
|
main_display: Hoofd menu
|
||||||
|
search_menu: Zoek menu
|
||||||
|
select_source: Media selecteren
|
||||||
|
options_menu: Opties menu
|
||||||
|
rss_display: RSS beeld
|
||||||
|
information: Informatie beeld
|
||||||
|
the_time: De tijd is
|
||||||
|
loading_radio: Radio zenders laden
|
||||||
|
loading_media: Media laden
|
||||||
|
search: Zoek
|
||||||
|
source_radio: Internet Radio
|
||||||
|
source_media: Muziek selectie
|
||||||
|
sleeping: Slaapen
|
||||||
Executable
+217
@@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi Internet Radio Class
|
||||||
|
# $Id: language_class.py,v 1.18 2016/05/24 15:03:19 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This class reads the /var/lib/radio/language file for both espeech and LCD display
|
||||||
|
# The format of this file is:
|
||||||
|
# <label>:<text>
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from log_class import Log
|
||||||
|
from translate_class import Translate
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
translate = Translate()
|
||||||
|
|
||||||
|
# System files
|
||||||
|
RadioLibDir = "/var/lib/radiod"
|
||||||
|
LanguageFile = RadioLibDir + "/language"
|
||||||
|
VoiceFile = RadioLibDir + "/voice"
|
||||||
|
|
||||||
|
class Language:
|
||||||
|
|
||||||
|
speech = False
|
||||||
|
|
||||||
|
# Speech text (Loaded from /var/lib/radio/language)
|
||||||
|
LanguageText = {
|
||||||
|
'main_display': 'Main display',
|
||||||
|
'search_menu': 'Search menu',
|
||||||
|
'select_source': 'Select source',
|
||||||
|
'options_menu': 'Options menu',
|
||||||
|
'rss_display': 'RSS display',
|
||||||
|
'information': 'Information display',
|
||||||
|
'the_time': 'The time is',
|
||||||
|
'loading_radio': 'Loading radio stations',
|
||||||
|
'loading_media': 'Loading media library',
|
||||||
|
'loading_pandora': 'Loading Pandora radio',
|
||||||
|
'search': 'Search',
|
||||||
|
'source_radio': 'Internet Radio',
|
||||||
|
'source_media': 'Music library',
|
||||||
|
'source_pandora': 'Pandora Radio',
|
||||||
|
'stopping_radio': 'Stopping radio',
|
||||||
|
'sleep': 'sleep',
|
||||||
|
'on': 'on',
|
||||||
|
'off': 'off',
|
||||||
|
'on': 'on',
|
||||||
|
'yes': 'yes',
|
||||||
|
'no': 'no',
|
||||||
|
'random': 'Random',
|
||||||
|
'consume': 'Consume',
|
||||||
|
'repeat': 'Repeat',
|
||||||
|
'reload': 'Reload',
|
||||||
|
'timer': 'Timer',
|
||||||
|
'alarm': 'Alarm',
|
||||||
|
'alarmhours': 'Alarm hours',
|
||||||
|
'alarmminutes': 'Alarm minutes',
|
||||||
|
'streaming': 'Streaming',
|
||||||
|
'colour': 'Colour',
|
||||||
|
'voice': 'voice',
|
||||||
|
'Track': 'Track', # (Pecus)
|
||||||
|
'Station': 'Station', # (Pecus)
|
||||||
|
'h00': '00', # (Pecus)
|
||||||
|
'h01': '01', # (Pecus)
|
||||||
|
'h02': '02', # (Pecus)
|
||||||
|
'h03': '03', # (Pecus)
|
||||||
|
'h04': '04', # (Pecus)
|
||||||
|
'h05': '05', # (Pecus)
|
||||||
|
'h06': '06', # (Pecus)
|
||||||
|
'h07': '07', # (Pecus)
|
||||||
|
'h08': '08', # (Pecus)
|
||||||
|
'h09': '09', # (Pecus)
|
||||||
|
'h10': '10', # (Pecus)
|
||||||
|
'h11': '11', # (Pecus)
|
||||||
|
'h12': '12', # (Pecus)
|
||||||
|
'h13': '13', # (Pecus)
|
||||||
|
'h14': '14', # (Pecus)
|
||||||
|
'h15': '15', # (Pecus)defaultStation
|
||||||
|
'h16': '16', # (Pecus)
|
||||||
|
'h17': '17', # (Pecus)
|
||||||
|
'h18': '18', # (Pecus)
|
||||||
|
'h19': '19', # (Pecus)
|
||||||
|
'h20': '20', # (Pecus)
|
||||||
|
'h21': '21', # (Pecus)
|
||||||
|
'h22': '22', # (Pecus)
|
||||||
|
'h23': '23', # (Pecus)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialisation routine - Load language
|
||||||
|
def __init__(self,speech = False):
|
||||||
|
log.init('radio')
|
||||||
|
self.speech = speech
|
||||||
|
self.load()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Load language text file
|
||||||
|
def load(self):
|
||||||
|
if os.path.isfile(LanguageFile):
|
||||||
|
try:
|
||||||
|
with open(LanguageFile) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith('#'):
|
||||||
|
continue
|
||||||
|
if len(line) < 1:
|
||||||
|
continue
|
||||||
|
line = line.rstrip()
|
||||||
|
param,value = line.split(':')
|
||||||
|
self.LanguageText[param] = str(value)
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.message("Error reading " + LanguageFile, log.ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the text by label
|
||||||
|
def getText(self,label):
|
||||||
|
text = ''
|
||||||
|
try:
|
||||||
|
text = self.LanguageText[label]
|
||||||
|
except:
|
||||||
|
log.message("language.getText Invalid label " + label, log.ERROR)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
# Get the menu text
|
||||||
|
def getMenuText(self):
|
||||||
|
menuText = []
|
||||||
|
sLabels = ['main_display','search_menu','select_source',
|
||||||
|
'options_menu','rss_display','information',
|
||||||
|
'sleep',
|
||||||
|
]
|
||||||
|
|
||||||
|
for label in sLabels:
|
||||||
|
menuText.append(self.getText(label))
|
||||||
|
|
||||||
|
return menuText
|
||||||
|
|
||||||
|
# Get the menu text
|
||||||
|
def getOptionText(self):
|
||||||
|
OptionText = []
|
||||||
|
sLabels = [ 'random','consume','repeat','reload','timer', 'alarm',
|
||||||
|
'alarmhours','alarmminutes','streaming','colour',
|
||||||
|
]
|
||||||
|
|
||||||
|
for label in sLabels:
|
||||||
|
OptionText.append(self.getText(label))
|
||||||
|
|
||||||
|
return OptionText
|
||||||
|
|
||||||
|
# Speak message
|
||||||
|
def speak(self,message,volume):
|
||||||
|
if os.path.isfile(VoiceFile): # and self.speech: # (Pecus)
|
||||||
|
try:
|
||||||
|
message = self.purgeChars(message)
|
||||||
|
cmd = self.execCommand("cat " + VoiceFile)
|
||||||
|
cmd = cmd + str(volume) + " --stdout | aplay"
|
||||||
|
cmd = "echo " + '"' + message + '"' + " | " + cmd + " >/dev/null 2>&1"
|
||||||
|
log.message(cmd, log.DEBUG)
|
||||||
|
|
||||||
|
# If the first character is ! then supress the message
|
||||||
|
if len(message) > 0 and message[0] != '!':
|
||||||
|
self.execCommand(cmd)
|
||||||
|
except:
|
||||||
|
log.message("Error reading " + VoiceFile, log.ERROR)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Remove problem charachters from speech text
|
||||||
|
def purgeChars(self,message):
|
||||||
|
chars = ['!',':','|','*','[',']',
|
||||||
|
'_','"','.']
|
||||||
|
|
||||||
|
# If the first character is ! then supress the message
|
||||||
|
message = message.lstrip()
|
||||||
|
|
||||||
|
if message[0] is '!':
|
||||||
|
supress = True
|
||||||
|
else:
|
||||||
|
supress = False
|
||||||
|
for char in chars:
|
||||||
|
message = message.replace(char,'')
|
||||||
|
|
||||||
|
message = message.replace('/',' ')
|
||||||
|
message = message.replace('-',',')
|
||||||
|
if supress:
|
||||||
|
message = '!' + message
|
||||||
|
return message
|
||||||
|
|
||||||
|
# Display text
|
||||||
|
def displayList(self):
|
||||||
|
for label in self.LanguageText:
|
||||||
|
text = self.LanguageText[label]
|
||||||
|
print label + ': ' + text
|
||||||
|
return
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def execCommand(self,cmd):
|
||||||
|
p = os.popen(cmd)
|
||||||
|
return p.readline().rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
|
# Test Language class
|
||||||
|
if __name__ == '__main__':
|
||||||
|
language = Language()
|
||||||
|
language.load()
|
||||||
|
language.displayList()
|
||||||
|
|
||||||
|
# End of class
|
||||||
Binary file not shown.
Executable
+359
@@ -0,0 +1,359 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: latin-1 -*-
|
||||||
|
#
|
||||||
|
# $Id: lcd_class.py,v 1.28 2016/07/23 13:21:28 bob Exp $
|
||||||
|
# Raspberry Pi Internet Radio
|
||||||
|
# using an HD44780 LCD display
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# From original LCD routines : Matt Hawkins
|
||||||
|
# Site : http://www.raspberrypi-spy.co.uk
|
||||||
|
# Timing improvements fromobert Coward/Paul Carpenter
|
||||||
|
# Site : http://www.raspberrypi-spy.co.uk
|
||||||
|
# http://www.pcserviceslectronics.co.uk
|
||||||
|
#
|
||||||
|
# Expanded to use 4 x 20 display
|
||||||
|
#
|
||||||
|
# This program uses Music Player Daemon 'mpd'and it's client 'mpc'
|
||||||
|
# See http://mpd.wikia.com/wiki/Music_Player_Daemon_Wiki
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
from translate_class import Translate
|
||||||
|
from config_class import Configuration
|
||||||
|
|
||||||
|
# The wiring for the LCD is as follows:
|
||||||
|
# 1 : GND
|
||||||
|
# 2 : 5V
|
||||||
|
# 3 : Contrast (0-5V)*
|
||||||
|
# 4 : RS (Register Select)
|
||||||
|
# 5 : R/W (Read Write) - GROUND THIS PIN
|
||||||
|
# 6 : Enable or Strobe
|
||||||
|
# 7 : Data Bit 0 - NOT USED
|
||||||
|
# 8 : Data Bit 1 - NOT USED
|
||||||
|
# 9 : Data Bit 2 - NOT USED
|
||||||
|
# 10: Data Bit 3 - NOT USED
|
||||||
|
# 11: Data Bit 4
|
||||||
|
# 12: Data Bit 5
|
||||||
|
# 13: Data Bit 6
|
||||||
|
# 14: Data Bit 7
|
||||||
|
# 15: LCD Backlight +5V**
|
||||||
|
# 16: LCD Backlight GND
|
||||||
|
|
||||||
|
# Define GPIO to LCD mapping
|
||||||
|
lcd_select = 7
|
||||||
|
lcd_enable = 8
|
||||||
|
LCD_D4_21 = 21 # Rev 1 Board
|
||||||
|
LCD_D4_27 = 27 # Rev 2 Board
|
||||||
|
lcd_data4 = LCD_D4_27
|
||||||
|
lcd_data5 = 22
|
||||||
|
lcd_data6 = 23
|
||||||
|
lcd_data7 = 24
|
||||||
|
lcd_bri = 10 # Brightness control (Pecus)
|
||||||
|
|
||||||
|
# Define LCD device constants
|
||||||
|
LCD_WIDTH = 16 # Default characters per line
|
||||||
|
LCD_CHR = True
|
||||||
|
LCD_CMD = False
|
||||||
|
|
||||||
|
LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
|
||||||
|
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
|
||||||
|
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
|
||||||
|
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line
|
||||||
|
|
||||||
|
# Some LCDs use different addresses (16 x 4 line LCDs)
|
||||||
|
# Comment out the above two lines and uncomment the two lines below
|
||||||
|
# LCD_LINE_3 = 0x90 # LCD RAM address for the 3rd line
|
||||||
|
# LCD_LINE_4 = 0xD0 # LCD RAM address for the 4th line
|
||||||
|
|
||||||
|
# If using a 4 x 16 display also amend the lcd.setWidth(<width>) statement in rradio4.py
|
||||||
|
|
||||||
|
# Timing constants
|
||||||
|
E_PULSE = 0.00001 # Pulse width of enable
|
||||||
|
E_DELAY = 0.00001 # Delay between writes
|
||||||
|
E_POSTCLEAR = 0.05 # Delay after clearing display
|
||||||
|
|
||||||
|
translate = Translate()
|
||||||
|
config = Configuration()
|
||||||
|
|
||||||
|
# Temporary files for displayed text. /tmp is a tempfs in RAM (Pecus)
|
||||||
|
# Remember! If you don't want to kill your SD card, add line in /etc/fstab : (Pecus)
|
||||||
|
# 'tmpfs /tmp /tmpfs size=10M,noatime 0 0' (Pecus)
|
||||||
|
Line1File = "/tmp/radiod/line1.txt" # (Pecus)
|
||||||
|
Line2File = "/tmp/radiod/line2.txt" # (Pecus)
|
||||||
|
Line3File = "/tmp/radiod/line3.txt" # (Pecus)
|
||||||
|
Line4File = "/tmp/radiod/line4.txt" # (Pecus)
|
||||||
|
|
||||||
|
|
||||||
|
# Lcd Class
|
||||||
|
class Lcd:
|
||||||
|
global lcd_enable, lcd_select, lcd_data4, lcd_data5, lcd_data6, lcd_data7
|
||||||
|
width = LCD_WIDTH
|
||||||
|
# If display can support umlauts set to True else False
|
||||||
|
RawMode = False # Test only
|
||||||
|
ScrollSpeed = 0.3 # Default scroll speed
|
||||||
|
lcd_data4 = LCD_D4_27 # Default for revision 2 boards
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Create files for displayed texts (Pecus)
|
||||||
|
if not os.path.isdir("/tmp/radiod/"): # (Pecus)
|
||||||
|
os.mkdir ("/tmp/radiod/") # (Pecus)
|
||||||
|
self.writeToFile (Line1File,"1") # (Pecus)
|
||||||
|
self.writeToFile (Line2File,"2") # (Pecus)
|
||||||
|
self.writeToFile (Line3File,"3") # (Pecus)
|
||||||
|
self.writeToFile (Line4File,"4") # (Pecus)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Initialise for either revision 1 or 2 boards
|
||||||
|
def init(self,revision=2):
|
||||||
|
# LCD outputs
|
||||||
|
global lcd_enable, lcd_select, lcd_bri
|
||||||
|
global lcd_data4, lcd_data5, lcd_data6, lcd_data7
|
||||||
|
|
||||||
|
if revision == 1:
|
||||||
|
lcd_data4 = LCD_D4_21
|
||||||
|
|
||||||
|
# Get LCD configuration connects including lcd_data4
|
||||||
|
lcd_select = config.getLcdGpio("lcd_select")
|
||||||
|
lcd_enable = config.getLcdGpio("lcd_enable")
|
||||||
|
|
||||||
|
if revision != 1:
|
||||||
|
lcd_data4 = config.getLcdGpio("lcd_data4")
|
||||||
|
|
||||||
|
lcd_data5 = config.getLcdGpio("lcd_data5")
|
||||||
|
lcd_data6 = config.getLcdGpio("lcd_data6")
|
||||||
|
lcd_data7 = config.getLcdGpio("lcd_data7")
|
||||||
|
lcd_bri = config.getLcdGpio("lcd_bri")
|
||||||
|
|
||||||
|
GPIO.setwarnings(False) # Disable warnings
|
||||||
|
GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
|
||||||
|
GPIO.setup(lcd_enable, GPIO.OUT) # E
|
||||||
|
GPIO.setup(lcd_select, GPIO.OUT) # RS
|
||||||
|
GPIO.setup(lcd_data4, GPIO.OUT) # DB4
|
||||||
|
GPIO.setup(lcd_data5, GPIO.OUT) # DB5
|
||||||
|
GPIO.setup(lcd_data6, GPIO.OUT) # DB6
|
||||||
|
GPIO.setup(lcd_data7, GPIO.OUT) # DB7
|
||||||
|
GPIO.setup(lcd_bri, GPIO.OUT) # brightness (Pecus)
|
||||||
|
self.lcd_init()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Initialise the display
|
||||||
|
def lcd_init(self):
|
||||||
|
#self._byte_out(0x33,LCD_CMD) # 110011 Initialise LCD?
|
||||||
|
#self._byte_out(0x32,LCD_CMD) # 110010 Initialise LCD?
|
||||||
|
self._byte_out(0x00,LCD_CMD) # 000000 Initialise OLED?
|
||||||
|
self._byte_out(0x02,LCD_CMD) # 000010 Initialise OLED?
|
||||||
|
# Problem with OLED display after reboot ?? (Pecus)
|
||||||
|
self._byte_out(0x28,LCD_CMD) # 101000 Data length, number of lines, font size (Pecus)
|
||||||
|
time.sleep(E_POSTCLEAR) # waiting for longer delay (Pecus)
|
||||||
|
self._byte_out(0x08,LCD_CMD) # display OFF, cursor/blink off - required for OLED (Pecus)
|
||||||
|
time.sleep(E_POSTCLEAR) # waiting for longer delay (Pecus)
|
||||||
|
self._byte_out(0x01,LCD_CMD) # 000001 Clear display (Pecus)
|
||||||
|
time.sleep(E_POSTCLEAR) # waiting for longer delay (Pecus)
|
||||||
|
self._byte_out(0x06,LCD_CMD) # 000110 Cursor move direction
|
||||||
|
self._byte_out(0x17,LCD_CMD) # character mode, power on
|
||||||
|
self._byte_out(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
|
||||||
|
time.sleep(0.3) # Allow to settle before using
|
||||||
|
self.setBright(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Output byte to Led mode = Command or Data
|
||||||
|
def _byte_out(self,bits, mode):
|
||||||
|
global lcd_enable, lcd_select
|
||||||
|
global lcd_data4, lcd_data5, lcd_data6, lcd_data7
|
||||||
|
# Send byte to data pins
|
||||||
|
# bits = data
|
||||||
|
# mode = True for character
|
||||||
|
# False for command
|
||||||
|
GPIO.output(lcd_select, mode) # RS
|
||||||
|
|
||||||
|
# High bits
|
||||||
|
GPIO.output(lcd_data4, False)
|
||||||
|
GPIO.output(lcd_data5, False)
|
||||||
|
GPIO.output(lcd_data6, False)
|
||||||
|
GPIO.output(lcd_data7, False)
|
||||||
|
if bits&0x10==0x10:
|
||||||
|
GPIO.output(lcd_data4, True)
|
||||||
|
if bits&0x20==0x20:
|
||||||
|
GPIO.output(lcd_data5, True)
|
||||||
|
if bits&0x40==0x40:
|
||||||
|
GPIO.output(lcd_data6, True)
|
||||||
|
if bits&0x80==0x80:
|
||||||
|
GPIO.output(lcd_data7, True)
|
||||||
|
|
||||||
|
# Toggle 'Enable' pin
|
||||||
|
time.sleep(E_DELAY)
|
||||||
|
GPIO.output(lcd_enable, True)
|
||||||
|
time.sleep(E_PULSE)
|
||||||
|
GPIO.output(lcd_enable, False)
|
||||||
|
time.sleep(E_DELAY)
|
||||||
|
|
||||||
|
# Low bits
|
||||||
|
GPIO.output(lcd_data4, False)
|
||||||
|
GPIO.output(lcd_data5, False)
|
||||||
|
GPIO.output(lcd_data6, False)
|
||||||
|
GPIO.output(lcd_data7, False)
|
||||||
|
if bits&0x01==0x01:
|
||||||
|
GPIO.output(lcd_data4, True)
|
||||||
|
if bits&0x02==0x02:
|
||||||
|
GPIO.output(lcd_data5, True)
|
||||||
|
if bits&0x04==0x04:
|
||||||
|
GPIO.output(lcd_data6, True)
|
||||||
|
if bits&0x08==0x08:
|
||||||
|
GPIO.output(lcd_data7, True)
|
||||||
|
|
||||||
|
# Toggle 'Enable' pin
|
||||||
|
time.sleep(E_DELAY)
|
||||||
|
GPIO.output(lcd_enable, True)
|
||||||
|
time.sleep(E_PULSE)
|
||||||
|
GPIO.output(lcd_enable, False)
|
||||||
|
time.sleep(E_DELAY)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set the display width
|
||||||
|
def setWidth(self,width):
|
||||||
|
self.width = width
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send string to display
|
||||||
|
def _string(self,message):
|
||||||
|
s = message.ljust(self.width," ")
|
||||||
|
if not self.RawMode:
|
||||||
|
s = translate.toLCD(s)
|
||||||
|
for i in range(self.width):
|
||||||
|
self._byte_out(ord(s[i]),LCD_CHR)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Display Line 1 on LCD
|
||||||
|
def line1(self,text):
|
||||||
|
self.writeToFile (Line1File,text) # (Pecus)
|
||||||
|
self._byte_out(LCD_LINE_1, LCD_CMD)
|
||||||
|
self._string(text)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Display Line 2 on LCD
|
||||||
|
def line2(self,text):
|
||||||
|
self.writeToFile (Line2File,text) # (Pecus)
|
||||||
|
self._byte_out(LCD_LINE_2, LCD_CMD)
|
||||||
|
self._string(text)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Display Line 3 on LCD
|
||||||
|
def line3(self,text):
|
||||||
|
self.writeToFile (Line3File,text) # (Pecus)
|
||||||
|
self._byte_out(LCD_LINE_3, LCD_CMD)
|
||||||
|
self._string(text)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Display Line 4 on LCD
|
||||||
|
def line4(self,text):
|
||||||
|
self.writeToFile (Line4File,text) # (Pecus)
|
||||||
|
self._byte_out(LCD_LINE_4, LCD_CMD)
|
||||||
|
self._string(text)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scroll message on line 1
|
||||||
|
def scroll1(self,mytext,interrupt):
|
||||||
|
self.writeToFile (Line1File,mytext) # (Pecus)
|
||||||
|
self._scroll(mytext,LCD_LINE_1,interrupt)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scroll message on line 2
|
||||||
|
def scroll2(self,mytext,interrupt):
|
||||||
|
self.writeToFile (Line2File,mytext) # (Pecus)
|
||||||
|
self._scroll(mytext,LCD_LINE_2,interrupt)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scroll message on line 3
|
||||||
|
def scroll3(self,mytext,interrupt):
|
||||||
|
self.writeToFile (Line3File,mytext) # (Pecus)
|
||||||
|
self._scroll(mytext,LCD_LINE_3,interrupt)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scroll message on line 4
|
||||||
|
def scroll4(self,mytext,interrupt):
|
||||||
|
self.writeToFile (Line4File,mytext) # (Pecus)
|
||||||
|
self._scroll(mytext,LCD_LINE_4,interrupt)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Scroll line - interrupt() breaks out routine if True
|
||||||
|
def _scroll(self,mytext,line,interrupt):
|
||||||
|
ilen = len(mytext)
|
||||||
|
skip = False
|
||||||
|
|
||||||
|
self._byte_out(line, LCD_CMD)
|
||||||
|
self._string(mytext[0:self.width + 1])
|
||||||
|
|
||||||
|
if (ilen <= self.width):
|
||||||
|
skip = True
|
||||||
|
|
||||||
|
if not skip:
|
||||||
|
for i in range(0, 5):
|
||||||
|
time.sleep(0.2)
|
||||||
|
if interrupt():
|
||||||
|
skip = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not skip:
|
||||||
|
for i in range(0, ilen - self.width + 1 ):
|
||||||
|
self._byte_out(line, LCD_CMD)
|
||||||
|
self._string(mytext[i:i+self.width])
|
||||||
|
if interrupt():
|
||||||
|
skip = True
|
||||||
|
break
|
||||||
|
time.sleep(self.ScrollSpeed)
|
||||||
|
|
||||||
|
if not skip:
|
||||||
|
for i in range(0, 5):
|
||||||
|
time.sleep(0.2)
|
||||||
|
if interrupt():
|
||||||
|
break
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set Scroll line speed - Best values are 0.2 and 0.3
|
||||||
|
# Limit to between 0.05 and 1.0
|
||||||
|
def setScrollSpeed(self,speed):
|
||||||
|
if speed < 0.05:
|
||||||
|
speed = 0.2
|
||||||
|
elif speed > 1.0:
|
||||||
|
speed = 0.3
|
||||||
|
self.ScrollSpeed = speed
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set raw mode on (No translation)
|
||||||
|
def setRawMode(self,value):
|
||||||
|
self.RawMode = value
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clear display
|
||||||
|
def clearDisplay(self):
|
||||||
|
self.writeToFile (Line1File,"") # (Pecus)
|
||||||
|
self.writeToFile (Line2File,"") # (Pecus)
|
||||||
|
self.writeToFile (Line3File,"") # (Pecus)
|
||||||
|
self.writeToFile (Line4File,"") # (Pecus)
|
||||||
|
self._byte_out(0x01,LCD_CMD) # 000001 Clear display
|
||||||
|
time.sleep(E_POSTCLEAR)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set brightness (Pecus)
|
||||||
|
def setBright(self,value):
|
||||||
|
GPIO.output(lcd_bri, value)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Write text to file (Pecus)
|
||||||
|
def writeToFile(self,fname,text): # (Pecus)
|
||||||
|
file = open(fname, "w") # (Pecus)
|
||||||
|
file.write(text) # (Pecus)
|
||||||
|
file.close() # (Pecus)
|
||||||
|
return # (Pecus)
|
||||||
|
|
||||||
|
# End of Lcd class
|
||||||
Binary file not shown.
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# $Id: log_class.py,v 1.7 2015/01/24 10:29:14 bob Exp $
|
||||||
|
# Raspberry Pi Internet Radio
|
||||||
|
# Logging class
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
# Modified to use /etc/radiod.conf from 3.15 onwards
|
||||||
|
#
|
||||||
|
# Log levels are :
|
||||||
|
# CRITICAL 50
|
||||||
|
# ERROR 40
|
||||||
|
# WARNING 30
|
||||||
|
# INFO 20
|
||||||
|
# DEBUG 10
|
||||||
|
# NOTSET 0
|
||||||
|
#
|
||||||
|
# See https://docs.python.org/2/library/logging.html
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
config = ConfigParser.ConfigParser()
|
||||||
|
ConfigFile = "/etc/radiod.conf"
|
||||||
|
|
||||||
|
class Log:
|
||||||
|
|
||||||
|
CRITICAL = logging.CRITICAL
|
||||||
|
ERROR = logging.ERROR
|
||||||
|
WARNING = logging.WARNING
|
||||||
|
INFO = logging.INFO
|
||||||
|
DEBUG = logging.DEBUG
|
||||||
|
NONE = 0
|
||||||
|
|
||||||
|
module = '' # Module name for log entries
|
||||||
|
loglevel = logging.INFO
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
def init(self,module):
|
||||||
|
self.module = module
|
||||||
|
self.loglevel = self.getConfig()
|
||||||
|
return
|
||||||
|
|
||||||
|
def message(self,message,level):
|
||||||
|
# Set up logging, level
|
||||||
|
if level != self.NONE:
|
||||||
|
logger = logging.getLogger('gipiod')
|
||||||
|
hdlr = logging.FileHandler('/var/log/' + self.module + '.log')
|
||||||
|
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
||||||
|
hdlr.setFormatter(formatter)
|
||||||
|
logger.addHandler(hdlr)
|
||||||
|
logger.setLevel(self.loglevel)
|
||||||
|
|
||||||
|
# write to log
|
||||||
|
if level == self.CRITICAL:
|
||||||
|
logger.critical(message)
|
||||||
|
elif level == self.ERROR:
|
||||||
|
logger.error(message)
|
||||||
|
elif level == self.WARNING:
|
||||||
|
logger.warning(message)
|
||||||
|
elif level == self.INFO:
|
||||||
|
logger.info(message)
|
||||||
|
elif level == self.DEBUG:
|
||||||
|
logger.debug(message)
|
||||||
|
|
||||||
|
logger.removeHandler(hdlr)
|
||||||
|
hdlr.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Temporary set log level
|
||||||
|
def setLevel(self,level):
|
||||||
|
self.loglevel = level
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the log level from the configuration file
|
||||||
|
def getLevel(self):
|
||||||
|
return self.loglevel
|
||||||
|
|
||||||
|
# Get configuration loglevel option
|
||||||
|
def getConfig(self):
|
||||||
|
section = 'RADIOD'
|
||||||
|
option = 'loglevel'
|
||||||
|
strLogLevel = 'INFO'
|
||||||
|
|
||||||
|
# Get loglevel option
|
||||||
|
config.read(ConfigFile)
|
||||||
|
try:
|
||||||
|
strLogLevel = config.get(section,option)
|
||||||
|
|
||||||
|
except ConfigParser.NoSectionError:
|
||||||
|
msg = ConfigParser.NoSectionError(section),'in',ConfigFile
|
||||||
|
self.message(msg,self.ERROR)
|
||||||
|
|
||||||
|
if strLogLevel == "CRITICAL":
|
||||||
|
loglevel = self.CRITICAL
|
||||||
|
elif strLogLevel == "ERROR":
|
||||||
|
loglevel = self.ERROR
|
||||||
|
elif strLogLevel == "WARNING":
|
||||||
|
loglevel = self.WARNING
|
||||||
|
elif strLogLevel == "INFO":
|
||||||
|
loglevel = self.INFO
|
||||||
|
elif strLogLevel == "DEBUG":
|
||||||
|
loglevel = self.DEBUG
|
||||||
|
elif strLogLevel == "NONE":
|
||||||
|
loglevel = self.NONE
|
||||||
|
else:
|
||||||
|
loglevel = self.INFO
|
||||||
|
return loglevel
|
||||||
|
|
||||||
|
# End of log class
|
||||||
|
|
||||||
Executable
BIN
Binary file not shown.
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# $Id: menu_switch_class.py,v 1.2 2016/05/08 11:57:31 bob Exp $
|
||||||
|
# Raspberry Retro Pi Internet Radio
|
||||||
|
# Retro radio menu switch
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
# Status LED class
|
||||||
|
class MenuSwitch:
|
||||||
|
# Status settings
|
||||||
|
CLEAR = 0
|
||||||
|
VALUE1 = 1
|
||||||
|
VALUE2 = 2
|
||||||
|
VALUE4 = 4
|
||||||
|
switch1 = None
|
||||||
|
switch2 = None
|
||||||
|
switch4 = None
|
||||||
|
|
||||||
|
def __init__(self, switch1, switch2, switch4,callback):
|
||||||
|
self.switch1 = switch1
|
||||||
|
self.switch2 = switch2
|
||||||
|
self.switch4 = switch4
|
||||||
|
self.callback = callback
|
||||||
|
|
||||||
|
# Set up switch lines
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
GPIO.setup(self.switch1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
GPIO.setup(self.switch2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
GPIO.setup(self.switch4, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
|
# Add event detection to the GPIO inputs
|
||||||
|
GPIO.add_event_detect(self.switch1, GPIO.BOTH, callback=self.callback, bouncetime=100)
|
||||||
|
GPIO.add_event_detect(self.switch2, GPIO.BOTH, callback=self.callback, bouncetime=100)
|
||||||
|
GPIO.add_event_detect(self.switch4, GPIO.BOTH, callback=self.callback, bouncetime=100)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get switch state
|
||||||
|
def get(self):
|
||||||
|
value = 0
|
||||||
|
if not GPIO.input(self.switch1):
|
||||||
|
value += 1
|
||||||
|
if not GPIO.input(self.switch2):
|
||||||
|
value += 2
|
||||||
|
if not GPIO.input(self.switch4):
|
||||||
|
value += 4
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
switch_value = 0
|
||||||
|
|
||||||
|
def menu_swich_event(switch):
|
||||||
|
global switch_value
|
||||||
|
time.sleep(0.1)
|
||||||
|
value = menu_switch.get()
|
||||||
|
if value != switch_value:
|
||||||
|
print "Switch", switch,"Value =", value
|
||||||
|
switch_value = value
|
||||||
|
return
|
||||||
|
|
||||||
|
menu_switch = MenuSwitch(24,8,7,menu_swich_event)
|
||||||
|
switch_value = menu_switch.get()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# End of class
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
Creating /var/lib/radiod/stationlist
|
||||||
|
|
||||||
|
cp /var/lib/radiod/station.urls /var/lib/radiod/stationlist
|
||||||
|
|
||||||
|
Creating M3U files from /var/lib/radiod/stationlist
|
||||||
|
|
||||||
Executable
+2762
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Executable
+176
@@ -0,0 +1,176 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi Internet Radio Class
|
||||||
|
# $Id: radio_daemon.py,v 1.7 2015/10/31 15:40:05 bob Exp $
|
||||||
|
# Author : Sander Marechal
|
||||||
|
# Website http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
|
||||||
|
#
|
||||||
|
# Adapted by Bob Rathbone for the Internet Radio
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This class is the daemon class for radio_class.py
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys, os, time, atexit
|
||||||
|
from signal import SIGTERM
|
||||||
|
|
||||||
|
class Daemon:
|
||||||
|
"""
|
||||||
|
A generic daemon class.
|
||||||
|
|
||||||
|
Usage: subclass the Daemon class and override the run() method
|
||||||
|
"""
|
||||||
|
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
||||||
|
self.stdin = stdin
|
||||||
|
self.stdout = stdout
|
||||||
|
self.stderr = stderr
|
||||||
|
self.pidfile = pidfile
|
||||||
|
|
||||||
|
def daemonize(self):
|
||||||
|
"""
|
||||||
|
do the UNIX double-fork magic, see Stevens' "Advanced
|
||||||
|
Programming in the UNIX Environment" for details (ISBN 0201563177)
|
||||||
|
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
# exit first parent
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError, e:
|
||||||
|
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# decouple from parent environment
|
||||||
|
os.chdir("/")
|
||||||
|
os.setsid()
|
||||||
|
os.umask(0)
|
||||||
|
|
||||||
|
# do second fork
|
||||||
|
try:
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
# exit from second parent
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError, e:
|
||||||
|
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# redirect standard file descriptors
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
si = file(self.stdin, 'r')
|
||||||
|
so = file(self.stdout, 'a+')
|
||||||
|
se = file(self.stderr, 'a+', 0)
|
||||||
|
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||||
|
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||||
|
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||||
|
|
||||||
|
# write pidfile
|
||||||
|
atexit.register(self.delpid)
|
||||||
|
pid = str(os.getpid())
|
||||||
|
file(self.pidfile,'w+').write("%s\n" % pid)
|
||||||
|
|
||||||
|
def delpid(self):
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Start the daemon
|
||||||
|
"""
|
||||||
|
self.begin(True) # Daemonize
|
||||||
|
|
||||||
|
def nodaemon(self):
|
||||||
|
"""
|
||||||
|
Start the program in foreground
|
||||||
|
Test purposes only
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.begin(False)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print "\nRadio stopped"
|
||||||
|
|
||||||
|
def begin(self,daemonize):
|
||||||
|
"""
|
||||||
|
Start the daemon
|
||||||
|
"""
|
||||||
|
# Check for a pidfile to see if the daemon already runs
|
||||||
|
try:
|
||||||
|
pf = file(self.pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if pid:
|
||||||
|
message = "pidfile %s already exist. Daemon already running?\n"
|
||||||
|
sys.stderr.write(message % self.pidfile)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Start the daemon
|
||||||
|
if daemonize:
|
||||||
|
self.daemonize()
|
||||||
|
else:
|
||||||
|
print "Radio running pid", os.getpid()
|
||||||
|
|
||||||
|
self.run()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stop the daemon
|
||||||
|
"""
|
||||||
|
# Get the pid from the pidfile
|
||||||
|
try:
|
||||||
|
pf = file(self.pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if not pid:
|
||||||
|
message = "pidfile %s does not exist. Daemon not running?\n"
|
||||||
|
sys.stderr.write(message % self.pidfile)
|
||||||
|
return # not an error in a restart
|
||||||
|
|
||||||
|
# Try killing the daemon process
|
||||||
|
p = os.popen("killall pianobar 2> /dev/null")
|
||||||
|
try:
|
||||||
|
count = 3
|
||||||
|
while count > 0:
|
||||||
|
os.kill(pid, SIGTERM)
|
||||||
|
time.sleep(1.0)
|
||||||
|
count -= 1
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except OSError, err:
|
||||||
|
err = str(err)
|
||||||
|
if err.find("No such process") > 0:
|
||||||
|
if os.path.exists(self.pidfile):
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
else:
|
||||||
|
print str(err)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
"""
|
||||||
|
Restart the daemon
|
||||||
|
"""
|
||||||
|
self.stop()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
"""
|
||||||
|
Status
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
You should override this method when you subclass Daemon. It will be called after the process has been
|
||||||
|
daemonized by start() or restart().
|
||||||
|
"""
|
||||||
|
|
||||||
Binary file not shown.
Executable
+206
@@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi Internet Radio Remote Control Class
|
||||||
|
# $Id: rc_daemon.py,v 1.7 2015/10/31 15:40:05 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Sander Marechal
|
||||||
|
# Website http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
|
||||||
|
#
|
||||||
|
# Adapted by Bob Rathbone for the Internet Radio
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This is the daemon class for the PiFace CAD remote control
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys, os, time, atexit
|
||||||
|
from signal import SIGKILL
|
||||||
|
from signal import SIGHUP
|
||||||
|
|
||||||
|
class Daemon:
|
||||||
|
"""
|
||||||
|
A generic daemon class.
|
||||||
|
|
||||||
|
Usage: subclass the Daemon class and override the run() method
|
||||||
|
"""
|
||||||
|
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
||||||
|
self.stdin = stdin
|
||||||
|
self.stdout = stdout
|
||||||
|
self.stderr = stderr
|
||||||
|
self.pidfile = pidfile
|
||||||
|
|
||||||
|
def daemonize(self):
|
||||||
|
"""
|
||||||
|
do the UNIX double-fork magic, see Stevens' "Advanced
|
||||||
|
Programming in the UNIX Environment" for details (ISBN 0201563177)
|
||||||
|
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.ppid = os.getpid()
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
# exit first parent
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError, e:
|
||||||
|
sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# decouple from parent environment
|
||||||
|
os.chdir("/")
|
||||||
|
os.setsid()
|
||||||
|
os.umask(0)
|
||||||
|
|
||||||
|
# do second fork
|
||||||
|
try:
|
||||||
|
pid = os.fork()
|
||||||
|
if pid > 0:
|
||||||
|
# exit from second parent
|
||||||
|
sys.exit(0)
|
||||||
|
except OSError, e:
|
||||||
|
sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# redirect standard file descriptors
|
||||||
|
sys.stdout.flush()
|
||||||
|
sys.stderr.flush()
|
||||||
|
si = file(self.stdin, 'r')
|
||||||
|
so = file(self.stdout, 'a+')
|
||||||
|
se = file(self.stderr, 'a+', 0)
|
||||||
|
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||||
|
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||||
|
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||||
|
|
||||||
|
# write pidfile
|
||||||
|
atexit.register(self.delpid)
|
||||||
|
pid = str(os.getpid())
|
||||||
|
file(self.pidfile,'w+').write("%s\n" % pid)
|
||||||
|
|
||||||
|
def delpid(self):
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Start the daemon
|
||||||
|
"""
|
||||||
|
self.begin(True)
|
||||||
|
|
||||||
|
def nodaemon(self):
|
||||||
|
"""
|
||||||
|
Start the program in foreground
|
||||||
|
Test purposes only
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.begin(False)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pid = os.getpid()
|
||||||
|
print "\nStopping remote control pid",pid
|
||||||
|
os.kill(pid, SIGKILL)
|
||||||
|
|
||||||
|
def begin(self,daemonize):
|
||||||
|
"""
|
||||||
|
Start the daemon
|
||||||
|
"""
|
||||||
|
# Check for a pidfile to see if the daemon already runs
|
||||||
|
try:
|
||||||
|
pf = file(self.pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if pid:
|
||||||
|
message = "pidfile %s already exist. Daemon already running?\n"
|
||||||
|
sys.stderr.write(message % self.pidfile)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Start the daemon
|
||||||
|
if daemonize:
|
||||||
|
self.daemonize()
|
||||||
|
else:
|
||||||
|
print "remote control running pid", os.getpid()
|
||||||
|
|
||||||
|
self.run()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stop the daemon
|
||||||
|
"""
|
||||||
|
# Get the pid from the pidfile
|
||||||
|
try:
|
||||||
|
pf = file(self.pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if not pid:
|
||||||
|
message = "pidfile %s does not exist. Daemon not running?\n"
|
||||||
|
sys.stderr.write(message % self.pidfile)
|
||||||
|
return # not an error in a restart
|
||||||
|
|
||||||
|
# Stop lircd and irexec
|
||||||
|
try:
|
||||||
|
pid1 = int(self.exec_cmd("pidof lircd"))
|
||||||
|
os.kill(pid1, SIGKILL)
|
||||||
|
except OSError, err:
|
||||||
|
print str(err)
|
||||||
|
except ValueError:
|
||||||
|
print "lircd not running"
|
||||||
|
|
||||||
|
# Try killing the daemon process
|
||||||
|
try:
|
||||||
|
cpid = int(self.exec_cmd("pgrep -P " + str(pid)))
|
||||||
|
os.kill(cpid, SIGHUP)
|
||||||
|
if os.path.exists(self.pidfile):
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
os.kill(pid, SIGKILL)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except ValueError, err:
|
||||||
|
print "Remote control daemon not running"
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
|
||||||
|
except OSError, err:
|
||||||
|
err = str(err)
|
||||||
|
if err.find("No such process") > 0:
|
||||||
|
if os.path.exists(self.pidfile):
|
||||||
|
os.remove(self.pidfile)
|
||||||
|
else:
|
||||||
|
print str(err)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
"""
|
||||||
|
Restart the daemon
|
||||||
|
"""
|
||||||
|
self.stop()
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
"""
|
||||||
|
Status
|
||||||
|
"""
|
||||||
|
|
||||||
|
def flash(self):
|
||||||
|
"""
|
||||||
|
Flash activity LED
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
You should override this method when you subclass Daemon. It will be called after the process has been
|
||||||
|
daemonized by start() or restart().
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def exec_cmd(self,cmd):
|
||||||
|
p = os.popen(cmd)
|
||||||
|
result = p.readline().rstrip('\n')
|
||||||
|
return result
|
||||||
|
|
||||||
|
### End ###
|
||||||
Executable
BIN
Binary file not shown.
Executable
+250
@@ -0,0 +1,250 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi remote control daemon (Non-Piface variants)
|
||||||
|
# $Id: remote_control.py,v 1.17 2016/02/09 13:23:31 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This program uses the piface CAD libraries
|
||||||
|
# See http://www.piface.org.uk/products/piface_control_and_display/
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
# The important configuration files are
|
||||||
|
# /etc/lirc/lircrc Program to event registration file
|
||||||
|
# /etc/lircd.conf User generated remote control configuration file
|
||||||
|
#
|
||||||
|
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import ConfigParser
|
||||||
|
import pifacecad.ir
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import errno
|
||||||
|
|
||||||
|
# Radio project imports
|
||||||
|
from config_class import Configuration
|
||||||
|
from rc_daemon import Daemon
|
||||||
|
from log_class import Log
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
IR_LED=11 # GPIO 11 pin 23
|
||||||
|
remote_led = IR_LED
|
||||||
|
muted = False
|
||||||
|
udphost = 'localhost' # IR Listener UDP host default localhost
|
||||||
|
udpport = 5100 # IR Listener UDP port number default 5100
|
||||||
|
|
||||||
|
config = Configuration()
|
||||||
|
|
||||||
|
pidfile = '/var/run/radiod.pid'
|
||||||
|
|
||||||
|
# Signal SIGTERM handler
|
||||||
|
def signalHandler(signal,frame):
|
||||||
|
global log
|
||||||
|
pid = os.getpid()
|
||||||
|
log.message("Remote control stopped, PID " + str(pid), log.INFO)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Daemon class
|
||||||
|
class RemoteDaemon(Daemon):
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
global remote_led
|
||||||
|
global udpport
|
||||||
|
global udphost
|
||||||
|
log.init('radio')
|
||||||
|
progcall = str(sys.argv)
|
||||||
|
log.message(progcall, log.DEBUG)
|
||||||
|
log.message('Remote control running pid ' + str(os.getpid()), log.INFO)
|
||||||
|
signal.signal(signal.SIGHUP,signalHandler)
|
||||||
|
exec_cmd('sudo service lirc start')
|
||||||
|
|
||||||
|
remote_led = config.getRemoteLed()
|
||||||
|
if remote_led > 0:
|
||||||
|
GPIO.setwarnings(False) # Disable warnings
|
||||||
|
GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
|
||||||
|
GPIO.setup(remote_led, GPIO.OUT) # Output LED
|
||||||
|
flash_led(remote_led)
|
||||||
|
else:
|
||||||
|
log.message("Remote control LED disabled", log.DEBUG)
|
||||||
|
udpport = config.getRemoteUdpPort()
|
||||||
|
udphost = config.getRemoteUdpHost()
|
||||||
|
log.message("UDP connect host " + udphost + " port " + str(udpport), log.DEBUG)
|
||||||
|
listener()
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
# Get the pid from the pidfile
|
||||||
|
try:
|
||||||
|
pf = file(self.pidfile,'r')
|
||||||
|
pid = int(pf.read().strip())
|
||||||
|
pf.close()
|
||||||
|
except IOError:
|
||||||
|
pid = None
|
||||||
|
|
||||||
|
if not pid:
|
||||||
|
message = "Remote control status: not running"
|
||||||
|
log.message(message, log.INFO)
|
||||||
|
print message
|
||||||
|
else:
|
||||||
|
message = "Remote control running pid " + str(pid)
|
||||||
|
log.message(message, log.INFO)
|
||||||
|
print message
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test udp send
|
||||||
|
def send(self,msg):
|
||||||
|
udpSend(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test the LED
|
||||||
|
def flash(self):
|
||||||
|
log.init('radio')
|
||||||
|
remote_led = config.getRemoteLed()
|
||||||
|
if remote_led > 0:
|
||||||
|
GPIO.setwarnings(False) # Disable warnings
|
||||||
|
GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
|
||||||
|
GPIO.setup(remote_led, GPIO.OUT) # Output LED
|
||||||
|
flash_led(remote_led)
|
||||||
|
return
|
||||||
|
|
||||||
|
# End of class overrides
|
||||||
|
|
||||||
|
# Handle events
|
||||||
|
def handleIRevent(event):
|
||||||
|
global muted
|
||||||
|
global remote_led
|
||||||
|
message = "Remote control sent " + event.ir_code
|
||||||
|
log.message(message, log.DEBUG)
|
||||||
|
if remote_led > 0:
|
||||||
|
GPIO.output(remote_led, True)
|
||||||
|
button = event.ir_code
|
||||||
|
print button
|
||||||
|
udpSend(button)
|
||||||
|
|
||||||
|
if remote_led > 0:
|
||||||
|
GPIO.output(remote_led, False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def exec_cmd(cmd):
|
||||||
|
log.message(cmd, log.DEBUG)
|
||||||
|
p = os.popen(cmd)
|
||||||
|
result = p.readline().rstrip('\n')
|
||||||
|
return result
|
||||||
|
|
||||||
|
# The main Remote control listen routine
|
||||||
|
def listener():
|
||||||
|
log.message("Remote: setup IR listener", log.DEBUG)
|
||||||
|
listener = pifacecad.ir.IREventListener(prog="piradio")
|
||||||
|
listener.register('KEY_VOLUMEUP',handleIRevent)
|
||||||
|
listener.register('KEY_VOLUMEDOWN',handleIRevent)
|
||||||
|
listener.register('KEY_CHANNELUP',handleIRevent)
|
||||||
|
listener.register('KEY_CHANNELDOWN',handleIRevent)
|
||||||
|
listener.register('KEY_MENU',handleIRevent)
|
||||||
|
listener.register('KEY_MUTE',handleIRevent)
|
||||||
|
listener.register('KEY_LEFT',handleIRevent)
|
||||||
|
listener.register('KEY_RIGHT',handleIRevent)
|
||||||
|
listener.register('KEY_UP',handleIRevent)
|
||||||
|
listener.register('KEY_DOWN',handleIRevent)
|
||||||
|
listener.register('KEY_OK',handleIRevent)
|
||||||
|
listener.register('KEY_LANGUAGE',handleIRevent)
|
||||||
|
listener.register('KEY_INFO',handleIRevent)
|
||||||
|
listener.register('KEY_SLEEP',handleIRevent) # (Pecus)
|
||||||
|
listener.register('KEY_WAKEUP',handleIRevent) # (Pecus)
|
||||||
|
listener.register('KEY_RADIO',handleIRevent) # (Pecus)
|
||||||
|
listener.register('KEY_MEDIA',handleIRevent) # (Pecus)
|
||||||
|
listener.register('KEY_PANDORA',handleIRevent) # (Pecus)
|
||||||
|
log.message("Activating IR Remote Control listener", log.DEBUG)
|
||||||
|
listener.activate()
|
||||||
|
|
||||||
|
|
||||||
|
# Send button data to radio program
|
||||||
|
def udpSend(button):
|
||||||
|
global udpport
|
||||||
|
global udphost
|
||||||
|
data = ''
|
||||||
|
log.message("Remote control daemon udpSend " + button, log.DEBUG)
|
||||||
|
|
||||||
|
try:
|
||||||
|
clientsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
clientsocket.settimeout(3) # Moze ustawienie na 6 pomoze na wybudzanie pandory po WAKEUP
|
||||||
|
clientsocket.sendto(button, (udphost, udpport))
|
||||||
|
data = clientsocket.recv(100).strip()
|
||||||
|
|
||||||
|
except socket.error, e:
|
||||||
|
err = e.args[0]
|
||||||
|
if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
|
||||||
|
log.message("IR remote udpSend no data" + e, log.ERROR)
|
||||||
|
else:
|
||||||
|
# Errors such as timeout
|
||||||
|
log.message("IR remote udpSend " + str(e), log.ERROR)
|
||||||
|
|
||||||
|
if len(data) > 0:
|
||||||
|
log.message("IR daemon server sent: " + data, log.DEBUG)
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Flash the IR activity LED
|
||||||
|
def flash_led(led):
|
||||||
|
count = 6
|
||||||
|
delay = 0.3
|
||||||
|
log.message("Flash LED on GPIO " + str(led), log.DEBUG)
|
||||||
|
|
||||||
|
while count > 0:
|
||||||
|
GPIO.output(led, True)
|
||||||
|
time.sleep(delay)
|
||||||
|
GPIO.output(led, False)
|
||||||
|
time.sleep(delay)
|
||||||
|
count -= 1
|
||||||
|
return
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def execCommand(cmd):
|
||||||
|
p = os.popen(cmd)
|
||||||
|
return p.readline().rstrip('\n')
|
||||||
|
|
||||||
|
# Print usage
|
||||||
|
def usage():
|
||||||
|
print "usage: %s start|stop|status|version|flash|send|config" % sys.argv[0]
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
### Main routine ###
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
daemon = RemoteDaemon('/var/run/remote.pid')
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
if 'start' == sys.argv[1]:
|
||||||
|
daemon.start()
|
||||||
|
elif 'nodaemon' == sys.argv[1]:
|
||||||
|
daemon.nodaemon()
|
||||||
|
elif 'stop' == sys.argv[1]:
|
||||||
|
daemon.stop()
|
||||||
|
elif 'flash' == sys.argv[1]:
|
||||||
|
daemon.flash()
|
||||||
|
elif 'status' == sys.argv[1]:
|
||||||
|
daemon.status()
|
||||||
|
elif 'version' == sys.argv[1]:
|
||||||
|
print "Version 0.1"
|
||||||
|
elif 'send' == sys.argv[1]:
|
||||||
|
daemon.send('IR_REMOTE')
|
||||||
|
elif 'config' == sys.argv[1]:
|
||||||
|
config = Configuration()
|
||||||
|
print "LED = GPIO", config.getRemoteLed()
|
||||||
|
print "HOST =", config.getRemoteUdpHost()
|
||||||
|
print "PORT =", config.getRemoteUdpPort()
|
||||||
|
print "LISTEN =", config.getRemoteListenHost()
|
||||||
|
else:
|
||||||
|
print "Unknown command: " + sys.argv[1]
|
||||||
|
usage()
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
# End of script
|
||||||
|
|
||||||
Executable
+179
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: latin-1 -*-
|
||||||
|
#
|
||||||
|
# $Id: rss_class.py,v 1.23 2016/06/28 06:40:51 bob Exp $
|
||||||
|
# Raspberry Pi RSS feed class
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import urllib2
|
||||||
|
from xml.dom.minidom import parseString
|
||||||
|
from log_class import Log
|
||||||
|
from translate_class import Translate
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
translate = Translate()
|
||||||
|
|
||||||
|
url = "/var/lib/radiod/rss"
|
||||||
|
|
||||||
|
# Tags to strip out
|
||||||
|
tags = ['<h1>', '</h1>',
|
||||||
|
'<h2>', '</h2>',
|
||||||
|
'<h3>', '</h3>',
|
||||||
|
'<h4>', '</h4>',
|
||||||
|
'<p>', '</p>', '<p/>',
|
||||||
|
'<br>', '</br>','<br/>',
|
||||||
|
'<strong>', '</strong>',
|
||||||
|
'<title>', '</title>',
|
||||||
|
'<description>', '</description>',
|
||||||
|
]
|
||||||
|
|
||||||
|
class Rss:
|
||||||
|
rss = [] # Array for the RSS feed
|
||||||
|
length = 0 # Number of RSS news items
|
||||||
|
feed_available = False
|
||||||
|
rss_error = False # RSS Error (prevents repetitive error logging)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
log.init('radio')
|
||||||
|
if os.path.isfile(url):
|
||||||
|
self.feed_available = True
|
||||||
|
return
|
||||||
|
|
||||||
|
# Gets the next RSS entry from the rss array
|
||||||
|
def getFeed(self):
|
||||||
|
self.feed_available = False
|
||||||
|
line = "No RSS feed"
|
||||||
|
if self.length < 1:
|
||||||
|
self.rss = self.get_new_feed(url)
|
||||||
|
self.length = self.rss.__len__()
|
||||||
|
|
||||||
|
feed = "No RSS feed"
|
||||||
|
if self.length > 0:
|
||||||
|
self.feed_available = True
|
||||||
|
line = self.rss.pop()
|
||||||
|
self.length -= 1
|
||||||
|
feed = translate.all(line)
|
||||||
|
feed = feed.lstrip('"')
|
||||||
|
feed = feed.lstrip('<')
|
||||||
|
if not self.rss_error:
|
||||||
|
log.message(feed,log.DEBUG)
|
||||||
|
return feed
|
||||||
|
|
||||||
|
# Is an RSS news feed available
|
||||||
|
def isAvailable(self):
|
||||||
|
return self.feed_available
|
||||||
|
|
||||||
|
# Get a new feed and put it into the rss array
|
||||||
|
def get_new_feed(self,url):
|
||||||
|
rss = []
|
||||||
|
if os.path.isfile(url):
|
||||||
|
rss_feed = self.exec_cmd("cat " + url)
|
||||||
|
try:
|
||||||
|
file = urllib2.urlopen(rss_feed)
|
||||||
|
data = file.read()
|
||||||
|
file.close()
|
||||||
|
dom = parseString(data)
|
||||||
|
dom.normalize()
|
||||||
|
rss = self.parse_feed(dom)
|
||||||
|
self.rss_error = False # Clear RSS Error
|
||||||
|
log.message("Getting RSS feed: " + rss_feed,log.INFO)
|
||||||
|
except:
|
||||||
|
if not self.rss_error:
|
||||||
|
log.message("Invalid RSS feed: " + rss_feed,log.ERROR)
|
||||||
|
self.rss_error = True # Set RSS error
|
||||||
|
rss.append("No RSS feed found")
|
||||||
|
return rss
|
||||||
|
|
||||||
|
def parse_feed(self,dom):
|
||||||
|
rss = []
|
||||||
|
for news in dom.getElementsByTagName('*'):
|
||||||
|
display = False
|
||||||
|
line = news.toxml()
|
||||||
|
line = line.replace("<", "<") # Replace special string
|
||||||
|
line = line.replace(">", ">")
|
||||||
|
|
||||||
|
msg = "LINE:" + line
|
||||||
|
line = line.lstrip(' ')
|
||||||
|
if (line.find("VIDEO:") != -1):
|
||||||
|
continue
|
||||||
|
if (line.find("AUDIO:") != -1):
|
||||||
|
continue
|
||||||
|
if (line.find("<rss") != -1):
|
||||||
|
continue
|
||||||
|
if (line.find("<item") != -1):
|
||||||
|
continue
|
||||||
|
if (line.find("<image") == 0):
|
||||||
|
continue
|
||||||
|
if (line.find("<title>") >= 0):
|
||||||
|
display= True
|
||||||
|
if (line.find("<description>") >= 0):
|
||||||
|
display= True
|
||||||
|
|
||||||
|
if display:
|
||||||
|
title = ''
|
||||||
|
description = ''
|
||||||
|
line = line.rstrip(' ')
|
||||||
|
line = line.replace("![CDATA[", "")
|
||||||
|
line = line.replace("]]>", "")
|
||||||
|
|
||||||
|
if (line.find("<description>") == 0):
|
||||||
|
description = line.split("</description>", 2)[0]
|
||||||
|
description = self._strip_string(description, "<img", "</img>")
|
||||||
|
description = self._strip_string(description, "<a href", "</a>")
|
||||||
|
description = self._strip_string(description, "<br ", "</br>")
|
||||||
|
|
||||||
|
if (line.find("<title>") >= 0):
|
||||||
|
title = line.split("</title>", 2)[0]
|
||||||
|
|
||||||
|
if len(title) > 0:
|
||||||
|
for tag in tags: # Strip out HTML tags
|
||||||
|
title = title.replace(tag, "")
|
||||||
|
rss.append(title)
|
||||||
|
|
||||||
|
if len(description) > 0:
|
||||||
|
for tag in tags: # Strip out HTML tags
|
||||||
|
description = description.replace(tag, "")
|
||||||
|
rss.append(description)
|
||||||
|
|
||||||
|
self.feed_available = True
|
||||||
|
rss.reverse()
|
||||||
|
return rss
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def exec_cmd(self,cmd):
|
||||||
|
p = os.popen(cmd)
|
||||||
|
result = p.readline().rstrip('\n')
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Strip string (between tags)
|
||||||
|
def _strip_string(self, text, s_start, s_end):
|
||||||
|
new_text = text
|
||||||
|
|
||||||
|
try:
|
||||||
|
while new_text.find(s_start) > 0:
|
||||||
|
idx_start = new_text.find(s_start)
|
||||||
|
replace_str = new_text[idx_start:]
|
||||||
|
idx_end = replace_str.find(s_end)
|
||||||
|
if idx_end < 0:
|
||||||
|
idx_end = replace_str.find("/>")
|
||||||
|
len2 = 2
|
||||||
|
else:
|
||||||
|
len2 = len(s_end)
|
||||||
|
replace_str = replace_str[0:idx_end + len2]
|
||||||
|
new_text = new_text.replace(replace_str,'')
|
||||||
|
except:
|
||||||
|
new_text = text
|
||||||
|
return new_text
|
||||||
|
|
||||||
|
# End of class
|
||||||
Binary file not shown.
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Radio daemon post install script 2
|
||||||
|
# $Id: run_raspi-config.sh,v 1.3 2015/01/31 15:42:20 bob Exp $
|
||||||
|
|
||||||
|
TYPE=$1 # No instructions before this line
|
||||||
|
|
||||||
|
I2C=4 # Uses I2C libraries
|
||||||
|
CAD=5 # PiFace control and display
|
||||||
|
|
||||||
|
echo "The installation will now run the raspi-config cofiguration program!"
|
||||||
|
echo "When it runs select Advanced options"
|
||||||
|
|
||||||
|
if [[ ${TYPE} -eq ${CAD} ]]; then
|
||||||
|
echo "and enable automatic loading of the SPI kernel module"
|
||||||
|
elif [[ ${TYPE} -eq ${I2C} ]]; then
|
||||||
|
echo "and enable automatic loading of the I2C kernel module"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Enter y to continue or x to exit: "
|
||||||
|
read ans
|
||||||
|
if [[ ${ans} == 'x' ]]; then
|
||||||
|
echo "Installation stopped"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
/usr/bin/raspi-config
|
||||||
|
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+60
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# LCD test program using the lcd_class.py
|
||||||
|
# Use this program to test LCD wiring
|
||||||
|
#
|
||||||
|
# $Id: test_lcd.py,v 1.9 2014/06/04 08:31:34 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
import traceback
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
from lcd_class import Lcd
|
||||||
|
stderr = sys.stderr.write;
|
||||||
|
|
||||||
|
lcd = Lcd()
|
||||||
|
|
||||||
|
# Try to trap any exit errors and cleanup GPIO
|
||||||
|
def exit_fn():
|
||||||
|
if not traceback.format_exc().startswith('None'):
|
||||||
|
s=traceback.format_exc()
|
||||||
|
|
||||||
|
# Register
|
||||||
|
atexit.register(exit_fn)
|
||||||
|
|
||||||
|
def interrupt():
|
||||||
|
return False
|
||||||
|
|
||||||
|
boardrevision = 2
|
||||||
|
stderr("Are you using an old revision 1 board y/n: ")
|
||||||
|
answer = raw_input("")
|
||||||
|
if answer == 'y':
|
||||||
|
boardrevision = 1
|
||||||
|
|
||||||
|
print "Use Ctl-C to exit"
|
||||||
|
|
||||||
|
lcd.init(boardrevision)
|
||||||
|
lcd.setWidth(16)
|
||||||
|
lcd.line1("Bob Rathbone")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
lcd.scroll2("Line 2: abcdefghijklmnopqrstuvwxyz 0123456789",interrupt)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print "\nExit"
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.cleanup()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Executable
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# $Id: test_remote_control.py,v 1.1 2015/03/08 11:47:41 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
# IR remote control test
|
||||||
|
#
|
||||||
|
|
||||||
|
import pifacecad.ir
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
def print_ir_code(event):
|
||||||
|
print (event.ir_code)
|
||||||
|
key = event.ir_code
|
||||||
|
if key == 'KEY_VOLUMEUP':
|
||||||
|
exec_cmd('mpc volume +5')
|
||||||
|
if key == 'KEY_VOLUMEDOWN':
|
||||||
|
exec_cmd('mpc volume -5')
|
||||||
|
if key == 'KEY_CHANNELUP':
|
||||||
|
exec_cmd('mpc next')
|
||||||
|
if key == 'KEY_CHANNELDOWN':
|
||||||
|
exec_cmd('mpc prev')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Execute system command
|
||||||
|
def exec_cmd(cmd):
|
||||||
|
p = os.popen(cmd)
|
||||||
|
result = p.readline().rstrip('\n')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
listener = pifacecad.ir.IREventListener(prog="piradio")
|
||||||
|
listener.register('KEY_VOLUMEUP',print_ir_code)
|
||||||
|
listener.register('KEY_VOLUMEDOWN',print_ir_code)
|
||||||
|
listener.register('KEY_CHANNELUP',print_ir_code)
|
||||||
|
listener.register('KEY_CHANNELDOWN',print_ir_code)
|
||||||
|
listener.register('KEY_MENU',print_ir_code)
|
||||||
|
listener.register('KEY_MUTE',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_LEFT',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_RIGHT',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_UP',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_DOWN',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_OK',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_LANGUAGE',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_INFO',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_SLEEP',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_WAKEUP',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_RADIO',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_MEDIA',print_ir_code) # (Pecus)
|
||||||
|
listener.register('KEY_PANDORA',print_ir_code) # (Pecus)
|
||||||
|
print "Activating"
|
||||||
|
listener.activate()
|
||||||
|
|
||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Test push buttons for the Raspberry PI internet radio
|
||||||
|
#
|
||||||
|
# $Id: test_switches.py,v 1.6 2015/07/13 07:25:32 bob Exp $
|
||||||
|
#
|
||||||
|
# Author Bob Rathbone
|
||||||
|
# Web site http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
# Switch definitions
|
||||||
|
MENU_SWITCH = 25
|
||||||
|
LEFT_SWITCH = 14
|
||||||
|
RIGHT_SWITCH = 15
|
||||||
|
UP_SWITCH = 17
|
||||||
|
DOWN_SWITCH = 18
|
||||||
|
MUTE_SWITCH = 4
|
||||||
|
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
|
||||||
|
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
# For rev 1 boards with no inbuilt pull-up/down resistors the
|
||||||
|
# Wire the GPIO inputs to ground via a 10K resistor and uncomment these lines
|
||||||
|
#GPIO.setup(MENU_SWITCH, GPIO.IN)
|
||||||
|
#GPIO.setup(UP_SWITCH, GPIO.IN)
|
||||||
|
#GPIO.setup(DOWN_SWITCH, GPIO.IN)
|
||||||
|
#GPIO.setup(LEFT_SWITCH, GPIO.IN)
|
||||||
|
#GPIO.setup(RIGHT_SWITCH, GPIO.IN)
|
||||||
|
|
||||||
|
# For rev 2 boards with inbuilt pull-up/down resistors the
|
||||||
|
# following lines are used instead of the above, so
|
||||||
|
# there is no need to physically wire the 10k resistors
|
||||||
|
GPIO.setup(MENU_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
GPIO.setup(UP_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
GPIO.setup(DOWN_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
GPIO.setup(LEFT_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
GPIO.setup(RIGHT_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
GPIO.setup(MUTE_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
menu_switch = GPIO.input(MENU_SWITCH)
|
||||||
|
up_switch = GPIO.input(UP_SWITCH)
|
||||||
|
down_switch = GPIO.input(DOWN_SWITCH)
|
||||||
|
left_switch = GPIO.input(LEFT_SWITCH)
|
||||||
|
right_switch = GPIO.input(RIGHT_SWITCH)
|
||||||
|
mute_switch = GPIO.input(MUTE_SWITCH)
|
||||||
|
|
||||||
|
if menu_switch:
|
||||||
|
print "menu_switch"
|
||||||
|
elif up_switch:
|
||||||
|
print "up_switch"
|
||||||
|
elif down_switch:
|
||||||
|
print "down_switch"
|
||||||
|
elif left_switch:
|
||||||
|
print "left_switch"
|
||||||
|
elif right_switch:
|
||||||
|
print "right_switch"
|
||||||
|
elif mute_switch:
|
||||||
|
print "mute_switch"
|
||||||
|
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print "\nExit"
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
GPIO.cleanup()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# End of program
|
||||||
Executable
+420
@@ -0,0 +1,420 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: latin-1 -*-
|
||||||
|
#
|
||||||
|
# Raspberry Pi Radio Character translation class
|
||||||
|
# Escaped characters, html and unicode translation to ascii
|
||||||
|
#
|
||||||
|
# $Id: translate_class.py,v 1.24 2016/04/14 06:37:56 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
# Useful Links on character encodings
|
||||||
|
# http://www.zytrax.com/tech/web/entities.html
|
||||||
|
# http://www.utf8-chartable.de/
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import unicodedata
|
||||||
|
from log_class import Log
|
||||||
|
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
|
||||||
|
class Translate:
|
||||||
|
displayUmlauts = True
|
||||||
|
|
||||||
|
# Escaped codes (from unicode)
|
||||||
|
codes = {
|
||||||
|
'//' : '/', # Double /
|
||||||
|
' ' : ' ', # Double spaces
|
||||||
|
'\\xa0' : ' ', # Line feed to space
|
||||||
|
'\\' : "'", # Double bacslash to apostrophe
|
||||||
|
'\\n' : ' ', # Line feed to space
|
||||||
|
|
||||||
|
# Special characters
|
||||||
|
'\\xc2\\xa1' : '!', # Inverted exclamation
|
||||||
|
'\\xc2\\xa2' : 'c', # Cent sign
|
||||||
|
'\\xc2\\xa3' : '#', # Pound sign
|
||||||
|
'\\xc2\\xa4' : '$', # Currency sign
|
||||||
|
'\\xc2\\xa5' : 'Y', # Yen sign
|
||||||
|
'\\xc2\\xa6' : '|', # Broken bar
|
||||||
|
'\\xc2\\xa7' : '?', # Section sign
|
||||||
|
'\\xc2\\xa8' : ':', # Diaerisis
|
||||||
|
'\\xc2\\xa9' : '(C)', # Copyright
|
||||||
|
'\\xc2\\xaa' : '?', # Feminal ordinal
|
||||||
|
'\\xc2\\xab' : '<<', # Double left
|
||||||
|
'\\xc2\\xac' : '-', # Not sign
|
||||||
|
'\\xc2\\xad' : '', # Soft hyphen
|
||||||
|
'\\xc2\\xae' : '(R)', # Registered sign
|
||||||
|
'\\xc2\\xaf' : '-', # Macron
|
||||||
|
'\\xc2\\xb0' : 'o', # Degrees sign
|
||||||
|
'\\xc2\\xb1' : '+-', # Plus minus
|
||||||
|
'\\xc2\\xb2' : '2', # Superscript 2
|
||||||
|
'\\xc2\\xb3' : '3', # Superscript 3
|
||||||
|
'\\xc2\\xb4' : '', # Acute accent
|
||||||
|
'\\xc2\\xb5' : 'u', # Micro sign
|
||||||
|
'\\xc2\\xb6' : '', # Pilcrow
|
||||||
|
'\\xc2\\xb7' : '.', # Middle dot
|
||||||
|
'\\xc2\\xb8' : '', # Cedilla
|
||||||
|
'\\xc2\\xb9' : '1', # Superscript 1
|
||||||
|
'\\xc2\\xba' : '', # Masculine indicator
|
||||||
|
'\\xc2\\xbb' : '>>', # Double right
|
||||||
|
'\\xc2\\xbc' : '1/4', # 1/4 fraction
|
||||||
|
'\\xc2\\xbd' : '1/2', # 1/2 Fraction
|
||||||
|
'\\xc2\\xbe' : '3/4', # 3/4 Fraction
|
||||||
|
'\\xc2\\xbf' : '?', # Inverted ?
|
||||||
|
|
||||||
|
# German unicode escape sequences
|
||||||
|
'\\xc3\\x83' : chr(223), # Sharp s es-zett
|
||||||
|
'\\xc3\\x9f' : chr(223), # Sharp s ?
|
||||||
|
'\\xc3\\xa4' : chr(228), # a umlaut
|
||||||
|
'\\xc3\\xb6' : chr(246), # o umlaut
|
||||||
|
'\\xc3\\xbc' : chr(252), # u umlaut
|
||||||
|
'\\xc3\\x84' : chr(196), # A umlaut
|
||||||
|
'\\xc3\\x96' : chr(214), # O umlaut
|
||||||
|
'\\xc3\\x9c' : chr(220), # U umlaut
|
||||||
|
|
||||||
|
# Norwegian unicode escape sequences
|
||||||
|
'\\xc3\\x98' : 'O', # Oslash
|
||||||
|
'\\xc3\\xb8' : 'o', # Oslash
|
||||||
|
'\\xc3\\x85' : 'A', # Aring
|
||||||
|
'\\xc3\\x93' : 'O', # O grave
|
||||||
|
'\\xc3\\xa5' : 'a', # aring
|
||||||
|
'\\xc3\\x86' : 'AE', # AElig
|
||||||
|
'\\xc3\\x98' : 'O', # O crossed
|
||||||
|
'\\xc3\\x99' : 'U', # U grave
|
||||||
|
'\\xc3\\xa6' : 'ae', # aelig
|
||||||
|
'\\xc3\\xb0' : 'o', # o umlaut
|
||||||
|
'\\xc3\\xb3' : 'o', # o tilde
|
||||||
|
'\\xc3\\xb8' : 'o', # oslash
|
||||||
|
'\\xc2\\x88' : 'A', # aelig
|
||||||
|
'\\xc2\\xb4' : 'A', # aelig
|
||||||
|
|
||||||
|
# French (Latin) unicode escape sequences
|
||||||
|
'\\xc3\\x80' : 'A', # A grave
|
||||||
|
'\\xc3\\x81' : 'A', # A acute
|
||||||
|
'\\xc3\\x82' : 'A', # A circumflex
|
||||||
|
'\\xc3\\x83' : 'A', # A tilde
|
||||||
|
'\\xc3\\x88' : 'E', # E grave
|
||||||
|
'\\xc3\\x89' : 'E', # E acute
|
||||||
|
'\\xc3\\x8a' : 'E', # E circumflex
|
||||||
|
'\\xc3\\xa0' : chr(224), # a grave
|
||||||
|
'\\xc3\\xa1' : chr(225), # a acute
|
||||||
|
'\\xc3\\xa2' : chr(226), # a circumflex
|
||||||
|
'\\xc3\\xa8' : chr(232), # e grave
|
||||||
|
'\\xc3\\xa9' : chr(233), # e acute
|
||||||
|
'\\xc3\\xaa' : chr(234), # e circumflex
|
||||||
|
'\\xc3\\xb6' : "'", # Hyphon
|
||||||
|
'\\xc3\\xb7' : "/", # Division sign
|
||||||
|
|
||||||
|
# Hungarian lower case
|
||||||
|
'\\xc3\\xb3' : chr(243), #
|
||||||
|
'\\xc3\\xad' : chr(237), #
|
||||||
|
'\\xc3\\xb5' : chr(245), #
|
||||||
|
'\\xc5\\x91' : chr(245), #
|
||||||
|
'\\xc5\\xb1' : chr(252), #
|
||||||
|
'\\xc3\\xba' : chr(250), # Ã
|
||||||
|
|
||||||
|
# Polish unicode escape sequences
|
||||||
|
'\\xc4\\x84' : 'A', # A,
|
||||||
|
'\\xc4\\x85' : 'a', # a,
|
||||||
|
'\\xc4\\x86' : 'C', # C'
|
||||||
|
'\\xc4\\x87' : 'c', # c'
|
||||||
|
'\\xc4\\x98' : 'E', # E,
|
||||||
|
'\\xc4\\x99' : 'e', # e,
|
||||||
|
'\\xc5\\x81' : 'L', # L/
|
||||||
|
'\\xc5\\x82' : 'l', # l/
|
||||||
|
'\\xc5\\x83' : 'N', # N'
|
||||||
|
'\\xc5\\x84' : 'n', # n'
|
||||||
|
'\\xc5\\x9a' : 'S', # S'
|
||||||
|
'\\xc5\\x9b' : 's', # s'
|
||||||
|
'\\xc5\\xb9' : 'Z', # Z'
|
||||||
|
'\\xc5\\xba' : 'z', # z'
|
||||||
|
'\\xc5\\xbb' : 'Z', # Z.
|
||||||
|
'\\xc5\\xbc' : 'z', # z.
|
||||||
|
|
||||||
|
# Greek upper case
|
||||||
|
'\\xce\\x91' : 'A', # Alpha
|
||||||
|
'\\xce\\x92' : 'B', # Beta
|
||||||
|
'\\xce\\x93' : 'G', # Gamma
|
||||||
|
'\\xce\\x94' : 'D', # Delta
|
||||||
|
'\\xce\\x95' : 'E', # Epsilon
|
||||||
|
'\\xce\\x96' : 'Z', # Zeta
|
||||||
|
'\\xce\\x97' : 'H', # Eta
|
||||||
|
'\\xce\\x98' : 'TH', # Theta
|
||||||
|
'\\xce\\x99' : 'I', # Iota
|
||||||
|
'\\xce\\x9a' : 'K', # Kappa
|
||||||
|
'\\xce\\x9b' : 'L', # Lamda
|
||||||
|
'\\xce\\x9c' : 'M', # Mu
|
||||||
|
'\\xce\\x9e' : 'N', # Nu
|
||||||
|
'\\xce\\x9f' : 'O', # Omicron
|
||||||
|
'\\xce\\xa0' : 'Pi', # Pi
|
||||||
|
'\\xce ' : 'Pi', # Pi ?
|
||||||
|
'\\xce\\xa1' : 'R', # Rho
|
||||||
|
'\\xce\\xa3' : 'S', # Sigma
|
||||||
|
'\\xce\\xa4' : 'T', # Tau
|
||||||
|
'\\xce\\xa5' : 'Y', # Upsilon
|
||||||
|
'\\xce\\xa6' : 'F', # Fi
|
||||||
|
'\\xce\\xa7' : 'X', # Chi
|
||||||
|
'\\xce\\xa8' : 'PS', # Psi
|
||||||
|
'\\xce\\xa9' : 'O', # Omega
|
||||||
|
|
||||||
|
# Greek lower case
|
||||||
|
'\\xce\\xb1' : 'a', # Alpha
|
||||||
|
'\\xce\\xb2' : 'b', # Beta
|
||||||
|
'\\xce\\xb3' : 'c', # Gamma
|
||||||
|
'\\xce\\xb4' : 'd', # Delta
|
||||||
|
'\\xce\\xb5' : 'e', # Epsilon
|
||||||
|
'\\xce\\xb6' : 'z', # Zeta
|
||||||
|
'\\xce\\xb7' : 'h', # Eta
|
||||||
|
'\\xce\\xb8' : 'th', # Theta
|
||||||
|
'\\xce\\xb9' : 'i', # Iota
|
||||||
|
'\\xce\\xba' : 'k', # Kappa
|
||||||
|
'\\xce\\xbb' : 'l', # Lamda
|
||||||
|
'\\xce\\xbc' : 'm', # Mu
|
||||||
|
'\\xce\\xbd' : 'v', # Nu
|
||||||
|
'\\xce\\xbe' : 'ks', # Xi
|
||||||
|
'\\xce\\xbf' : 'o', # Omicron
|
||||||
|
'\\xce\\xc0' : 'p', # Pi
|
||||||
|
'\\xce\\xc1' : 'r', # Rho
|
||||||
|
'\\xce\\xc3' : 's', # Sigma
|
||||||
|
'\\xce\\xc4' : 't', # Tau
|
||||||
|
'\\xce\\xc5' : 'y', # Upsilon
|
||||||
|
'\\xce\\xc6' : 'f', # Fi
|
||||||
|
'\\xce\\xc7' : 'x', # Chi
|
||||||
|
'\\xce\\xc8' : 'ps', # Psi
|
||||||
|
'\\xce\\xc9' : 'o', # Omega
|
||||||
|
|
||||||
|
# Currency other special character
|
||||||
|
'\\xa3' : chr(156), # UK pound sign
|
||||||
|
'\\xa9' : chr(169), # Copyright
|
||||||
|
|
||||||
|
# German short hex representation
|
||||||
|
'\\xdf' : chr(223), # Sharp s es-zett
|
||||||
|
'\\xe4' : chr(228), # a umlaut
|
||||||
|
'\\xf6' : chr(246), # o umlaut
|
||||||
|
'\\xfc' : chr(252), # u umlaut
|
||||||
|
'\\xc4' : chr(196), # A umlaut
|
||||||
|
'\\xd6' : chr(214), # O umlaut
|
||||||
|
'\\xdc' : chr(220), # U umlaut
|
||||||
|
|
||||||
|
# Spanish and French
|
||||||
|
'\\xe0' : chr(224), # Small a reverse acute
|
||||||
|
'\\xe1' : chr(225), # Small a acute
|
||||||
|
'\\xe2' : chr(226), # Small audo bashcircumflex
|
||||||
|
'\\xe7' : chr(231), # Small c Cedilla
|
||||||
|
'\\xe8' : chr(232), # Small e grave
|
||||||
|
'\\xe9' : chr(233), # Small e acute
|
||||||
|
'\\xea' : chr(234), # Small e circumflex
|
||||||
|
'\\xeb' : chr(235), # Small e diarisis
|
||||||
|
'\\xed' : chr(237), # Small i acute
|
||||||
|
'\\xee' : chr(238), # Small i circumflex
|
||||||
|
'\\xf1' : chr(241), # Small n tilde
|
||||||
|
'\\xf3' : chr(243), # Small o acute
|
||||||
|
'\\xf4' : chr(244), # Small o circumflex
|
||||||
|
'\\xf9' : chr(249), # Small u circumflex
|
||||||
|
'\\xfa' : chr(250), # Small u acute
|
||||||
|
'\\xfb' : chr(251), # u circumflex
|
||||||
|
|
||||||
|
'\\xc0' : chr(192), # Small A grave
|
||||||
|
'\\xc1' : chr(193), # Capital A acute
|
||||||
|
|
||||||
|
'\\xc7' : chr(199), # Capital C Cedilla
|
||||||
|
'\\xc9' : chr(201), # Capital E acute
|
||||||
|
'\\xcd' : chr(205), # Capital I acute
|
||||||
|
'\\xd3' : chr(211), # Capital O acute
|
||||||
|
'\\xda' : chr(218), # Capital U acute
|
||||||
|
|
||||||
|
'\\xbf' : chr(191), # Spanish Punctuation
|
||||||
|
|
||||||
|
'xb0' : 'o', # Degres symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
HtmlCodes = {
|
||||||
|
# Currency
|
||||||
|
chr(156) : '#', # Pound by hash
|
||||||
|
chr(169) : '(c)', # Copyright
|
||||||
|
|
||||||
|
# Norwegian
|
||||||
|
chr(216) : 'O', # Oslash
|
||||||
|
|
||||||
|
# Spanish french
|
||||||
|
chr(241) : 'n', # Small tilde n
|
||||||
|
chr(191) : '?', # Small u acute to u
|
||||||
|
chr(224) : 'a', # Small a grave to a
|
||||||
|
chr(225) : 'a', # Small a acute to a
|
||||||
|
chr(226) : 'a', # Small a circumflex to a
|
||||||
|
chr(232) : 'e', # Small e grave to e
|
||||||
|
chr(233) : 'e', # Small e acute to e
|
||||||
|
chr(234) : 'e', # Small e circumflex to e
|
||||||
|
chr(235) : 'e', # Small e diarisis to e
|
||||||
|
chr(237) : 'i', # Small i acute to i
|
||||||
|
chr(238) : 'i', # Small i circumflex to i
|
||||||
|
chr(243) : 'o', # Small o acute to o
|
||||||
|
chr(244) : 'o', # Small o circumflex to o
|
||||||
|
chr(250) : 'u', # Small u acute to u
|
||||||
|
chr(251) : 'u', # Small u circumflex to u
|
||||||
|
chr(192) : 'A', # Capital A grave to A
|
||||||
|
chr(193) : 'A', # Capital A acute to A
|
||||||
|
chr(201) : 'E', # Capital E acute to E
|
||||||
|
chr(205) : 'I', # Capital I acute to I
|
||||||
|
chr(209) : 'N', # Capital N acute to N
|
||||||
|
chr(211) : 'O', # Capital O acute to O
|
||||||
|
chr(218) : 'U', # Capital U acute to U
|
||||||
|
chr(220) : 'U', # Capital U umlaut to U
|
||||||
|
chr(231) : 'c', # Small c Cedilla
|
||||||
|
chr(199) : 'C', # Capital C Cedilla
|
||||||
|
|
||||||
|
# German
|
||||||
|
chr(196) : "Ae", # A umlaut
|
||||||
|
chr(214) : "Oe", # O umlaut
|
||||||
|
chr(220) : "Ue", # U umlaut
|
||||||
|
}
|
||||||
|
|
||||||
|
unicodes = {
|
||||||
|
'\\u201e' : '"', # ORF feed
|
||||||
|
'\\u3000' : " ",
|
||||||
|
'\\u201c' : '"',
|
||||||
|
'\\u201d' : '"',
|
||||||
|
'\\u0153' : "oe", # French oe
|
||||||
|
'\\u2009' : ' ', # Short space to space
|
||||||
|
'\\u2013' : '-', # Long dash to minus sign
|
||||||
|
'\\u2019' : "'", # French apostrophe
|
||||||
|
|
||||||
|
# Polish unicodes (I don't know why, but works :) ) (Pecus)
|
||||||
|
"'u0104" : "A", # A, (Pecus)
|
||||||
|
"'u0105" : "a", # a, (Pecus)
|
||||||
|
"'u0106" : "C", # C' (Pecus)
|
||||||
|
"'u0107" : "c", # c' (Pecus)
|
||||||
|
"'u0118" : "E", # E, (Pecus)
|
||||||
|
"'u0119" : "e", # e, (Pecus)
|
||||||
|
"'u0141" : "L", # L/ (Pecus)
|
||||||
|
"'u0142" : "l", # l/ (Pecus)
|
||||||
|
"'u0143" : "N", # N' (Pecus)
|
||||||
|
"'u0144" : "n", # n' (Pecus)
|
||||||
|
"'xd3" : "O", # O' (Pecus)
|
||||||
|
"'xf3" : "o", # o' (Pecus)
|
||||||
|
"'u015a" : "S", # S' (Pecus)
|
||||||
|
"'u015b" : "s", # s' (Pecus)
|
||||||
|
"'u0179" : "Z", # Z' (Pecus)
|
||||||
|
"'u017a" : "z", # z' (Pecus)
|
||||||
|
"'u017b" : "Z", # Z. (Pecus)
|
||||||
|
"'u017c" : "z", # z. (Pecus)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
log.init('radio')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Translate all
|
||||||
|
def all(self,text):
|
||||||
|
s = self._convert2escape(text)
|
||||||
|
s = self._escape(s)
|
||||||
|
s = self._unicode(s)
|
||||||
|
s = self._html(s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Convert unicode to escape codes
|
||||||
|
def _convert2escape(self,text):
|
||||||
|
s = repr(text)
|
||||||
|
if s.__len__() > 2:
|
||||||
|
s= s[1:-1] # Strip ' characters
|
||||||
|
s = s.lstrip("'")
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Convert escaped characters (umlauts) to normal characters
|
||||||
|
def escape(self,text):
|
||||||
|
s = self._convert2escape(text)
|
||||||
|
s = self._escape(s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Convert escaped characters (umlauts etc.) to normal characters
|
||||||
|
def _escape(self,text):
|
||||||
|
s = text
|
||||||
|
for code in self.codes:
|
||||||
|
s = s.replace(code, self.codes[code])
|
||||||
|
s = s.replace("'oC",'oC') # Degrees C fudge
|
||||||
|
s = s.replace("'oF",'oF') # Degrees C fudge
|
||||||
|
return s
|
||||||
|
|
||||||
|
# HTML translations (callable)
|
||||||
|
def html(self,text):
|
||||||
|
s = self._html(s)
|
||||||
|
_convert_html(s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
# HTML translations
|
||||||
|
def _html(self,text):
|
||||||
|
s = text
|
||||||
|
s = s.replace('<', '<')
|
||||||
|
s = s.replace('>', '>')
|
||||||
|
s = s.replace('"', '"')
|
||||||
|
s = s.replace(' ', ' ')
|
||||||
|
s = s.replace('&', '&')
|
||||||
|
s = s.replace('©', '(c)')
|
||||||
|
s = s.replace(''', "'") # ' in html (like RSS) (Pecus)
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Convert &#nn sequences
|
||||||
|
def _convert_html(s):
|
||||||
|
c = re.findall('&#[0-9][0-9][0-9]', s)
|
||||||
|
c += re.findall('&#[0-9][0-9]', s)
|
||||||
|
for html in c:
|
||||||
|
ch = int(html.replace('&#', ''))
|
||||||
|
if ch > 31 and ch < 127:
|
||||||
|
s = s.replace(html,chr(ch))
|
||||||
|
else:
|
||||||
|
s = s.replace(html,'')
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Unicodes etc (callable)
|
||||||
|
def unicode(self,text):
|
||||||
|
s = self._convert2escape(text)
|
||||||
|
s = self._unicode(s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Unicodes etc
|
||||||
|
def _unicode(self,text):
|
||||||
|
s = text
|
||||||
|
for unicode in self.unicodes:
|
||||||
|
s = s.replace(unicode, self.unicodes[unicode])
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Decode greek
|
||||||
|
def decode_greek(self,text):
|
||||||
|
s = text.decode('macgreek')
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Display umlats as oe ae etc
|
||||||
|
def displayUmlauts(self,value):
|
||||||
|
self.displayUmlauts = value
|
||||||
|
return
|
||||||
|
|
||||||
|
# Translate special characters (umlautes etc) to LCD values
|
||||||
|
# See standard character patterns for LCD display
|
||||||
|
def toLCD(self,sp):
|
||||||
|
s = sp
|
||||||
|
for HtmlCode in self.HtmlCodes:
|
||||||
|
s = s.replace(HtmlCode, self.HtmlCodes[HtmlCode])
|
||||||
|
|
||||||
|
if self.displayUmlauts:
|
||||||
|
s = s.replace(chr(223), chr(226)) # Sharp s
|
||||||
|
s = s.replace(chr(246), chr(239)) # o umlaut (Problem in Hungarian?)
|
||||||
|
s = s.replace(chr(228), chr(225)) # a umlaut
|
||||||
|
s = s.replace(chr(252), chr(245)) # u umlaut (Problem in Hungarian?)
|
||||||
|
else:
|
||||||
|
s = s.replace(chr(228), "ae") # a umlaut
|
||||||
|
s = s.replace(chr(223), "ss") # Sharp s
|
||||||
|
s = s.replace(chr(246), "oe") # o umlaut
|
||||||
|
s = s.replace(chr(252), "ue") # u umlaut
|
||||||
|
return s
|
||||||
|
|
||||||
|
# End of class
|
||||||
Executable
BIN
Binary file not shown.
Executable
+118
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Raspberry Pi TCPIP server class
|
||||||
|
# $Id: udp_server_class.py,v 1.5 2015/11/08 09:29:05 bob Exp $
|
||||||
|
#
|
||||||
|
# Author : Bob Rathbone
|
||||||
|
# Site : http://www.bobrathbone.com
|
||||||
|
#
|
||||||
|
# This program uses the Python socket server
|
||||||
|
# See https://docs.python.org/2/library/socketserver.html
|
||||||
|
#
|
||||||
|
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
|
||||||
|
#
|
||||||
|
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
|
||||||
|
# The authors shall not be liable for any loss or damage however caused.
|
||||||
|
#
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import SocketServer
|
||||||
|
from log_class import Log
|
||||||
|
|
||||||
|
log = Log()
|
||||||
|
|
||||||
|
PORT = 5100
|
||||||
|
HOST = '0.0.0.0'
|
||||||
|
callback = None
|
||||||
|
client_data = ""
|
||||||
|
|
||||||
|
# Class to handle the data requests
|
||||||
|
class RequestHandler(SocketServer.BaseRequestHandler):
|
||||||
|
# Client connection event
|
||||||
|
def setup(self):
|
||||||
|
log.message("UDP server client connect", log.DEBUG)
|
||||||
|
|
||||||
|
# Handle the data request
|
||||||
|
def handle(self):
|
||||||
|
global callback
|
||||||
|
global client_data
|
||||||
|
socket = None
|
||||||
|
try:
|
||||||
|
client_data = self.request[0].strip()
|
||||||
|
socket = self.request[1]
|
||||||
|
log.message("UDP Server received: " + client_data, Log.DEBUG)
|
||||||
|
callback() # Call the get data routine
|
||||||
|
socket.sendto('OK', self.client_address)
|
||||||
|
except:
|
||||||
|
log.message("UDP RequestHandler error", Log.ERROR)
|
||||||
|
socket.sendto('NOTOK', self.client_address)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle client disconnect
|
||||||
|
def finish(self):
|
||||||
|
log.message("UDP server client disconnect", Log.DEBUG)
|
||||||
|
|
||||||
|
# Handle time out
|
||||||
|
def handle_timeout(self):
|
||||||
|
log.message("UDP server server timeout", Log.DEBUG)
|
||||||
|
|
||||||
|
class UDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
|
||||||
|
pass # We will override class methods
|
||||||
|
port = PORT
|
||||||
|
host = HOST
|
||||||
|
|
||||||
|
# Listen for incomming connections
|
||||||
|
def listen(self,server, mycallback):
|
||||||
|
global log
|
||||||
|
log.init('radio')
|
||||||
|
global callback
|
||||||
|
callback = mycallback # Set up the callback
|
||||||
|
# Start a thread with the server
|
||||||
|
server_thread = threading.Thread(target=server.serve_forever)
|
||||||
|
# Exit the server thread when the main thread terminates
|
||||||
|
server_thread.daemon = True
|
||||||
|
server_thread.name = 'remote'
|
||||||
|
server_thread.timeout = 2
|
||||||
|
server_thread.start()
|
||||||
|
msg = "UDP listen:" + server_thread.name + " " + str(self.host) + " port " + str(self.port)
|
||||||
|
log.message(msg, Log.INFO)
|
||||||
|
return
|
||||||
|
|
||||||
|
def getServerAddress(self):
|
||||||
|
return (self.host,self.port)
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
global client_data
|
||||||
|
data = client_data
|
||||||
|
client_data = ''
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Test UDP server class
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
server = None
|
||||||
|
|
||||||
|
# Call back routine to get data event
|
||||||
|
def callback():
|
||||||
|
global server
|
||||||
|
print "Data =", server.getData()
|
||||||
|
return
|
||||||
|
|
||||||
|
server = UDPServer((HOST, PORT), RequestHandler)
|
||||||
|
server.serve_forever()
|
||||||
|
print "Listening", server.fileno()
|
||||||
|
|
||||||
|
server.listen(server,callback)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
server.shutdown()
|
||||||
|
server.server_close()
|
||||||
|
log.message("Exiting remote listener", Log.INFO)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# End of class
|
||||||
Executable
BIN
Binary file not shown.
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
espeak -ven+f2 -k5 -s150 -a
|
||||||
Reference in New Issue
Block a user