├── .gitignore
├── CHANGELOG
├── COPYING
├── MANIFEST.in
├── README.md
├── bin
├── lirc_hardware.conf
├── pifacecadsysinfo.sh
├── setup-lirc.py
└── setup_pifacecad_lirc.sh
├── docs
├── Makefile
├── conf.py
├── creating_custom_bitmaps.rst
├── example.rst
├── images
│ └── lirc.png
├── included_examples.rst
├── index.rst
├── installation.rst
├── libpeerconnection.log
├── lirc.dia
├── lirc.rst
├── reference.rst
├── reference_tools.rst
└── tools.rst
├── examples
├── hangman.py
├── radio.py
├── radiolircrc
├── sysinfo.py
├── traintimes.py
├── tweets.py
└── weather.py
├── pifacecad
├── __init__.py
├── core.py
├── ir.py
├── lcd.py
├── tools
│ ├── __init__.py
│ ├── question.py
│ └── scanf.py
└── version.py
├── requirements.txt
├── setup.py
└── tests
├── test_custombitmap.py
├── test_irandswitches.py
├── test_irint.py
├── test_pifacecadtools.py
├── test_switches.py
├── testlircrc
└── tests.py
/.gitignore:
--------------------------------------------------------------------------------
1 | dpkg-files
2 | makedebpkg.sh
3 | *.deb
4 | _build
5 | *.log
6 | MANIFEST
7 | deb_dist
8 |
9 | # databases
10 | *.db
11 |
12 | *.py[cod]
13 |
14 | # C extensions
15 | *.so
16 |
17 | # Packages
18 | *.egg
19 | *.egg-info
20 | dist
21 | build
22 | eggs
23 | parts
24 | var
25 | sdist
26 | develop-eggs
27 | .installed.cfg
28 | lib
29 | lib64
30 | __pycache__
31 |
32 | # Installer logs
33 | pip-log.txt
34 |
35 | # Unit test / coverage reports
36 | .coverage
37 | .tox
38 | nosetests.xml
39 |
40 | # Translations
41 | *.mo
42 |
43 | # Mr Developer
44 | .mr.developer.cfg
45 | .project
46 | .pydevproject
47 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | v2.0.8
5 | ------
6 | - Updated LIRC setup script so that it works for Kernel >= v3.18.5+
7 |
8 | v2.0.7
9 | ------
10 | - Fixed bug in sysinfo, using floats instead.
11 |
12 | v2.0.6
13 | ------
14 | - Added reference to examples location after installation instructions.
15 | - Added included examples section in docs.
16 |
17 | v2.0.5
18 | ------
19 | - Fixed a bug in LCDQuestion.
20 |
21 | v2.0.4
22 | ------
23 | - Fixed a typo in the docs.
24 |
25 | v2.0.3
26 | ------
27 | - Updated documentation. Examples page, creating custom bitmaps and tools.
28 | - Fixed bug in LCDQuestion.
29 |
30 | v2.0.2
31 | ------
32 | - Updated lirc docs.
33 |
34 | v2.0.1
35 | ------
36 | - Fixed init bug where display_control and other vars would not exist.
37 |
38 | v2.0.0
39 | ------
40 | - Updated for compatibility with pifacecommon v4.0.0.
41 | - All occurrences of `board_num` have been replaced with `hardware_addr` which
42 | is more appropriate.
43 | - PiFace Control and Display is initialised when you instantiate the object.
44 | **You do not need to call pifacecad.init() any more.**
45 | - PiFaceCAD inherits registers from MCP23S17 so you can access all the
46 | registers on the chip. For example GPPUP:
47 |
48 | >>> cad = PiFaceCAD()
49 | >>> cad.gppub.value = 0xff
50 | >>> cad.gppub.bits[4].value = 1
51 |
52 | - SwitchEventListener now requires that you provide a chip object, not
53 | a hardware_addr (board_num). This defaults to PiFaceCAD(hardware_addr=0).
54 | - Interrupt events contain a chip object representing the calling chip instead
55 | of the hardware_addr (board_num). You can still access the
56 | hardware_addr (board_num) like so:
57 |
58 | >>> def my_callback(event):
59 | ... print(event.chip.hardware_addr)
60 |
61 | - Removed the Switch class. It added arbitrary restrictions and
62 | was not very useful. `pifacecommon.mcp23s17.MCP23S17RegisterBit` is more
63 | appropriate.
64 | - Updated examples to reflect new changes.
65 | - See the docs (particularly the examples) for more information.
66 |
67 | v1.1.3 *ignore*
68 | ---------------
69 | - Fixed weather.py example (import sys).
70 |
71 | v1.1.2 *ignore*
72 | ---------------
73 | - Fixed toggle_mask bug and updated test_switches.
74 |
75 | v1.1.1 *ignore*
76 | ---------------
77 | - Updated for compatibility with pifacecommon v4.0.0.
78 | *v2.0.0 note: this is a lie, the 4.0.0 version of pifacecommon that
79 | this point talks about was never merged into master. Ignore all
80 | versions of pifacecad up until v2.0.0*
81 |
82 | v1.1.0
83 | ------
84 | - Added bus and chip select.
85 | - Added board detection and optional board initialisation.
86 | - Added IODIR_FALLING_EDGE and IODIR_RISING_EDGE (see pifacecommon v3.1.1).
87 | - Updated radio example to use a more sensible `prog` name for LIRC and to
88 | use the custom lircrc file at the Debian examples location. Also added
89 | mplayer detection and ability to operate without lirc configured.
90 | - Fixed Python 2 support in tools.scanf.
91 | - Limited support for some examples (Radio, Weather and Train Times are
92 | Python 3 only). Feel free to add in support and submit a patch. :)
93 |
94 | v1.0.0
95 | ------
96 | - Initial release.
97 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGELOG COPYING README.md
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | pifacecad
2 | =========
3 |
4 | PiFace Control and Display Python module.
5 |
6 | Contrast is adjusted with screwdriver + screw by IR node.
7 |
8 |
9 | Documentation
10 | =============
11 |
12 | [http://pifacecad.readthedocs.org/](http://pifacecad.readthedocs.org/)
13 |
14 | You can also find the documentation and some examples installed at:
15 |
16 | /usr/share/doc/python3-pifacecad/
17 |
18 | Install
19 | =======
20 |
21 | Make sure you are using the latest version of Raspbian:
22 |
23 | $ sudo apt-get update
24 | $ sudo apt-get upgrade
25 |
26 | Install `pifacecad` (for Python 3 and 2) with the following command:
27 |
28 | $ sudo apt-get install python{,3}-pifacecad
29 |
30 | Test by running the `sysinfo.py` program:
31 |
32 | $ python3 /usr/share/doc/python3-pifacecad/examples/sysinfo.py
33 |
34 | You will need to [configure the IR receiver](http://pifacecad.readthedocs.org/en/latest/lirc.html).
35 |
36 | Install on Raspbian Strech and RaspberryPi 3
37 | ============================================
38 |
39 | The piface packages are currently not in the main repositories. You can,
40 | however, simply build them from their sources:
41 |
42 | * Enable the spi-interface:
43 |
44 | ```
45 | $ sudo raspi-config
46 | Interfacing Options -> SPI -> Yes
47 | ```
48 | * https://github.com/tompreston/python-lirc
49 | ```
50 | $ sudo aptitude install liblircclient-dev cython gcc python{,3}-setuptools python{,3}-dev
51 | $ git clone https://github.com/tompreston/python-lirc.git
52 | $ cd python-lirc/
53 | $ make py3 && sudo python3 setup.py install
54 | $ make py2 && sudo python setup.py install
55 | ```
56 | * https://github.com/piface/pifacecommon
57 | ```
58 | $ git clone https://github.com/piface/pifacecommon.git
59 | $ cd pifacecommon/
60 | $ sudo python setup.py install
61 | $ sudo python3 setup.py install
62 | ```
63 | * https://github.com/piface/pifacecad
64 | ```
65 | $ git clone https://github.com/piface/pifacecad.git
66 | $ cd pifacecad/
67 | $ sudo python setup.py install
68 | $ sudo python3 setup.py install
69 | ```
70 | * run the hello world demo:
71 | ```
72 | >>> import pifacecad
73 |
74 | >>> cad = pifacecad.PiFaceCAD() # create PiFace Control and Display object
75 | >>> cad.lcd.backlight_on() # turns the backlight on
76 | >>> cad.lcd.write("Hello, world!") # writes hello world on to the LCD
77 | ```
78 | * Cleanup:
79 | ```
80 | $ sudo rm -rf pifacecad/ pifacecommon/ python-lirc/
81 | ```
82 |
--------------------------------------------------------------------------------
/bin/lirc_hardware.conf:
--------------------------------------------------------------------------------
1 | # /etc/lirc/hardware.conf
2 | #
3 | # Arguments which will be used when launching lircd
4 | LIRCD_ARGS="--uinput"
5 | #
6 | #Don't start lircmd even if there seems to be a good config file
7 | #START_LIRCMD=false
8 | #
9 | #Don't start irexec, even if a good config file seems to exist.
10 | #START_IREXEC=false
11 | #
12 | #Try to load appropriate kernel modules
13 | LOAD_MODULES=true
14 | #
15 | # Run "lircd --driver=help" for a list of supported drivers.
16 | DRIVER="default"
17 | # usually /dev/lirc0 is the correct setting for systems using udev
18 | DEVICE="/dev/lirc0"
19 | MODULES="lirc_rpi"
20 | #
21 | # Default configuration files for your hardware if any
22 | LIRCD_CONF=""
23 | LIRCMD_CONF=""
24 |
--------------------------------------------------------------------------------
/bin/pifacecadsysinfo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ### BEGIN INIT INFO
3 | # Provides: pifacecadsysinfo
4 | # Required-Start: $remote_fs $syslog
5 | # Required-Stop: $remote_fs $syslog
6 | # Default-Start: 2 3 4 5
7 | # Default-Stop: 0 1 6
8 | # Short-Description: Start pifacecad status daemon at boot time.
9 | # Description: Start pifacecad status daemon at boot time.
10 | ### END INIT INFO
11 |
12 | LOCKFILE="/var/lock/pifacecad_status_service.lock"
13 | SYSINFO_FILE="/usr/share/doc/python3-pifacecad/examples/sysinfo.py"
14 |
15 | start() {
16 | echo -n "Starting PiFace CAD status service: "
17 | /usr/bin/python3 $SYSINFO_FILE &
18 | ### Create the lock file ###
19 | echo $! > $LOCKFILE
20 | status
21 | }
22 |
23 | stop() {
24 | echo -n "Stopping PiFace CAD status service: "
25 | pid=$(cat $LOCKFILE)
26 | kill $pid
27 | # Now, delete the lock file ###
28 | rm -f $LOCKFILE
29 | # clean up the screen
30 | /usr/bin/python3 $SYSINFO_FILE clear &
31 | status
32 | }
33 |
34 | status() {
35 | if [ -e $LOCKFILE ]
36 | then
37 | echo "[Running]"
38 | else
39 | echo "[Stopped]"
40 | fi
41 | }
42 |
43 | ### main logic ###
44 | case "$1" in
45 | start)
46 | start
47 | ;;
48 | stop)
49 | stop
50 | ;;
51 | status)
52 | status
53 | ;;
54 | restart|reload|force-reload)
55 | stop
56 | start
57 | ;;
58 | *)
59 | echo $"Usage: $0 {start|stop|restart|reload|force-reload|status}"
60 | exit 1
61 | esac
62 | exit 0
63 |
--------------------------------------------------------------------------------
/bin/setup-lirc.py:
--------------------------------------------------------------------------------
1 | def setup_lirc():
2 | # setup /etc/modules
3 | # check if lines are already there
4 | write_lirc_dev = write_lirc_rpi = True
5 | with open('/etc/modules', 'r') as modfile:
6 | for line in modfile:
7 | if "lirc_dev" in line:
8 | write_lirc_dev = False
9 | if "lirc_rpi gpio_in_pin=23" in line:
10 | write_lirc_rpi = False
11 | # write them
12 | if write_lirc_dev or write_lirc_rpi:
13 | with open('/etc/modules', 'a') as modfile:
14 | if write_lirc_dev:
15 | modfile.write("lirc_dev\n")
16 | if write_lirc_rpi:
17 | modfile.write("lirc_rpi gpio_in_pin=23\n")
18 |
19 | # setup /etc/lirc/hardware.conf
20 | shutil.copyfile(
21 | "/etc/lirc/hardware.conf", "/etc/lirc/hardware.conf.backup")
22 | # TODO make this grab the file from the web
23 | shutil.copyfile("lirc_hardware.conf", "/etc/lirc/hardware.conf")
24 | os.chmod(
25 | "/etc/lirc/hardware.conf",
26 | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
27 | stat.S_IRGRP | stat.S_IXGRP |
28 | stat.S_IROTH | stat.S_IXOTH)
29 |
30 | # install lirc
31 | run_cmd(INSTALL_LIRC_CMD, "Failed to install lirc.")
32 |
33 | # point to documentation
34 | print(
35 | "\n\n\tYou will need to generate a key map for your remote.\n"
36 | "\tSee: {}\n\n".format(KEY_MAP_INSTRUCTIONS))
37 |
38 |
39 | if __name__ == '__main__':
40 | setup_lirc()
41 |
--------------------------------------------------------------------------------
/bin/setup_pifacecad_lirc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #: Description: Configures Raspberry Pi for the IR Recevier on
3 | #: PiFace Control and Display.
4 |
5 | MODULES_FILE="/etc/modules"
6 | HARDWARECONF_FILE="/etc/lirc/hardware.conf"
7 | LIRCD_FILE="/etc/lirc/lircd.conf"
8 | BOOTCONFIG="/boot/config.txt"
9 |
10 | #=======================================================================
11 | # NAME: version less than or equal to
12 | # DESCRIPTION: True if version in first arg <= version in second arg
13 | #=======================================================================
14 | verlte() {
15 | [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
16 | }
17 |
18 | #=======================================================================
19 | # NAME: version less than
20 | # DESCRIPTION: True if version in first arg < version in second arg
21 | #=======================================================================
22 | verlt() {
23 | [ "$1" = "$2" ] && return 1 || verlte $1 $2
24 | }
25 |
26 | #=======================================================================
27 | # NAME: issue_warning
28 | # DESCRIPTION: Issues a warning about running this script.
29 | #=======================================================================
30 | issue_warning() {
31 | echo "This script will overwrite the following files:"
32 | echo $HARDWARECONF_FILE
33 | if [ verlt $(uname -r) 3.18 ]; then
34 | echo $MODULES_FILE
35 | else
36 | echo $BOOTCONFIG
37 | fi
38 | echo "Do you wish to continue?"
39 | select yn in "Yes" "No"; do
40 | case $yn in
41 | Yes ) break;;
42 | No ) exit;;
43 | esac
44 | done
45 | }
46 |
47 | #=======================================================================
48 | # NAME: backup_file
49 | # DESCRIPTION: Creates a backup of a file.
50 | #=======================================================================
51 | backup_file() {
52 | echo "Backing up $1."
53 | cp $1 $1.setup_pifacecad_lirc.backup
54 | }
55 |
56 | #=======================================================================
57 | # NAME: install_lirc
58 | # DESCRIPTION: Install LIRC.
59 | #=======================================================================
60 | install_lirc() {
61 | echo "Installing LIRC."
62 | apt-get install --assume-yes lirc || \
63 | { echo 'Installing LIRC failed.' ; exit 1; }
64 | }
65 |
66 | #=======================================================================
67 | # NAME: setup_modules
68 | # DESCRIPTION: Add appropriate kernel modules.
69 | #=======================================================================
70 | setup_modules() {
71 | if [ verlt $(uname -r) 3.18 ]; then
72 | # old way for enable modules
73 | echo "Configuring modules."
74 | backup_file $MODULES_FILE
75 | # add "lirc_dev" and "lirc_rpi gpio_in_pin=23" to $MODULES_FILE
76 | for line in "lirc_dev" "lirc_rpi gpio_in_pin=23"; do
77 | if ! grep -q "$line" $MODULES_FILE; then
78 | echo "$line" >> $MODULES_FILE
79 | fi
80 | done
81 | else
82 | # new way to enable IR (kernel v3.18+)
83 | line="dtoverlay=lirc-rpi,gpio_in_pin=23,gpio_in_pull=high"
84 | backup_file $BOOTCONFIG
85 | if ! grep -q "$line" $BOOTCONFIG; then
86 | echo "$line" >> $BOOTCONFIG
87 | fi
88 | fi
89 | }
90 |
91 | #=======================================================================
92 | # NAME: setup_hardwareconf
93 | # DESCRIPTION: Copies hardware conf
94 | #=======================================================================
95 | setup_hardwareconf() {
96 | echo "Configuring hardware."
97 | backup_file $HARDWARECONF_FILE
98 |
99 | # write to the hardware conf
100 | cat << EOF > $HARDWARECONF_FILE
101 | # /etc/lirc/hardware.conf
102 | #
103 | # Arguments which will be used when launching lircd
104 | LIRCD_ARGS="--uinput"
105 | #
106 | #Don't start lircmd even if there seems to be a good config file
107 | #START_LIRCMD=false
108 | #
109 | #Don't start irexec, even if a good config file seems to exist.
110 | #START_IREXEC=false
111 | #
112 | #Try to load appropriate kernel modules
113 | LOAD_MODULES=true
114 | #
115 | # Run "lircd --driver=help" for a list of supported drivers.
116 | DRIVER="default"
117 | # usually /dev/lirc0 is the correct setting for systems using udev
118 | DEVICE="/dev/lirc0"
119 | MODULES="lirc_rpi"
120 | #
121 | # Default configuration files for your hardware if any
122 | LIRCD_CONF=""
123 | LIRCMD_CONF=""
124 | EOF
125 | }
126 |
127 | #=======================================================================
128 | # NAME: final_message
129 | # DESCRIPTION: Install
130 | #=======================================================================
131 | final_message() {
132 | printf "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
133 | Now you must create an $LIRCD_FILE for your remote control.
134 | Either download one from here:
135 |
136 | http://lirc.sourceforge.net/remotes/
137 |
138 | Or generate one yourself with the following command:
139 |
140 | sudo irrecord -f -d /dev/lirc0 /etc/lirc/lircd.conf
141 |
142 | For more information go to:
143 |
144 | http://piface.github.io/pifacecad/lirc.html#configuring-lirc
145 |
146 | You will need to reboot.
147 | "
148 | }
149 |
150 | #=======================================================================
151 | # MAIN
152 | #=======================================================================
153 | # check if the script is being run as root
154 | if [[ $EUID -ne 0 ]]
155 | then
156 | printf 'This script must be run as root.\nExiting..\n'
157 | exit 1
158 | fi
159 |
160 | issue_warning
161 | install_lirc
162 | setup_modules
163 | setup_hardwareconf
164 | final_message
165 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PiFaceControlandDisplayCAD.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PiFaceControlandDisplayCAD.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PiFaceControlandDisplayCAD"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PiFaceControlandDisplayCAD"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # PiFace Control and Display (CAD) documentation build configuration file, created by
5 | # sphinx-quickstart on Fri Jun 21 12:41:57 2013.
6 | #
7 | # This file is execfile()d with the current directory set to its containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys, os
16 |
17 | # If extensions (or modules to document with autodoc) are in another directory,
18 | # add these directories to sys.path here. If the directory is relative to the
19 | # documentation root, use os.path.abspath to make it absolute, like shown here.
20 | sys.path.insert(0, os.path.abspath('..'))
21 | try:
22 | import pifacecommon
23 | except ImportError:
24 | sys.path.insert(0, os.path.abspath('../../pifacecommon/'))
25 |
26 | # mock add lirc
27 | # from unittest.mock import MagicMock
28 | from mock import Mock as MagicMock
29 |
30 | class Mock(MagicMock):
31 | @classmethod
32 | def __getattr__(cls, name):
33 | return Mock()
34 |
35 | MOCK_MODULES = ['lirc']
36 | sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
37 |
38 | # -- General configuration -----------------------------------------------------
39 |
40 | # If your documentation needs a minimal Sphinx version, state it here.
41 | #needs_sphinx = '1.0'
42 |
43 | # Add any Sphinx extension module names here, as strings. They can be extensions
44 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
45 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage']
46 |
47 | # Add any paths that contain templates here, relative to this directory.
48 | templates_path = ['_templates']
49 |
50 | # The suffix of source filenames.
51 | source_suffix = '.rst'
52 |
53 | # The encoding of source files.
54 | #source_encoding = 'utf-8-sig'
55 |
56 | # The master toctree document.
57 | master_doc = 'index'
58 |
59 | # General information about the project.
60 | project = 'PiFace Control and Display (CAD)'
61 | copyright = '2013, Thomas Preston'
62 |
63 | # The version info for the project you're documenting, acts as replacement for
64 | # |version| and |release|, also used in various other places throughout the
65 | # built documents.
66 | #
67 | # The short X.Y version.
68 | import pifacecad.version
69 | version = pifacecad.version.__version__
70 | # The full version, including alpha/beta/rc tags.
71 | release = pifacecad.version.__version__
72 |
73 | # The language for content autogenerated by Sphinx. Refer to documentation
74 | # for a list of supported languages.
75 | #language = None
76 |
77 | # There are two options for replacing |today|: either, you set today to some
78 | # non-false value, then it is used:
79 | #today = ''
80 | # Else, today_fmt is used as the format for a strftime call.
81 | #today_fmt = '%B %d, %Y'
82 |
83 | # List of patterns, relative to source directory, that match files and
84 | # directories to ignore when looking for source files.
85 | exclude_patterns = ['_build']
86 |
87 | # The reST default role (used for this markup: `text`) to use for all documents.
88 | #default_role = None
89 |
90 | # If true, '()' will be appended to :func: etc. cross-reference text.
91 | #add_function_parentheses = True
92 |
93 | # If true, the current module name will be prepended to all description
94 | # unit titles (such as .. function::).
95 | #add_module_names = True
96 |
97 | # If true, sectionauthor and moduleauthor directives will be shown in the
98 | # output. They are ignored by default.
99 | #show_authors = False
100 |
101 | # The name of the Pygments (syntax highlighting) style to use.
102 | pygments_style = 'sphinx'
103 |
104 | # A list of ignored prefixes for module index sorting.
105 | #modindex_common_prefix = []
106 |
107 |
108 | # -- Options for HTML output ---------------------------------------------------
109 |
110 | # on_rtd is whether we are on readthedocs.org
111 | import os
112 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
113 |
114 | if not on_rtd: # only import and set the theme if we're building docs locally
115 | import sphinx_rtd_theme
116 | html_theme = 'sphinx_rtd_theme'
117 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
118 |
119 | # otherwise, readthedocs.org uses their theme by default, so no need to
120 | # specify it
121 |
122 | # The theme to use for HTML and HTML Help pages. See the documentation for
123 | # a list of builtin themes.
124 | # html_theme = 'default'
125 |
126 | # Theme options are theme-specific and customize the look and feel of a theme
127 | # further. For a list of options available for each theme, see the
128 | # documentation.
129 | #html_theme_options = {}
130 |
131 | # Add any paths that contain custom themes here, relative to this directory.
132 | #html_theme_path = []
133 |
134 | # The name for this set of Sphinx documents. If None, it defaults to
135 | # " v documentation".
136 | #html_title = None
137 |
138 | # A shorter title for the navigation bar. Default is the same as html_title.
139 | #html_short_title = None
140 |
141 | # The name of an image file (relative to this directory) to place at the top
142 | # of the sidebar.
143 | #html_logo = None
144 |
145 | # The name of an image file (within the static path) to use as favicon of the
146 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
147 | # pixels large.
148 | #html_favicon = None
149 |
150 | # Add any paths that contain custom static files (such as style sheets) here,
151 | # relative to this directory. They are copied after the builtin static files,
152 | # so a file named "default.css" will overwrite the builtin "default.css".
153 | html_static_path = ['_static']
154 |
155 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
156 | # using the given strftime format.
157 | #html_last_updated_fmt = '%b %d, %Y'
158 |
159 | # If true, SmartyPants will be used to convert quotes and dashes to
160 | # typographically correct entities.
161 | #html_use_smartypants = True
162 |
163 | # Custom sidebar templates, maps document names to template names.
164 | #html_sidebars = {}
165 |
166 | # Additional templates that should be rendered to pages, maps page names to
167 | # template names.
168 | #html_additional_pages = {}
169 |
170 | # If false, no module index is generated.
171 | #html_domain_indices = True
172 |
173 | # If false, no index is generated.
174 | #html_use_index = True
175 |
176 | # If true, the index is split into individual pages for each letter.
177 | #html_split_index = False
178 |
179 | # If true, links to the reST sources are added to the pages.
180 | #html_show_sourcelink = True
181 |
182 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
183 | #html_show_sphinx = True
184 |
185 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
186 | #html_show_copyright = True
187 |
188 | # If true, an OpenSearch description file will be output, and all pages will
189 | # contain a tag referring to it. The value of this option must be the
190 | # base URL from which the finished HTML is served.
191 | #html_use_opensearch = ''
192 |
193 | # This is the file name suffix for HTML files (e.g. ".xhtml").
194 | #html_file_suffix = None
195 |
196 | # Output file base name for HTML help builder.
197 | htmlhelp_basename = 'PiFaceControlandDisplayCADdoc'
198 |
199 |
200 | # -- Options for LaTeX output --------------------------------------------------
201 |
202 | latex_elements = {
203 | # The paper size ('letterpaper' or 'a4paper').
204 | #'papersize': 'letterpaper',
205 |
206 | # The font size ('10pt', '11pt' or '12pt').
207 | #'pointsize': '10pt',
208 |
209 | # Additional stuff for the LaTeX preamble.
210 | #'preamble': '',
211 | }
212 |
213 | # Grouping the document tree into LaTeX files. List of tuples
214 | # (source start file, target name, title, author, documentclass [howto/manual]).
215 | latex_documents = [
216 | ('index', 'PiFaceControlandDisplayCAD.tex', 'PiFace Control and Display (CAD) Documentation',
217 | 'Thomas Preston', 'manual'),
218 | ]
219 |
220 | # The name of an image file (relative to this directory) to place at the top of
221 | # the title page.
222 | #latex_logo = None
223 |
224 | # For "manual" documents, if this is true, then toplevel headings are parts,
225 | # not chapters.
226 | #latex_use_parts = False
227 |
228 | # If true, show page references after internal links.
229 | #latex_show_pagerefs = False
230 |
231 | # If true, show URL addresses after external links.
232 | #latex_show_urls = False
233 |
234 | # Documents to append as an appendix to all manuals.
235 | #latex_appendices = []
236 |
237 | # If false, no module index is generated.
238 | #latex_domain_indices = True
239 |
240 |
241 | # -- Options for manual page output --------------------------------------------
242 |
243 | # One entry per manual page. List of tuples
244 | # (source start file, name, description, authors, manual section).
245 | man_pages = [
246 | ('index', 'pifacecontrolanddisplaycad', 'PiFace Control and Display (CAD) Documentation',
247 | ['Thomas Preston'], 1)
248 | ]
249 |
250 | # If true, show URL addresses after external links.
251 | #man_show_urls = False
252 |
253 |
254 | # -- Options for Texinfo output ------------------------------------------------
255 |
256 | # Grouping the document tree into Texinfo files. List of tuples
257 | # (source start file, target name, title, author,
258 | # dir menu entry, description, category)
259 | texinfo_documents = [
260 | ('index', 'PiFaceControlandDisplayCAD', 'PiFace Control and Display (CAD) Documentation',
261 | 'Thomas Preston', 'PiFaceControlandDisplayCAD', 'One line description of project.',
262 | 'Miscellaneous'),
263 | ]
264 |
265 | # Documents to append as an appendix to all manuals.
266 | #texinfo_appendices = []
267 |
268 | # If false, no module index is generated.
269 | #texinfo_domain_indices = True
270 |
271 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
272 | #texinfo_show_urls = 'footnote'
273 |
274 |
275 | # Example configuration for intersphinx: refer to the Python standard library.
276 | intersphinx_mapping = {'http://docs.python.org/': None}
277 |
278 | todo_include_todos = True
--------------------------------------------------------------------------------
/docs/creating_custom_bitmaps.rst:
--------------------------------------------------------------------------------
1 | #######################
2 | Creating Custom Bitmaps
3 | #######################
4 |
5 | You can store up to eight custom bitmaps using PiFace Control and Display. Each
6 | bitmap describes a single character on the LCD screen (5x8 pixels).
7 |
8 | To create a custom bitmap you instantiate an :class:`LCDBitmap` which takes a list
9 | of binary values each describing a line. For example::
10 |
11 | >>> quaver = pifacecad.LCDBitmap([0, 2, 3, 2, 14, 30, 12, 0])
12 |
13 | or as hex::
14 |
15 | >>> quaver = pifacecad.LCDBitmap([0x0, 0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0])
16 |
17 | or as binary (you can almost see the bitmap image here)::
18 |
19 | >>> quaver = pifacecad.LCDBitmap([0b00000,
20 | ... 0b00010,
21 | ... 0b00011,
22 | ... 0b00010,
23 | ... 0b01110,
24 | ... 0b11110,
25 | ... 0b01100,
26 | ... 0b00000])
27 |
28 | .. note:: You can use `this tool `_
29 | to help design custom bitmaps. Make sure you select 5x8.
30 |
31 | You store and display custom bitmaps with the following commands::
32 |
33 | >>> cad = pifacecad.PiFaceCAD()
34 | >>> cad.lcd.store_custom_bitmap(0, quaver)
35 | >>> cad.lcd.write_custom_bitmap(0)
36 |
37 | Here is a complete code example::
38 |
39 | >>> import pifacecad
40 | >>> cad = pifacecad.PiFaceCAD()
41 | >>> quaver = pifacecad.LCDBitmap([0x0, 0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0])
42 | >>> cad.lcd.store_custom_bitmap(0, quaver)
43 | >>> cad.lcd.write_custom_bitmap(0)
44 |
--------------------------------------------------------------------------------
/docs/example.rst:
--------------------------------------------------------------------------------
1 | ########
2 | Examples
3 | ########
4 |
5 | Basic usage
6 | ===========
7 |
8 | Hello, World!::
9 |
10 | >>> import pifacecad
11 |
12 | >>> cad = pifacecad.PiFaceCAD() # create PiFace Control and Display object
13 | >>> cad.lcd.backlight_on() # turns the backlight on
14 | >>> cad.lcd.write("Hello, world!") # writes hello world on to the LCD
15 |
16 | Reading the switches::
17 |
18 | >>> cad.switches[3].value # reads the value of switch 3
19 | 1
20 | >>> cad.switch_port.value # reads the value of the switch port
21 | 4
22 |
23 | Cursor control::
24 |
25 | >>> cad.lcd.set_cursor(4, 1) # set the cursor to col 4 on the second row
26 | >>> cad.lcd.cursor_off() # turns the cursor off
27 | >>> cad.lcd.write("3.141592") # writes π to the LCD
28 |
29 | >>> cad.lcd.blink_off() # turns the blinking off
30 | >>> cad.lcd.cursor_on() # turns the cursor on
31 | >>> cad.lcd.home() # send the cursor home
32 |
33 | >>> cad.lcd.write("PiFace Control\nand Display") # '\n' starts a new line
34 | >>> cad.lcd.clear() # clear the screen (also sends the cursor home)
35 |
36 | The LCD has more RAM than just the 16x2 characters that you can see::
37 |
38 | >>> cad.lcd.write("Something really, really long.")
39 | >>> cad.lcd.move_right()
40 | >>> cad.lcd.move_left()
41 | >>> cad.lcd.see_cursor() # move the display so that we can see the cursor
42 |
43 | The `viewport_corner` variable describes which column the top left display
44 | character is showing from RAM::
45 |
46 | >>> cad.lcd.viewport_corner # inspect the viewport_corner variable
47 | 8
48 | >>> cad.lcd.viewport_corner = 15 # set the viewport_corner variable
49 |
50 | You can also `create your own custom bitmaps `_.
51 |
52 | IR Receiver
53 | ===========
54 |
55 | You need to add your LIRC client (your program) to ~/.lircrc::
56 |
57 | $ cat ~/.lircrc
58 | begin
59 | prog = pifacecadexample
60 | button = 1
61 | config = one
62 | end
63 |
64 | begin
65 | prog = pifacecadexample
66 | button = 2
67 | config = two
68 | end
69 |
70 | Then, register IR codes to functions using :class:`IREventListener`::
71 |
72 | >>> import pifacecad
73 |
74 | >>> def print_ir_code(event):
75 | ... print(event.ir_code)
76 | ...
77 | >>> pifacecad.PiFaceCAD() # initialise a PiFace Control and Display board
78 | >>> listener = pifacecad.IREventListener(prog="pifacecadexample")
79 | >>> listener.register('one', print_ir_code)
80 | >>> listener.register('two', print_ir_code)
81 | >>> listener.activate()
82 |
83 | Now when you press 1 or 2 on your remote, "one" or "two" is printed.
84 |
85 | Interrupts
86 | ==========
87 |
88 | Instead of polling the switches we can use the :class:`SwitchEventListener` to
89 | register actions that we wish to be called on certain switch events.
90 |
91 | >>> import pifacecad
92 | >>> def update_pin_text(event):
93 | ... event.chip.lcd.set_cursor(13, 0)
94 | ... event.chip.lcd.write(str(event.pin_num))
95 | ...
96 | >>> cad = pifacecad.PiFaceCAD()
97 | >>> cad.lcd.write("You pressed: ")
98 | >>> listener = pifacecad.SwitchEventListener(chip=cad)
99 | >>> for i in range(8):
100 | ... listener.register(i, pifacecad.IODIR_FALLING_EDGE, update_pin_text)
101 | >>> listener.activate()
102 |
103 | The screen should update as buttons are pressed. To stop the listener, call
104 | it's ``deactivate`` method:
105 |
106 | >>> listener.deactivate()
107 |
108 |
109 | The :class:`Event` object has some interesting attributes. You can access them
110 | like so::
111 |
112 | >>> import pifacecad
113 | >>> cad = pifacecad.PiFaceCAD()
114 | >>> listener = pifacecad.SwitchEventListener(chip=cad)
115 | >>> listener.register(0, pifacecad.IODIR_RISING_EDGE, print)
116 | >>> listener.activate()
117 |
118 | This would print out the event informaion whenever you unpress switch 0::
119 |
120 | interrupt_flag: 0b1
121 | interrupt_capture: 0b11111111
122 | pin_num: 0
123 | direction: 1
124 | chip:
125 | timestamp: 1380893579.447889
126 |
--------------------------------------------------------------------------------
/docs/images/lirc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piface/pifacecad/7b938b77f80d27836e8a22651b368fc5c72273fa/docs/images/lirc.png
--------------------------------------------------------------------------------
/docs/included_examples.rst:
--------------------------------------------------------------------------------
1 | Included Examples
2 | =================
3 | The PiFace Control and Display Python libraries include some example programs
4 | at ``/usr/share/doc/python3-pifacecad/examples/``. You are encouraged to
5 | modify and improve them.
6 |
7 | You can also find the examples on `GitHub `_.
8 |
9 | .. note:: Some of the included examples are zipped (`.gz`) and require
10 | unzipping before they can be used.
11 |
12 | Internet Radio
13 | --------------
14 | An internet radio with IR remote control features.
15 |
16 | .. note:: In order to use the infrared remote control features of the radio
17 | you must first `configure LIRC `_ and configure the
18 | buttons in ``/usr/share/doc/python3-pifacecad/examples/radiolircrc``
19 | so that they match the buttons on your remote (in
20 | ``/etc/lirc/lircd.conf``).
21 |
22 | The internet radio requires the program ``mplayer`` to be installed in order to
23 | play audio streams. Install it with::
24 |
25 | $ sudo apt-get install mplayer
26 |
27 | Unzip the radio::
28 |
29 | $ gunzip /usr/share/doc/python3-pifacecad/examples/radio.py.gz
30 |
31 | Run the radio::
32 |
33 | $ python3 /usr/share/doc/python3-pifacecad/examples/radio.py
34 |
35 | ================ ==================
36 | Button Function
37 | ================ ==================
38 | Navigation left Previous station
39 | Navigation right Next station
40 | Navigation in Stop/Start playing
41 | Control 0 Station Preset 0
42 | Control 1 Station Preset 1
43 | Control 2 Station Preset 2
44 | Control 3 Station Preset 3
45 | Control 4 Exit
46 | ================ ==================
47 |
48 | Hangman
49 | -------
50 | A game of hangman. Guess which letters are in the word. To many incorrect
51 | guesses and, well, you know the rest.
52 |
53 | Unzip and run hangman::
54 |
55 | $ gunzip /usr/share/doc/python3-pifacecad/examples/radio.py.gz
56 | $ python3 /usr/share/doc/python3-pifacecad/examples/radio.py
57 |
58 | ================ =========================
59 | Button Function
60 | ================ =========================
61 | Navigation left Move cursor/Change letter
62 | Navigation right Move cursor/Change letter
63 | Navigation in Change mode/enter
64 | ================ =========================
65 |
66 | See hangman in action on `YouTube `_.
67 |
68 | Traintimes
69 | ----------
70 | Shows UK train times from Manchester Picadilly (MAN) to various other stations.
71 | Have a peek at the code to the alter which station you are departing from.
72 |
73 | ``traintimes.py`` depends on `Beautiful Soup 4 `_. Install it with::
74 |
75 | $ sudo apt-get install python3-bs4
76 |
77 | Then unzip and run traintimes::
78 |
79 | $ gunzip /usr/share/doc/python3-pifacecad/examples/traintimes.py.gz
80 | $ python3 /usr/share/doc/python3-pifacecad/examples/traintimes.py
81 |
82 | ================ ============================
83 | Button Function
84 | ================ ============================
85 | Navigation left Previous destination station
86 | Navigation right Next destination station
87 | Navigation in Refresh
88 | Control 4 Exit
89 | ================ ============================
90 |
91 | Tweets
92 | ------
93 | Shows latest tweets.
94 |
95 | Twitter requires the `Python Twitter Tools `_
96 | module to be installed::
97 |
98 | $ sudo apt-get install easy_install3
99 | $ sudo easy_install3 twitter
100 |
101 | Unzip and run tweets::
102 |
103 | $ gunzip /usr/share/doc/python3-pifacecad/examples/tweets.py.gz
104 | $ python3 /usr/share/doc/python3-pifacecad/examples/tweets.py
105 |
106 | ================ ==============
107 | Button Function
108 | ================ ==============
109 | Navigation left Previous tweet
110 | Navigation right Next tweet
111 | Navigation in Refresh
112 | Control 4 Exit
113 | ================ ==============
114 |
115 | Weather
116 | -------
117 | Shows current weather information. Unzip and run weather::
118 |
119 | $ gunzip /usr/share/doc/python3-pifacecad/examples/weather.py.gz
120 | $ python3 /usr/share/doc/python3-pifacecad/examples/weather.py
121 |
122 | ================ =================
123 | Button Function
124 | ================ =================
125 | Navigation left Previous location
126 | Navigation right Next location
127 | Navigation in Refresh
128 | Control 4 Exit
129 | ================ =================
130 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. PiFace Control and Display (CAD) documentation master file, created by
2 | sphinx-quickstart on Fri Jun 21 12:41:57 2013.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to PiFace Control and Display (CAD)'s documentation!
7 | ============================================================
8 |
9 | The pifacecad Python module provides functions and classes for interacting
10 | with PiFace Control and Display.
11 |
12 | .. note:: You may need to fiddle with the contrast settings in order to see
13 | characters on the screen. Turn the small screw located by the GPIO pins on
14 | PiFaceCAD.
15 |
16 | PiFace Control and Display has eight inputs switches, and IR receiver and an
17 | `HD44780 `_ LCD.
18 |
19 | Links:
20 | - `Blog `_
21 | - `GitHub `_
22 | - `C Library `_
23 | - `Custom Bitmaps Generator `_
24 |
25 | Contents:
26 |
27 | .. toctree::
28 | :maxdepth: 2
29 |
30 | installation
31 | example
32 | included_examples
33 | creating_custom_bitmaps
34 | tools
35 | lirc
36 | reference
37 | reference_tools
38 |
39 | Indices and tables
40 | ==================
41 |
42 | * :ref:`genindex`
43 | * :ref:`modindex`
44 | * :ref:`search`
45 |
46 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | ############
2 | Installation
3 | ############
4 |
5 | Make sure you are using the lastest version of Raspbian::
6 |
7 | $ sudo apt-get update
8 | $ sudo apt-get upgrade
9 |
10 | Install ``pifacecad`` (for Python 3 and 2) with the following command::
11 |
12 | $ sudo apt-get install python{,3}-pifacecad
13 |
14 | Test by running the ``sysinfo.py`` program::
15 |
16 | $ python3 /usr/share/doc/python3-pifacecad/examples/sysinfo.py
17 |
18 | You will need to `configure the IR receiver `_ yourself. More examples can be found in `/usr/share/doc/python3-pifacecad/examples/` (which may need unzipping using `gunzip`).
19 |
20 | SysInfo Service
21 | ===============
22 |
23 | You can run `sysinfo` as a service using::
24 |
25 | $ sudo service pifacecadsysinfo start
26 |
27 | You can stop the service with::
28 |
29 | $ sudo service pifacecadsysinfo stop
30 |
31 | You can enable the service to run at boot (handy for headless Raspberry Pi's)::
32 |
33 | $ sudo update-rc.d pifacecadsysinfo defaults
34 |
--------------------------------------------------------------------------------
/docs/libpeerconnection.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piface/pifacecad/7b938b77f80d27836e8a22651b368fc5c72273fa/docs/libpeerconnection.log
--------------------------------------------------------------------------------
/docs/lirc.dia:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piface/pifacecad/7b938b77f80d27836e8a22651b368fc5c72273fa/docs/lirc.dia
--------------------------------------------------------------------------------
/docs/lirc.rst:
--------------------------------------------------------------------------------
1 | ####
2 | LIRC
3 | ####
4 |
5 | This page contains some information about `Linux Infrared Remote Control
6 | (LIRC) `_ and how to set it up with PiFace CAD and
7 | the Raspberry Pi.
8 |
9 | LIRC looks something like this:
10 |
11 | .. image:: images/lirc.png
12 | :width: 813px
13 | :height: 182px
14 | :align: center
15 | :alt: An image of PiFace Digital with a simple circuit connected.
16 |
17 |
18 | Setting up the Infrared Receiver
19 | ================================
20 | Download and run the setup script::
21 |
22 | $ wget https://raw.github.com/piface/pifacecad/master/bin/setup_pifacecad_lirc.sh
23 | $ chmod +x setup_pifacecad_lirc.sh
24 | $ sudo ./setup_pifacecad_lirc.sh
25 |
26 | Alternatively you can set up the receiver manually.
27 |
28 | .. note:: The following instructions are for a manual set up of the above
29 | script. If you have run the script above, you can skip straight
30 | to :ref:`configuring-lirc`.
31 |
32 | First, install lirc::
33 |
34 | $ sudo apt-get install lirc
35 |
36 | The latest version of Raspbian should contain the ``lirc_rpi`` kernel module.
37 | It allows you to specify which GPIO pin the Infrared Receiver is attached to.
38 | On PiFace CAD, the Infrared Receiver is connected to GPIO pin 23. To load the
39 | module type::
40 |
41 | $ sudo modprobe lirc_rpi gpio_in_pin=23
42 |
43 | You can test that it works with the ``mode2`` program::
44 |
45 | $ sudo kill $(pidof lirc) # stop lirc using /dev/lirc0
46 | $ mode2 -d /dev/lirc0
47 |
48 | If, after pressing some buttons, you get a series of pulse/space lengths then
49 | your Infrared Receiver is working. To make sure that the module is loaded
50 | each time you boot you need one of two files depedning on which kernel
51 | version you are using.
52 |
53 | If you're using a kernel older than v3.18 then add the following lines to ``/etc/modules``::
54 |
55 | lirc_dev
56 | lirc_rpi gpio_in_pin=23
57 |
58 | If you're using a kernel newer than v3.18 then add the following line to ``/boot/config.txt``::
59 |
60 | dtoverlay=lirc-rpi,gpio_in_pin=23,gpio_in_pull=high
61 |
62 | Update ``/etc/lirc/hardware.conf`` to contain the following::
63 |
64 | # /etc/lirc/hardware.conf
65 | #
66 | # Arguments which will be used when launching lircd
67 | LIRCD_ARGS="--uinput"
68 |
69 | #Don't start lircmd even if there seems to be a good config file
70 | #START_LIRCMD=false
71 |
72 | #Don't start irexec, even if a good config file seems to exist.
73 | #START_IREXEC=false
74 |
75 | #Try to load appropriate kernel modules
76 | LOAD_MODULES=true
77 |
78 | # Run "lircd --driver=help" for a list of supported drivers.
79 | DRIVER="default"
80 | # usually /dev/lirc0 is the correct setting for systems using udev
81 | DEVICE="/dev/lirc0"
82 | MODULES="lirc_rpi"
83 |
84 | # Default configuration files for your hardware if any
85 | LIRCD_CONF=""
86 | LIRCMD_CONF=""
87 |
88 |
89 | Finally, reboot your Rasbperry Pi::
90 |
91 | $ sudo reboot
92 |
93 |
94 | .. _configuring-lirc:
95 |
96 | Configuring LIRC
97 | ================
98 |
99 | See also: http://www.lirc.org/html/configure.html
100 |
101 | lircd.conf
102 | ----------
103 |
104 | The ``/etc/lirc/lircd.conf`` file tells LIRC about your remote control. Since every
105 | remote control is different, you need to generate a different config for each
106 | remote. Alternatively you could try and find your remote control config file
107 | here: http://lirc.sourceforge.net/remotes/.
108 |
109 | To generate your own configuration run::
110 |
111 | $ sudo irrecord -f -d /dev/lirc0 /etc/lirc/lircd.conf
112 |
113 | and carefully follow the on-screen instructions. At some point it will ask you
114 | to enter the commands for each button you press. You can list the available
115 | commands (in another terminal) with::
116 |
117 | $ irrecord --list-namespace
118 |
119 | After you have finished, restart the lirc daemon (or reboot) and test your
120 | remote by running::
121 |
122 | $ irw
123 |
124 | Your commands should appear in the console.
125 |
126 | ~/.lircrc (or /etc/lirc/lircrc)
127 | -------------------------------
128 |
129 | The ``~/.lircrc`` file is used to configure what other programs see from LIRC.
130 | See examples in :ref:`ref-irexec` and :ref:`ref-python-lirc`.
131 |
132 | See also: http://www.lirc.org/html/configure.html#lircrc_format
133 |
134 | .. _ref-irexec:
135 |
136 | irexec
137 | ======
138 |
139 | `irexec `_ is a program that runs commands mapped to IR signals. It is configured using the ~/.lircrc file.
140 |
141 | Simple ~/.lircrc::
142 |
143 | begin
144 | prog = irexec
145 | button = KEY_1
146 | config = echo "You pressed one"
147 | repeat = 0
148 | end
149 |
150 | Now when you run ``irexec`` and press 1 on the remote control *You pressed one*
151 | will be printed to the console::
152 |
153 | $ irexec
154 | You pressed one
155 | You pressed one
156 | You pressed one
157 |
158 | Here is another example that uses ``mpc`` to control `Music Player Daemon
159 | `_::
160 |
161 | begin
162 | prog = irexec
163 | button = KEY_PREVIOUSSONG
164 | config = mpc prev
165 | repeat = 0
166 | end
167 | begin
168 | prog = irexec
169 | button = KEY_NEXTSONG
170 | config = mpc next
171 | repeat = 0
172 | end
173 | begin
174 | prog = irexec
175 | button = KEY_PLAY
176 | config = mpc play
177 | repeat = 1
178 | end
179 |
180 | .. _ref-python-lirc:
181 |
182 | python-lirc
183 | ===========
184 |
185 | `python-lirc `_ is a Python
186 | extension that allows us to access configs in ~/.lircrc when LIRC receives
187 | a signal.
188 |
189 | First we need to add more configurations to our ~/.lircrc::
190 |
191 | begin
192 | prog = irexec
193 | button = KEY_1
194 | config = echo "You pressed one"
195 | repeat = 0
196 | end
197 |
198 | begin
199 | prog = myprogram
200 | button = KEY_1
201 | config = one
202 | end
203 |
204 | begin
205 | prog = myprogram
206 | button = KEY_1
207 | config = two
208 | end
209 |
210 | Then we can wait for IR codes in Python::
211 |
212 | $ python3
213 | >>> import lirc
214 | >>> sockid = lirc.init("myprogram")
215 | >>> lirc.nextcode() # press 1 on remote after this
216 | ['one']
217 | >>> lirc.nextcode() # press 2 on remote after this
218 | ['two']
219 |
220 | PiFace CAD
221 | ==========
222 |
223 | PiFace Control and Display provides a wrapper around python-lirc. An example of
224 | how to use it can be found in `Examples `_.
225 |
--------------------------------------------------------------------------------
/docs/reference.rst:
--------------------------------------------------------------------------------
1 | ######################
2 | Reference (PiFace CAD)
3 | ######################
4 |
5 | pifacecad.core
6 | ==============
7 |
8 | .. automodule:: pifacecad.core
9 | :members:
10 |
11 | pifacecad.ir
12 | ============
13 |
14 | .. automodule:: pifacecad.ir
15 | :members:
16 |
17 |
18 | pifacecad.lcd
19 | =============
20 |
21 | .. automodule:: pifacecad.lcd
22 | :members:
23 |
--------------------------------------------------------------------------------
/docs/reference_tools.rst:
--------------------------------------------------------------------------------
1 | ############################
2 | Reference (PiFace CAD Tools)
3 | ############################
4 |
5 | pifacecad.tools.question
6 | ========================
7 |
8 | .. automodule:: pifacecad.tools.question
9 | :members:
10 |
11 | pifacecad.tools.scanf
12 | ========================
13 |
14 | .. automodule:: pifacecad.tools.scanf
15 | :members:
--------------------------------------------------------------------------------
/docs/tools.rst:
--------------------------------------------------------------------------------
1 | Tools
2 | =====
3 |
4 | There are some tools provided with PiFace Control and Display which simplify
5 | getting input from a user.
6 |
7 | Question
8 | --------
9 | :class:`pifacecad.tools.question.LCDQuestion` will display a question on the
10 | top row and an answer on the bottom. The user can cycle through answers by
11 | moving the navigation switch left and right and select an answer by pressing
12 | the navigation switch in.
13 |
14 | It can be used like this::
15 |
16 | >>> import pifacecad
17 | >>> from pifacecad.tools.question import LCDQuestion
18 |
19 | >>> question = LCDQuestion(question="What is 5 x 2?", answers=["1", "10", "12"])
20 | >>> answer_index = question.ask()
21 |
22 | You can pass in an already initialised Control and Display object and also
23 | specify a custom selector::
24 |
25 | >>> import pifacecad
26 | >>> from pifacecad.tools.question import LCDQuestion
27 |
28 | >>> customcad = pifacecad.PiFaceCAD()
29 | >>> customcad.lcd.cursor_off()
30 | >>> customcad.lcd.blink_off()
31 |
32 | >>> question = LCDQuestion(question="What is 5 x 2?",
33 | ... answers=["1", "10", "12"],
34 | ... selector="#",
35 | ... cad=customcad)
36 | >>> answer_index = question.ask()
37 |
38 | Scanf
39 | -----
40 | :class:`pifacecad.tools.scanf.LCDScanf` will display a custom string that
41 | can be modified using the navigation switch. In 'select' mode, moving the
42 | navigation switch left or right (switches 6 and 7) selects a character.
43 | Change to 'edit' mode by pressing the navigation switch in (switch 5).
44 | In edit mode, moving the navigation switch left or right changes the character.
45 |
46 | To return the input, move the cursor under the 'enter' arrow and press the
47 | navigation switch in.
48 |
49 | The `LCDScanf` class can be used like this::
50 |
51 | >>> import pifacecad
52 | >>> from pifacecad.tools.scanf import LCDScanf
53 |
54 | >>> scanner = LCDScanf("Text: %c%2i%.%r")
55 | >>> print(scanner.scan()) # user enters things on PiFace C&D
56 | ['a', '13', '!']
57 |
58 | The format string defines editable variables using a `%` symbol. The
59 | format specification is::
60 |
61 | c: Characters
62 | C: Capital Characters
63 | i: Integers
64 | d: Integers
65 | x: Hexadecimal
66 | X: Capital Hexadecimal
67 | .: Punctuation
68 | m: Custom (specifed by ``custom_values`` in init args)
69 | r: Return (switch 5 to submit string)
70 |
71 | To add multiple characters in the same variable you can specify a number
72 | after the `%` symbol. For example, to request that the user enter a two
73 | digit integer::
74 |
75 | >>> print(LCDScanf("%2i%r").scan())
76 | ['42']
77 |
78 | You can use the `m` specifier to enter custom variable values::
79 |
80 | >>> scanner = LCDScanf("Animal: %m%r", custom_values=('cat', 'dog', 'fish'))
81 | >>> print(scanner.scan())
82 | ['fish']
83 |
--------------------------------------------------------------------------------
/examples/hangman.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import sys
3 | from time import sleep
4 | from random import randint
5 | import pifacecad
6 | import pifacecad.tools
7 | from pifacecad.lcd import LCD_WIDTH
8 |
9 |
10 | WORDS = ["piface", "raspberrypi", "horses", "eggs", "bacon"]
11 |
12 | TOP_LEFT_INDEX, TOP_MIDDLE_INDEX, TOP_RIGHT_INDEX = range(3)
13 | BOTTOM_LEFT_INDEX, BOTTOM_MIDDLE_INDEX, BOTTOM_RIGHT_INDEX = range(3, 6)
14 |
15 | # hangman is in six squares
16 | HANGMAN_WIDTH = 3
17 | stages = [
18 | # stage 0
19 | {
20 | 'top-left': pifacecad.LCDBitmap(
21 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
22 | 'top-middle': pifacecad.LCDBitmap(
23 | [0x0, 0x0, 0x1c, 0x4, 0x0, 0x0, 0x0, 0x0]),
24 | 'top-right': pifacecad.LCDBitmap(
25 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
26 | 'bottom-left': pifacecad.LCDBitmap(
27 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
28 | 'bottom-middle': pifacecad.LCDBitmap(
29 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f]),
30 | 'bottom-right': pifacecad.LCDBitmap(
31 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
32 | },
33 | # stage 1
34 | {
35 | 'top-left': pifacecad.LCDBitmap(
36 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
37 | 'top-middle': pifacecad.LCDBitmap(
38 | [0x0, 0x0, 0x1c, 0x4, 0x4, 0xe, 0xa, 0xe]),
39 | 'top-right': pifacecad.LCDBitmap(
40 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
41 | 'bottom-left': pifacecad.LCDBitmap(
42 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
43 | 'bottom-middle': pifacecad.LCDBitmap(
44 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f]),
45 | 'bottom-right': pifacecad.LCDBitmap(
46 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
47 | },
48 | # stage 2
49 | {
50 | 'top-left': pifacecad.LCDBitmap(
51 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
52 | 'top-middle': pifacecad.LCDBitmap(
53 | [0x0, 0x0, 0x1c, 0x4, 0x4, 0xe, 0xa, 0xe]),
54 | 'top-right': pifacecad.LCDBitmap(
55 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
56 | 'bottom-left': pifacecad.LCDBitmap(
57 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
58 | 'bottom-middle': pifacecad.LCDBitmap(
59 | [0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x1f]),
60 | 'bottom-right': pifacecad.LCDBitmap(
61 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
62 | },
63 | # stage 3
64 | {
65 | 'top-left': pifacecad.LCDBitmap(
66 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
67 | 'top-middle': pifacecad.LCDBitmap(
68 | [0x0, 0x0, 0x1c, 0x4, 0x4, 0xe, 0xa, 0xe]),
69 | 'top-right': pifacecad.LCDBitmap(
70 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
71 | 'bottom-left': pifacecad.LCDBitmap(
72 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
73 | 'bottom-middle': pifacecad.LCDBitmap(
74 | [0x1c, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x1f]),
75 | 'bottom-right': pifacecad.LCDBitmap(
76 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
77 | },
78 | # stage 4
79 | {
80 | 'top-left': pifacecad.LCDBitmap(
81 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
82 | 'top-middle': pifacecad.LCDBitmap(
83 | [0x0, 0x0, 0x1c, 0x4, 0x4, 0xe, 0xa, 0xe]),
84 | 'top-right': pifacecad.LCDBitmap(
85 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
86 | 'bottom-left': pifacecad.LCDBitmap(
87 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
88 | 'bottom-middle': pifacecad.LCDBitmap(
89 | [0x1f, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x1f]),
90 | 'bottom-right': pifacecad.LCDBitmap(
91 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
92 | },
93 | # stage 5
94 | {
95 | 'top-left': pifacecad.LCDBitmap(
96 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
97 | 'top-middle': pifacecad.LCDBitmap(
98 | [0x0, 0x0, 0x1c, 0x4, 0x4, 0xe, 0xa, 0xe]),
99 | 'top-right': pifacecad.LCDBitmap(
100 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
101 | 'bottom-left': pifacecad.LCDBitmap(
102 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
103 | 'bottom-middle': pifacecad.LCDBitmap(
104 | [0x1f, 0x4, 0x4, 0x8, 0x10, 0x0, 0x0, 0x1f]),
105 | 'bottom-right': pifacecad.LCDBitmap(
106 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
107 | },
108 | # stage 6
109 | {
110 | 'top-left': pifacecad.LCDBitmap(
111 | [0x0, 0x0, 0xf, 0x8, 0x8, 0x8, 0x8, 0x8]),
112 | 'top-middle': pifacecad.LCDBitmap(
113 | [0x0, 0x0, 0x1c, 0x4, 0x4, 0xe, 0xa, 0xe]),
114 | 'top-right': pifacecad.LCDBitmap(
115 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]),
116 | 'bottom-left': pifacecad.LCDBitmap(
117 | [0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x1f]),
118 | 'bottom-middle': pifacecad.LCDBitmap(
119 | [0x1f, 0x4, 0x4, 0xa, 0x11, 0x0, 0x0, 0x1f]),
120 | 'bottom-right': pifacecad.LCDBitmap(
121 | [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18]),
122 | },
123 | ]
124 |
125 |
126 | class HangmanIsDead(Exception):
127 | pass
128 |
129 |
130 | class Hangman(object):
131 | def __init__(self, cad):
132 | self._stage = 0
133 | self.cad = cad
134 | self.cad.lcd.blink_off()
135 | self.cad.lcd.backlight_on()
136 | self.print_hangman()
137 |
138 | def print_hangman(self):
139 | self.cad.lcd.set_cursor(0, 0)
140 | self.cad.lcd.write_custom_bitmap(
141 | TOP_LEFT_INDEX, stages[self.stage]['top-left'])
142 | self.cad.lcd.write_custom_bitmap(
143 | TOP_MIDDLE_INDEX, stages[self.stage]['top-middle'])
144 | self.cad.lcd.write_custom_bitmap(
145 | TOP_RIGHT_INDEX, stages[self.stage]['top-right'])
146 | self.cad.lcd.set_cursor(0, 1)
147 | self.cad.lcd.write_custom_bitmap(
148 | BOTTOM_LEFT_INDEX, stages[self.stage]['bottom-left'])
149 | self.cad.lcd.write_custom_bitmap(
150 | BOTTOM_MIDDLE_INDEX, stages[self.stage]['bottom-middle'])
151 | self.cad.lcd.write_custom_bitmap(
152 | BOTTOM_RIGHT_INDEX, stages[self.stage]['bottom-right'])
153 |
154 | @property
155 | def stage(self):
156 | return self._stage
157 |
158 | @stage.setter
159 | def stage(self, new_stage):
160 | new_stage = min(new_stage, len(stages)-1)
161 | self._stage = new_stage
162 | self.print_hangman()
163 | if new_stage >= len(stages)-1:
164 | raise HangmanIsDead()
165 |
166 |
167 | class HangmanGame(object):
168 | def __init__(self, word):
169 | self.cad = pifacecad.PiFaceCAD()
170 | self.hangman = Hangman(self.cad)
171 | self.word = word
172 | self.correct_guesses = list()
173 |
174 | def start(self):
175 | self.print_word()
176 | while True:
177 | letter = self.ask_for_letter()
178 |
179 | # if the user has already guessed this letter
180 | if letter in self.correct_guesses:
181 | try:
182 | self.hangman.stage += 1
183 | continue
184 | except HangmanIsDead:
185 | self.say_gave_over()
186 | break
187 |
188 | # if the letter is correct
189 | if letter in self.word:
190 | self.correct_guesses.append(letter)
191 | else:
192 | try:
193 | self.hangman.stage += 1
194 | continue
195 | except HangmanIsDead:
196 | self.say_gave_over()
197 | break
198 |
199 | self.print_word()
200 |
201 | if self.user_has_won():
202 | self.say_well_done()
203 | break
204 |
205 | def user_has_won(self):
206 | for c in self.word:
207 | if c in self.correct_guesses:
208 | continue
209 | else:
210 | return False
211 | else:
212 | return True
213 |
214 | def print_word(self):
215 | self.cad.lcd.set_cursor(3, 0)
216 | for c in self.word:
217 | if c in self.correct_guesses:
218 | self.cad.lcd.write(c)
219 | else:
220 | self.cad.lcd.write("_")
221 |
222 | def ask_for_letter(self):
223 | self.cad.lcd.set_cursor(3, 1)
224 | scanner = pifacecad.tools.LCDScanf(
225 | format="Guess: %c%r", cad=self.cad)
226 | return scanner.scan()[0] # scan returns a list
227 |
228 | def say_well_done(self):
229 | print("Well done!")
230 | self.cad.lcd.set_cursor(3, 1)
231 | self.cad.lcd.write("Well done!".ljust(LCD_WIDTH-HANGMAN_WIDTH))
232 |
233 | def say_gave_over(self):
234 | print("GAME OVER!")
235 | self.cad.lcd.set_cursor(3, 1)
236 | self.cad.lcd.write("GAME OVER!".ljust(LCD_WIDTH-HANGMAN_WIDTH))
237 |
238 |
239 | if __name__ == "__main__":
240 | try:
241 | word = sys.argv[1].lower()
242 | except IndexError:
243 | word = WORDS[randint(0, len(WORDS)-1)]
244 | game = HangmanGame(word)
245 | game.start()
246 |
--------------------------------------------------------------------------------
/examples/radio.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # requires `mplayer` to be installed
3 | from time import sleep
4 | import os
5 | import sys
6 | import signal
7 | import shlex
8 | import math
9 | import lirc
10 |
11 | PY3 = sys.version_info[0] >= 3
12 | if not PY3:
13 | print("Radio only works with `python3`.")
14 | sys.exit(1)
15 |
16 | from threading import Barrier # must be using Python 3
17 | import subprocess
18 | import pifacecommon
19 | import pifacecad
20 | from pifacecad.lcd import LCD_WIDTH
21 |
22 |
23 | UPDATE_INTERVAL = 1
24 |
25 | STATIONS = [
26 | {'name': "6 Music",
27 | 'source': 'http://www.bbc.co.uk/radio/listen/live/r6_aaclca.pls',
28 | 'info': 'http://www.bbc.co.uk/radio/player/bbc_6music'},
29 | {'name': "Radio 2",
30 | 'source': 'http://www.bbc.co.uk/radio/listen/live/r2_aaclca.pls',
31 | 'info': None},
32 | {'name': "Radio 4",
33 | 'source': 'http://www.bbc.co.uk/radio/listen/live/r4_aaclca.pls',
34 | 'info': None},
35 | {'name': "5 Live",
36 | 'source': 'http://www.bbc.co.uk/radio/listen/live/r5l_aaclca.pls',
37 | 'info': None},
38 | {'name': "Radio 4 Extra",
39 | 'source': 'http://www.bbc.co.uk/radio/listen/live/r4x_aaclca.pls',
40 | 'info': None},
41 | {'name': "Planet Rock",
42 | 'source': 'http://tx.sharp-stream.com/icecast.php?i=planetrock.mp3',
43 | 'info': None},
44 | ]
45 |
46 | PLAY_SYMBOL = pifacecad.LCDBitmap(
47 | [0x10, 0x18, 0x1c, 0x1e, 0x1c, 0x18, 0x10, 0x0])
48 | PAUSE_SYMBOL = pifacecad.LCDBitmap(
49 | [0x0, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x0, 0x0])
50 | INFO_SYMBOL = pifacecad.LCDBitmap(
51 | [0x6, 0x6, 0x0, 0x1e, 0xe, 0xe, 0xe, 0x1f])
52 | MUSIC_SYMBOL = pifacecad.LCDBitmap(
53 | [0x2, 0x3, 0x2, 0x2, 0xe, 0x1e, 0xc, 0x0])
54 |
55 | PLAY_SYMBOL_INDEX = 0
56 | PAUSE_SYMBOL_INDEX = 1
57 | INFO_SYMBOL_INDEX = 2
58 | MUSIC_SYMBOL_INDEX = 3
59 |
60 |
61 | class Radio(object):
62 | def __init__(self, cad, start_station=0):
63 | self.current_station_index = start_station
64 | self.playing_process = None
65 |
66 | # set up cad
67 | cad.lcd.blink_off()
68 | cad.lcd.cursor_off()
69 | cad.lcd.backlight_on()
70 |
71 | cad.lcd.store_custom_bitmap(PLAY_SYMBOL_INDEX, PLAY_SYMBOL)
72 | cad.lcd.store_custom_bitmap(PAUSE_SYMBOL_INDEX, PAUSE_SYMBOL)
73 | cad.lcd.store_custom_bitmap(INFO_SYMBOL_INDEX, INFO_SYMBOL)
74 | self.cad = cad
75 |
76 | @property
77 | def current_station(self):
78 | """Returns the current station dict."""
79 | return STATIONS[self.current_station_index]
80 |
81 | @property
82 | def playing(self):
83 | return self._is_playing
84 |
85 | @playing.setter
86 | def playing(self, should_play):
87 | if should_play:
88 | self.play()
89 | else:
90 | self.stop()
91 |
92 | @property
93 | def text_status(self):
94 | """Returns a text represenation of the playing status."""
95 | if self.playing:
96 | return "Now Playing"
97 | else:
98 | return "Stopped"
99 |
100 | def play(self):
101 | """Plays the current radio station."""
102 | print("Playing {}.".format(self.current_station['name']))
103 | # check if is m3u and send -playlist switch to mplayer
104 | if self.current_station['source'].split("?")[0][-3:] in ['m3u', 'pls']:
105 | play_command = "mplayer -quiet -playlist {stationsource}".format(
106 | stationsource=self.current_station['source'])
107 | else:
108 | play_command = "mplayer -quiet {stationsource}".format(
109 | stationsource=self.current_station['source'])
110 | self.playing_process = subprocess.Popen(
111 | play_command,
112 | #stdout=subprocess.PIPE,
113 | #stderr=subprocess.PIPE,
114 | shell=True,
115 | preexec_fn=os.setsid)
116 | self._is_playing = True
117 | self.update_display()
118 |
119 | def stop(self):
120 | """Stops the current radio station."""
121 | print("Stopping radio.")
122 | os.killpg(self.playing_process.pid, signal.SIGTERM)
123 | self._is_playing = False
124 | self.update_playing()
125 |
126 | def change_station(self, new_station_index):
127 | """Change the station index."""
128 | was_playing = self.playing
129 | if was_playing:
130 | self.stop()
131 | self.current_station_index = new_station_index % len(STATIONS)
132 | if was_playing:
133 | self.play()
134 |
135 | def next_station(self, event=None):
136 | self.change_station(self.current_station_index + 1)
137 |
138 | def previous_station(self, event=None):
139 | self.change_station(self.current_station_index - 1)
140 |
141 | def update_display(self):
142 | self.update_station()
143 | self.update_playing()
144 | # self.update_volume()
145 |
146 | def update_playing(self):
147 | """Updated the playing status."""
148 | #message = self.text_status.ljust(LCD_WIDTH-1)
149 | #self.cad.lcd.write(message)
150 | if self.playing:
151 | char_index = PLAY_SYMBOL_INDEX
152 | else:
153 | char_index = PAUSE_SYMBOL_INDEX
154 |
155 | self.cad.lcd.set_cursor(0, 0)
156 | self.cad.lcd.write_custom_bitmap(char_index)
157 |
158 | def update_station(self):
159 | """Updates the station status."""
160 | message = self.current_station['name'].ljust(LCD_WIDTH-1)
161 | self.cad.lcd.set_cursor(1, 0)
162 | self.cad.lcd.write(message)
163 |
164 | def toggle_playing(self, event=None):
165 | if self.playing:
166 | self.stop()
167 | else:
168 | self.play()
169 |
170 | def close(self):
171 | self.stop()
172 | self.cad.lcd.clear()
173 | self.cad.lcd.backlight_off()
174 |
175 |
176 | def radio_preset_switch(event):
177 | global radio
178 | radio.change_station(event.pin_num)
179 |
180 |
181 | def radio_preset_ir(event):
182 | global radio
183 | radio.change_station(int(event.ir_code))
184 |
185 |
186 | if __name__ == "__main__":
187 | # test for mpalyer
188 | try:
189 | subprocess.call(["mplayer"], stdout=open('/dev/null'))
190 | except OSError as e:
191 | if e.errno == os.errno.ENOENT:
192 | print(
193 | "MPlayer was not found, install with "
194 | "`sudo apt-get install mplayer`")
195 | sys.exit(1)
196 | else:
197 | raise # Something else went wrong while trying to run `mplayer`
198 |
199 | cad = pifacecad.PiFaceCAD()
200 | global radio
201 | radio = Radio(cad)
202 | radio.play()
203 |
204 | # listener cannot deactivate itself so we have to wait until it has
205 | # finished using a barrier.
206 | global end_barrier
207 | end_barrier = Barrier(2)
208 |
209 | # wait for button presses
210 | switchlistener = pifacecad.SwitchEventListener(chip=cad)
211 | for pstation in range(4):
212 | switchlistener.register(
213 | pstation, pifacecad.IODIR_ON, radio_preset_switch)
214 | switchlistener.register(4, pifacecad.IODIR_ON, end_barrier.wait)
215 | switchlistener.register(5, pifacecad.IODIR_ON, radio.toggle_playing)
216 | switchlistener.register(6, pifacecad.IODIR_ON, radio.previous_station)
217 | switchlistener.register(7, pifacecad.IODIR_ON, radio.next_station)
218 |
219 | irlistener = pifacecad.IREventListener(
220 | prog="pifacecad-radio-example",
221 | lircrc="/usr/share/doc/python3-pifacecad/examples/radiolircrc")
222 | for i in range(4):
223 | irlistener.register(str(i), radio_preset_ir)
224 |
225 | switchlistener.activate()
226 | try:
227 | irlistener.activate()
228 | except lirc.InitError:
229 | print("Could not initialise IR, radio running without IR contorls.")
230 | irlistener_activated = False
231 | else:
232 | irlistener_activated = True
233 |
234 | end_barrier.wait() # wait unitl exit
235 |
236 | # exit
237 | radio.close()
238 | switchlistener.deactivate()
239 | if irlistener_activated:
240 | irlistener.deactivate()
241 |
--------------------------------------------------------------------------------
/examples/radiolircrc:
--------------------------------------------------------------------------------
1 | begin
2 | remote = *
3 | button = 0
4 | prog = pifacecad-radio-example
5 | config = 0
6 | end
7 |
8 | begin
9 | remote = *
10 | button = 1
11 | prog = pifacecad-radio-example
12 | config = 1
13 | end
14 |
15 | begin
16 | remote = *
17 | button = 2
18 | prog = pifacecad-radio-example
19 | config = 2
20 | end
21 |
22 | begin
23 | remote = *
24 | button = 3
25 | prog = pifacecad-radio-example
26 | config = 3
27 | end
28 |
--------------------------------------------------------------------------------
/examples/sysinfo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | import subprocess
4 | from time import sleep
5 | import pifacecad
6 |
7 |
8 | UPDATE_INTERVAL = 60 * 5 # 5 mins
9 | GET_IP_CMD = "hostname --all-ip-addresses"
10 | GET_TEMP_CMD = "/opt/vc/bin/vcgencmd measure_temp"
11 | TOTAL_MEM_CMD = "free | grep 'Mem' | awk '{print $2}'"
12 | USED_MEM_CMD = "free | grep '\-\/+' | awk '{print $3}'"
13 |
14 | temperature_symbol = pifacecad.LCDBitmap(
15 | [0x4, 0x4, 0x4, 0x4, 0xe, 0xe, 0xe, 0x0])
16 | memory_symbol = pifacecad.LCDBitmap(
17 | [0xe, 0x1f, 0xe, 0x1f, 0xe, 0x1f, 0xe, 0x0])
18 | temp_symbol_index, memory_symbol_index = 0, 1
19 |
20 |
21 | def run_cmd(cmd):
22 | return subprocess.check_output(cmd, shell=True).decode('utf-8')
23 |
24 |
25 | def get_my_ip():
26 | return run_cmd(GET_IP_CMD)[:-1]
27 |
28 |
29 | def get_my_temp():
30 | return run_cmd(GET_TEMP_CMD)[5:9]
31 |
32 |
33 | def get_my_free_mem():
34 | total_mem = float(run_cmd(TOTAL_MEM_CMD))
35 | used_mem = float(run_cmd(USED_MEM_CMD))
36 | mem_perc = used_mem / total_mem
37 | return "{:.1%}".format(mem_perc)
38 |
39 |
40 | def wait_for_ip():
41 | ip = ""
42 | while len(ip) <= 0:
43 | sleep(1)
44 | ip = get_my_ip()
45 |
46 |
47 | def show_sysinfo():
48 | while True:
49 | cad.lcd.clear()
50 | cad.lcd.write("IP:{}\n".format(get_my_ip()))
51 |
52 | cad.lcd.write_custom_bitmap(temp_symbol_index)
53 | cad.lcd.write(":{}C ".format(get_my_temp()))
54 |
55 | cad.lcd.write_custom_bitmap(memory_symbol_index)
56 | cad.lcd.write(":{}".format(get_my_free_mem()))
57 | sleep(UPDATE_INTERVAL)
58 |
59 |
60 | if __name__ == "__main__":
61 | cad = pifacecad.PiFaceCAD()
62 | cad.lcd.blink_off()
63 | cad.lcd.cursor_off()
64 |
65 | if "clear" in sys.argv:
66 | cad.lcd.clear()
67 | cad.lcd.display_off()
68 | cad.lcd.backlight_off()
69 | else:
70 | cad.lcd.store_custom_bitmap(temp_symbol_index, temperature_symbol)
71 | cad.lcd.store_custom_bitmap(memory_symbol_index, memory_symbol)
72 | cad.lcd.backlight_on()
73 | cad.lcd.write("Waiting for IP..")
74 | wait_for_ip()
75 | show_sysinfo()
76 |
--------------------------------------------------------------------------------
/examples/traintimes.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """Get trains times to various destinations from your local train station."""
3 | import sys
4 | import re
5 | import itertools
6 | import threading
7 |
8 | PY3 = sys.version_info[0] >= 3
9 | if not PY3:
10 | print("Train Times only works with `python3`.")
11 | sys.exit(1)
12 |
13 | import urllib.request
14 | from time import sleep
15 | from bs4 import BeautifulSoup
16 | import pifacecommon
17 | import pifacecad
18 |
19 |
20 | UPDATE_INTERVAL = 60 # seconds
21 |
22 | DEFAULT_DEPARTURE_STATION = "MAN"
23 |
24 | TRAIN_STATIONS = [
25 | {'name': 'Manchester Piccadilly', 'code': 'MAN'},
26 | {'name': 'Manchester Oxford Road', 'code': 'MCO'},
27 | {'name': 'Manchester Airport', 'code': 'MIA'},
28 | {'name': 'London Euston', 'code': 'EUS'},
29 | {'name': 'Wigan Wallgate', 'code': 'WGW'},
30 | {'name': 'Stockport', 'code': 'SPT'},
31 | {'name': 'Southport', 'code': 'SOP'},
32 | {'name': 'Blackpool North', 'code': 'BPN'},
33 | {'name': 'Liverpool Lime Street', 'code': 'LIV'},
34 | {'name': 'Birmingham New Street', 'code': 'BHM'},
35 | {'name': 'Crewe via Stockport', 'code': 'CRE'},
36 | {'name': 'Crewe', 'code': 'CRE'},
37 | {'name': 'Macclesfield', 'code': 'MAC'},
38 | {'name': 'York', 'code': 'YRK'},
39 | {'name': 'Buxton', 'code': 'BUX'},
40 | {'name': 'Chester', 'code': 'CTR'},
41 | {'name': 'Wilmslow', 'code': 'WML'},
42 | {'name': 'Alderley Edge', 'code': 'ALD'},
43 | ]
44 |
45 | # Pattern for live departure board information
46 | LDB_URL = \
47 | 'http://ojp.nationalrail.co.uk/service/ldbboard/dep/{depart}/{arrive}/To'
48 |
49 | LATER_TIMES_PAGE_SIZE = 3
50 |
51 |
52 | class StationError(Exception):
53 | pass
54 |
55 |
56 | class NoTrainsError(Exception):
57 | pass
58 |
59 |
60 | class TrainDepartureBoard(object):
61 | def __init__(self, departing_station, destination_station):
62 | self.departing_station = departing_station
63 | self.destination_station = destination_station
64 | self._times = None
65 |
66 | @property
67 | def times(self):
68 | if self._times is None:
69 | self.update_times()
70 | return self._times
71 |
72 | def update_times(self):
73 | # thanks sean https://github.com/seanbechhofer/raspberrypi/blob/master/
74 | # python/trains.py
75 | timeshtml = urllib.request.urlopen(LDB_URL.format(
76 | depart=self.departing_station['code'],
77 | arrive=self.destination_station['code'])).read()
78 | soup = BeautifulSoup(timeshtml)
79 | self._times = list()
80 | for div in soup.find_all('div'):
81 | if 'class' in div.attrs and "tbl-cont" in div['class']:
82 | body = div.table.tbody
83 | for row in body.find_all('tr'):
84 | cells = row.find_all('td')
85 | time = dict()
86 | time['time'] = cells[0].contents[0].strip()
87 | #time['dest'] = cells[1].contents[0].strip()
88 | # Collapse all white space
89 | #time['dest'] = re.sub(r"\s+", ' ', time['dest'])
90 | time['report'] = cells[2].contents[0].strip()
91 | if re.match('[0-9][0-9]:[0-9][0-9]', time['report']):
92 | time['estimated'] = time['report']
93 | else:
94 | time['estimated'] = ""
95 | self._times.append(time)
96 |
97 | @property
98 | def later_times(self):
99 | return self.times[1:]
100 |
101 | @property
102 | def later_times_pages(self):
103 | if len(self.later_times) <= LATER_TIMES_PAGE_SIZE:
104 | return [self.later_times] # just one page
105 | else:
106 | return list(itertools.zip_longest(
107 | self.later_times[0::LATER_TIMES_PAGE_SIZE],
108 | self.later_times[1::LATER_TIMES_PAGE_SIZE],
109 | self.later_times[2::LATER_TIMES_PAGE_SIZE],
110 | fillvalue={'time': '', 'report': '', 'estimated': ''}
111 | ))
112 |
113 |
114 | class TrainDepartureBoardDisplay(object):
115 | def __init__(self, cad, departure_boards, board_index=0):
116 | self.departure_boards = departure_boards
117 | self.board_index = board_index
118 | self.later_times_page = 0
119 | self.timer = threading.Timer(UPDATE_INTERVAL, self.auto_update)
120 | self.timer.start()
121 | self.cad = cad
122 | self.cad.lcd.backlight_on()
123 | self.cad.lcd.blink_off()
124 | self.cad.lcd.cursor_off()
125 |
126 | @property
127 | def current_board(self):
128 | """Returns the current board."""
129 | return self.departure_boards[self.board_index]
130 |
131 | def next_board(self, event=None):
132 | self.board_index = (self.board_index + 1) % len(self.departure_boards)
133 | self.update()
134 |
135 | def previous_board(self, event=None):
136 | self.board_index = (self.board_index - 1) % len(self.departure_boards)
137 | self.update()
138 |
139 | def update(self):
140 | self.cad.lcd.clear()
141 | self.print_loading()
142 | self.later_times_page = 0
143 | self.update_board()
144 |
145 | def update_board(self):
146 | self.current_board.update_times()
147 | try:
148 | self.print_next_train_time()
149 | except NoTrainsError:
150 | self.cad.lcd.clear()
151 | self.cad.lcd.write("No trains for\n{}".format(
152 | self.current_board.destination_station['name']))
153 | else:
154 | self.print_later_train_times()
155 |
156 | def auto_update(self):
157 | print("Updating.")
158 | self.update_board()
159 | self.timer = threading.Timer(UPDATE_INTERVAL, self.auto_update)
160 | self.timer.start()
161 |
162 | def print_loading(self):
163 | self.cad.lcd.write("Loading times to\n{}".format(
164 | self.current_board.destination_station['name'][:16]))
165 |
166 | def print_next_train_time(self):
167 | try:
168 | next_time_string = self.current_board.times[0]['time']
169 | estimated_time_string = self.current_board.times[0]['estimated']
170 | except IndexError:
171 | raise NoTrainsError()
172 |
173 | self.cad.lcd.clear()
174 | self.cad.lcd.write(
175 | "{next_time} {station_code} {estimated_time}".format(
176 | next_time=next_time_string,
177 | station_code=self.current_board.destination_station['code'],
178 | estimated_time=estimated_time_string,
179 | )
180 | )
181 |
182 | def print_later_train_times(self):
183 | later_times = \
184 | self.current_board.later_times_pages[self.later_times_page]
185 | if len(later_times) >= 3:
186 | later_times_string = " ".join(
187 | [t['time'].replace(":", "") for t in later_times]
188 | )
189 | elif len(later_times) > 0:
190 | later_times_string = " ".join([t['time'] for t in later_times])
191 | else:
192 | later_times_string = "No more trains."
193 |
194 | self.cad.lcd.set_cursor(0, 1)
195 | self.cad.lcd.write(later_times_string.ljust(pifacecad.lcd.LCD_WIDTH))
196 |
197 | def next_later_times_page(self, event=None):
198 | total_pages = len(self.current_board.later_times_pages)
199 | self.later_times_page = (self.later_times_page + 1) % total_pages
200 | self.print_later_train_times()
201 |
202 | def close(self):
203 | if self.timer is not None:
204 | self.timer.cancel()
205 | self.cad.lcd.clear()
206 | self.cad.lcd.backlight_off()
207 |
208 |
209 | def get_station_from_code(code):
210 | for station in TRAIN_STATIONS:
211 | if station['code'] == code:
212 | return station
213 | else:
214 | raise StationError("Invalid station code", code)
215 |
216 | if __name__ == "__main__":
217 | try:
218 | departure_station = get_station_from_code(sys.argv[1].upper())
219 | except IndexError:
220 | departure_station = get_station_from_code(DEFAULT_DEPARTURE_STATION)
221 |
222 | departure_boards = [
223 | TrainDepartureBoard(departure_station, station)
224 | for station in TRAIN_STATIONS
225 | if station is not departure_station
226 | ]
227 |
228 | cad = pifacecad.PiFaceCAD()
229 |
230 | global traintimedisplay
231 | traintimedisplay = TrainDepartureBoardDisplay(cad, departure_boards)
232 | traintimedisplay.update()
233 |
234 | # listener cannot deactivate itself so we have to wait until it has
235 | # finished using a barrier.
236 | global end_barrier
237 | end_barrier = threading.Barrier(2)
238 |
239 | # wait for button presses
240 | global switchlistener
241 | switchlistener = pifacecad.SwitchEventListener(chip=cad)
242 | switchlistener.register(4, pifacecad.IODIR_ON, end_barrier.wait)
243 | switchlistener.register(
244 | 5, pifacecad.IODIR_ON, traintimedisplay.next_later_times_page)
245 | switchlistener.register(
246 | 6, pifacecad.IODIR_ON, traintimedisplay.previous_board)
247 | switchlistener.register(
248 | 7, pifacecad.IODIR_ON, traintimedisplay.next_board)
249 |
250 | switchlistener.activate()
251 | end_barrier.wait() # wait unitl exit
252 |
253 | # exit
254 | traintimedisplay.close()
255 | switchlistener.deactivate()
256 |
--------------------------------------------------------------------------------
/examples/tweets.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """twitter demo for PiFace CAD.
3 | Call with no arguments to pull latest tweets from home timeline.
4 | Call with string argument to search for that term
5 | """
6 | from time import sleep
7 | import threading
8 | import sys
9 | import os
10 | try:
11 | import twitter # http://mike.verdone.ca/twitter/
12 | except ImportError:
13 | print("You need to install Python Twitter Tools "
14 | "(http://mike.verdone.ca/twitter/).")
15 | sys.exit(1)
16 | import pifacecommon
17 | import pifacecad
18 |
19 |
20 | UPDATE_INTERVAL = 60
21 | PAGE_WIDTH = pifacecad.lcd.LCD_WIDTH * 2
22 |
23 | CONSUMER_KEY = "6eMHbsNHcxRLIzF4wZWf6g"
24 | CONSUMER_SECRET = "BDL5j87310UBqOtRzMLo2wP93xc8BZ3xh3IGAIjzB0A"
25 | # CONSUMER_KEY = ""
26 | # CONSUMER_SECRET = ""
27 |
28 | OAUTH_TOKEN = ""
29 | OAUTH_TOKEN_SECRET = ""
30 |
31 |
32 | class NoTweetsError(Exception):
33 | pass
34 |
35 |
36 | class TwitterTicker(object):
37 | def __init__(self, cad, oauth_token, oauth_secret, search_term=None):
38 | self.twitter = twitter.Twitter(
39 | auth=twitter.OAuth(
40 | oauth_token, oauth_secret,
41 | CONSUMER_KEY, CONSUMER_SECRET)
42 | )
43 | self.search_term = search_term
44 | self.cad = cad
45 | self.cad.lcd.blink_off()
46 | self.cad.lcd.cursor_off()
47 | self.cad.lcd.backlight_on()
48 | try:
49 | self.current_tweet = self.get_latest_tweet()
50 | except NoTweetsError:
51 | self.current_tweet = None
52 | self.display_tweet(self.current_tweet)
53 | self.timer = None
54 |
55 | @property
56 | def page(self):
57 | return self._current_page
58 |
59 | @page.setter
60 | def page(self, new_page):
61 | num_pages = 1 + int(len(self.current_tweet['text']) / PAGE_WIDTH)
62 | new_page %= num_pages
63 | self.display_tweet(self.current_tweet, new_page)
64 |
65 | def get_latest_tweet(self):
66 | if self.search_term is None:
67 | return self.twitter.statuses.home_timeline()[0]
68 |
69 | try:
70 | latest_tweets = self.twitter.search.tweets(
71 | q=self.search_term,
72 | since_id=self.current_tweet['id'])['statuses']
73 | except AttributeError:
74 | latest_tweets = self.twitter.search.tweets(
75 | q=self.search_term)['statuses']
76 |
77 | try:
78 | return latest_tweets[0]
79 | except IndexError:
80 | raise NoTweetsError()
81 |
82 | def update(self, event=None):
83 | """Updated the screen with the latest tweet."""
84 | print("Updating...")
85 | try:
86 | latest_tweet = self.get_latest_tweet()
87 | except NoTweetsError:
88 | return
89 | else:
90 | if self.current_tweet is None or \
91 | latest_tweet['id'] != self.current_tweet['id']:
92 | self.current_tweet = latest_tweet
93 | self.display_tweet(self.current_tweet)
94 |
95 | def auto_update(self):
96 | self.update()
97 | # update again soon
98 | self.timer = threading.Timer(UPDATE_INTERVAL, self.auto_update)
99 | self.timer.start()
100 |
101 | def display_tweet(self, tweet, page=0):
102 | self._current_page = page
103 | text = tweet['text']
104 | self.cad.lcd.clear()
105 | start = PAGE_WIDTH * page
106 | end = PAGE_WIDTH * (page + 1)
107 | top_line = text[start:start+pifacecad.lcd.LCD_WIDTH].ljust(
108 | pifacecad.lcd.LCD_WIDTH)
109 | bottom_line = text[start+pifacecad.lcd.LCD_WIDTH:end].ljust(
110 | pifacecad.lcd.LCD_WIDTH)
111 | self.cad.lcd.set_cursor(0, 0)
112 | self.cad.lcd.write(top_line)
113 | self.cad.lcd.set_cursor(0, 1)
114 | self.cad.lcd.write(bottom_line)
115 |
116 | def close(self):
117 | if self.timer is not None:
118 | self.timer.cancel()
119 | self.cad.lcd.clear()
120 | self.cad.lcd.backlight_off()
121 |
122 | def next_page(self, event=None):
123 | self.page += 1
124 |
125 | def previous_page(self, event=None):
126 | self.page -= 1
127 |
128 |
129 | if __name__ == "__main__":
130 | try:
131 | search_term = sys.argv[1]
132 | except IndexError:
133 | search_term = None
134 | print("Using home timeline.")
135 | else:
136 | print("Searching for", search_term)
137 |
138 | twitter_creds = os.path.expanduser('~/.twitter_piface_demo_credentials')
139 | if not os.path.exists(twitter_creds):
140 | twitter.oauth_dance(
141 | "PiFace Demo", CONSUMER_KEY, CONSUMER_SECRET, twitter_creds)
142 |
143 | oauth_token, oauth_secret = twitter.read_token_file(twitter_creds)
144 |
145 | cad = pifacecad.PiFaceCAD()
146 |
147 | global twitterticker
148 | twitterticker = TwitterTicker(cad, oauth_token, oauth_secret, search_term)
149 | twitterticker.auto_update() # start the updating process
150 |
151 | # listener cannot deactivate itself so we have to wait until it has
152 | # finished using a barrier.
153 | global end_barrier
154 | end_barrier = threading.Barrier(2)
155 |
156 | # wait for button presses
157 | switchlistener = pifacecad.SwitchEventListener(chip=cad)
158 | switchlistener.register(4, pifacecad.IODIR_ON, end_barrier.wait)
159 | switchlistener.register(5, pifacecad.IODIR_ON, twitterticker.update)
160 | switchlistener.register(6, pifacecad.IODIR_ON, twitterticker.previous_page)
161 | switchlistener.register(7, pifacecad.IODIR_ON, twitterticker.next_page)
162 |
163 | switchlistener.activate()
164 | end_barrier.wait() # wait unitl exit
165 |
166 | # exit
167 | twitterticker.close()
168 | switchlistener.deactivate()
169 |
--------------------------------------------------------------------------------
/examples/weather.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | Get your weather from a weather station just blocks from your home.
4 | Go to http://www.wunderground.com/wundermap/ and find a weather station near
5 | you. Click on a temperature bubble for that area. When the window pops up,
6 | click on hypertext link with the station ID, then on the bottom right of the
7 | page, click on the Current Conditions XML. Thats your link! Good luck!
8 | """
9 |
10 | import sys
11 | PY3 = sys.version_info[0] >= 3
12 | if not PY3:
13 | print("Weather only works with `python3`.")
14 | sys.exit(1)
15 |
16 | import urllib.request
17 | import xml.etree.ElementTree
18 | from time import sleep
19 | from threading import Barrier
20 | import pifacecommon
21 | import pifacecad
22 |
23 |
24 | UPDATE_INTERVAL = 60 # seconds
25 |
26 | WEATHER_STATIONS = [
27 | {"location": "Oldham", "id": "IGREATER54"},
28 | {"location": "London", "id": "IGREATER13"},
29 | {"location": "Paris", "id": "IILEDEFR15"},
30 | {"location": "New York", "id": "KNYNEWYO62"},
31 | ]
32 | URL_PREFIX = \
33 | "http://api.wunderground.com/weatherstation/WXCurrentObXML.asp?ID="
34 |
35 | TEMP_SYMBOL = pifacecad.LCDBitmap([0x4, 0x4, 0x4, 0x4, 0xe, 0xe, 0xe, 0x0])
36 | WIND_SYMBOL = pifacecad.LCDBitmap([0x0, 0xf, 0x3, 0x5, 0x9, 0x10, 0x0])
37 | TEMP_SYMBOL_INDEX, WIND_SYMBOL_INDEX = 0, 1
38 |
39 |
40 | class WeatherStation(object):
41 | def __init__(self, location, weather_id):
42 | self.location = location
43 | self.weather_id = weather_id
44 | self._xmltree = None
45 |
46 | def generate_xmltree(self):
47 | url = get_current_condition_url(self.weather_id)
48 | data = urllib.request.urlopen(url)
49 | self._xmltree = xml.etree.ElementTree.XML(data.read())
50 |
51 | @property
52 | def xmltree(self):
53 | """Only get xml info the first time we need it (WARNING: gets stale).
54 | """
55 | if self._xmltree is None:
56 | self.generate_xmltree()
57 | return self._xmltree
58 |
59 | @property
60 | def temperature(self):
61 | return self.xmltree.findall("temp_c")[0].text
62 |
63 | @property
64 | def wind_mph(self):
65 | return self.xmltree.findall("wind_mph")[0].text
66 |
67 |
68 | class WeatherDisplay(object):
69 | def __init__(self, cad, stations, station_index=0):
70 | self.stations = stations
71 | self.station_index = station_index
72 | self.cad = cad
73 | self.cad.lcd.store_custom_bitmap(TEMP_SYMBOL_INDEX, TEMP_SYMBOL)
74 | self.cad.lcd.store_custom_bitmap(WIND_SYMBOL_INDEX, WIND_SYMBOL)
75 | self.cad.lcd.backlight_on()
76 | self.cad.lcd.blink_off()
77 | self.cad.lcd.cursor_off()
78 |
79 | @property
80 | def current_station(self):
81 | """Returns the current station dict."""
82 | return self.stations[self.station_index]
83 |
84 | def next_station(self, event=None):
85 | self.station_index = (self.station_index + 1) % len(self.stations)
86 | self.update()
87 |
88 | def previous_station(self, event=None):
89 | self.station_index = (self.station_index - 1) % len(self.stations)
90 | self.update()
91 |
92 | def update(self, event=None):
93 | self.current_station.generate_xmltree() # before we print anything
94 | self.cad.lcd.clear()
95 | self.cad.lcd.write("{place}\n".format(
96 | place=self.stations[self.station_index].location))
97 | # temperature
98 | self.cad.lcd.write_custom_bitmap(TEMP_SYMBOL_INDEX)
99 | self.cad.lcd.write(":")
100 | self.cad.lcd.write("{temp}C ".format(
101 | temp=self.current_station.temperature))
102 | # wind
103 | self.cad.lcd.write_custom_bitmap(WIND_SYMBOL_INDEX)
104 | self.cad.lcd.write(":")
105 | self.cad.lcd.write("{wind}mph".format(
106 | wind=self.current_station.wind_mph))
107 |
108 | def close(self):
109 | self.cad.lcd.clear()
110 | self.cad.lcd.backlight_off()
111 |
112 |
113 | def get_current_condition_url(weather_station_id):
114 | return "{prefix}{id}".format(prefix=URL_PREFIX, id=weather_station_id)
115 |
116 |
117 | if __name__ == "__main__":
118 | stations = \
119 | [WeatherStation(s['location'], s['id']) for s in WEATHER_STATIONS]
120 |
121 | cad = pifacecad.PiFaceCAD()
122 | global weatherdisplay
123 | weatherdisplay = WeatherDisplay(cad, stations)
124 | weatherdisplay.update()
125 |
126 | # listener cannot deactivate itself so we have to wait until it has
127 | # finished using a barrier.
128 | global end_barrier
129 | end_barrier = Barrier(2)
130 |
131 | # wait for button presses
132 | switchlistener = pifacecad.SwitchEventListener(chip=cad)
133 | switchlistener.register(4, pifacecad.IODIR_ON, end_barrier.wait)
134 | switchlistener.register(5, pifacecad.IODIR_ON, weatherdisplay.update)
135 | switchlistener.register(
136 | 6, pifacecad.IODIR_ON, weatherdisplay.previous_station)
137 | switchlistener.register(
138 | 7, pifacecad.IODIR_ON, weatherdisplay.next_station)
139 |
140 | switchlistener.activate()
141 | end_barrier.wait() # wait unitl exit
142 |
143 | # exit
144 | weatherdisplay.close()
145 | switchlistener.deactivate()
146 |
--------------------------------------------------------------------------------
/pifacecad/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | pifacecad.py
3 | Provides I/O methods for interfacing with the RaspberryPi human interface
4 | Copyright (C) 2013 thomasmarkpreston@gmail.com
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 | """
19 | from pifacecommon.interrupts import (
20 | IODIR_FALLING_EDGE,
21 | IODIR_RISING_EDGE,
22 | IODIR_ON,
23 | IODIR_OFF,
24 | IODIR_BOTH,
25 | )
26 |
27 | from .core import (
28 | DEFAULT_SPI_BUS,
29 | DEFAULT_SPI_CHIP_SELECT,
30 | NUM_SWITCHES,
31 | )
32 |
33 | # classes
34 | from .core import (
35 | PiFaceCAD,
36 | SwitchEventListener,
37 | )
38 |
39 | from .lcd import (
40 | PiFaceLCD,
41 | LCDBitmap,
42 | )
43 |
44 | from .ir import (
45 | IREventListener,
46 | )
47 |
--------------------------------------------------------------------------------
/pifacecad/core.py:
--------------------------------------------------------------------------------
1 | import pifacecommon.mcp23s17
2 | import pifacecommon.interrupts
3 | import pifacecad.lcd
4 |
5 |
6 | DEFAULT_SPI_BUS = 0
7 | DEFAULT_SPI_CHIP_SELECT = 1
8 | NUM_SWITCHES = 8
9 |
10 |
11 | class NoPiFaceCADDetectedError(Exception):
12 | pass
13 |
14 |
15 | class PiFaceCAD(pifacecommon.mcp23s17.MCP23S17,
16 | pifacecommon.interrupts.GPIOInterruptDevice):
17 | """A PiFace Control and Display board.
18 |
19 | :attribute: switch_port -- See
20 | :class:`pifacecommon.mcp23s17.MCP23S17RegisterNeg`.
21 | :attribute: switches --
22 | list containing :class:`pifacecommon.mcp23s17.MCP23S17RegisterBitNeg`.
23 | :attribute: lcd -- See :class:`pifacecad.lcd.PiFaceLCD`.
24 |
25 | Example:
26 |
27 | >>> cad = pifacecad.PiFaceCAD()
28 | >>> hex(cad.switch_port.value)
29 | 0x02
30 | >>> cad.switches[1].value
31 | 1
32 | >>> cad.lcd.write("Hello, PiFaceLCD!")
33 | >>> cad.lcd.backlight_on()
34 | """
35 | def __init__(self,
36 | hardware_addr=0,
37 | bus=DEFAULT_SPI_BUS,
38 | chip_select=DEFAULT_SPI_CHIP_SELECT,
39 | init_board=True):
40 | super(PiFaceCAD, self).__init__(hardware_addr, bus, chip_select)
41 |
42 | self.switch_port = pifacecommon.mcp23s17.MCP23S17RegisterNeg(
43 | pifacecommon.mcp23s17.GPIOA, self)
44 |
45 | self.switches = [pifacecommon.mcp23s17.MCP23S17RegisterBitNeg(
46 | i, pifacecommon.mcp23s17.GPIOA, self)
47 | for i in range(NUM_SWITCHES)]
48 |
49 | if init_board:
50 | self.init_board()
51 |
52 | self.lcd = pifacecad.lcd.PiFaceLCD(
53 | control_port=pifacecad.lcd.HD44780ControlPort(self),
54 | data_port=pifacecad.lcd.HD44780DataPort(self),
55 | init_lcd=init_board)
56 |
57 | def enable_interrupts(self):
58 | self.gpintena.value = 0xFF
59 | self.gpio_interrupts_enable()
60 |
61 | def disable_interrupts(self):
62 | self.gpintena.value = 0x00
63 | self.gpio_interrupts_disable()
64 |
65 | def init_board(self):
66 | ioconfig = (
67 | pifacecommon.mcp23s17.BANK_OFF |
68 | pifacecommon.mcp23s17.INT_MIRROR_OFF |
69 | pifacecommon.mcp23s17.SEQOP_ON |
70 | pifacecommon.mcp23s17.DISSLW_OFF |
71 | pifacecommon.mcp23s17.HAEN_ON |
72 | pifacecommon.mcp23s17.ODR_OFF |
73 | pifacecommon.mcp23s17.INTPOL_LOW
74 | )
75 | self.iocon.value = ioconfig
76 | if self.iocon.value != ioconfig:
77 | raise NoPiFaceCADDetectedError(
78 | "No PiFace Control and Display board detected "
79 | "(hardware_addr={h}, bus={b}, chip_select={c}).".format(
80 | h=self.hardware_addr, b=self.bus, c=self.chip_select))
81 | else:
82 | # finish configuring the board
83 | self.iodira.value = 0xFF # GPIOA as inputs
84 | self.gppua.value = 0xFF # input pullups on
85 | self.gpiob.value = 0
86 | self.iodirb.value = 0 # GPIOB as outputs
87 | self.enable_interrupts()
88 |
89 |
90 | class SwitchEventListener(pifacecommon.interrupts.PortEventListener):
91 | """Listens for events on the switches and calls the mapped callback
92 | functions.
93 |
94 | >>> def print_flag(event):
95 | ... print(event.interrupt_flag)
96 | ...
97 | >>> listener = pifacecad.SwitchEventListener()
98 | >>> listener.register(0, pifacecad.IODIR_ON, print_flag)
99 | >>> listener.activate()
100 | """
101 | def __init__(self, chip=None):
102 | if chip is None:
103 | chip = PiFaceCAD()
104 | super(SwitchEventListener, self).__init__(
105 | pifacecommon.mcp23s17.GPIOA, chip)
106 |
--------------------------------------------------------------------------------
/pifacecad/ir.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import multiprocessing
3 | import multiprocessing.queues
4 | import lirc
5 | import pifacecommon.interrupts
6 |
7 |
8 | class IREvent(object):
9 | """An IR event."""
10 | def __init__(self, ir_code):
11 | self.ir_code = ir_code
12 |
13 |
14 | class IRFunctionMap(pifacecommon.interrupts.FunctionMap):
15 | """Maps an IR code to callback function."""
16 | def __init__(self, ir_code, callback):
17 | self.ir_code = ir_code
18 | super(IRFunctionMap, self).__init__(callback)
19 |
20 |
21 | class IREventListener(object):
22 | """Listens for IR events and calls the registered functions. `prog`
23 | specifies
24 |
25 | >>> def print_ir_code(event):
26 | ... print(event.ir_code)
27 | ...
28 | >>> listener = pifacecad.IREventListener(prog="myprogram")
29 | >>> listener.register('one', print_ir_code)
30 | >>> listener.activate()
31 | """
32 |
33 | TERMINATE_SIGNAL = "astalavista"
34 |
35 | def __init__(self, prog, lircrc=None):
36 | self.prog = prog
37 | self.lircrc = lircrc
38 | self.ir_function_maps = list()
39 | self.event_queue = multiprocessing.SimpleQueue()
40 | self.detector = multiprocessing.Process(
41 | target=watch_ir_events, args=(self.event_queue,))
42 | self.dispatcher = threading.Thread(
43 | target=pifacecommon.interrupts.handle_events, args=(
44 | self.ir_function_maps,
45 | self.event_queue,
46 | _event_matches_ir_function_map,
47 | IREventListener.TERMINATE_SIGNAL))
48 |
49 | def register(self, ir_code, callback):
50 | """Registers an ir_code to a callback function.
51 |
52 | :param ir_code: The IR code.
53 | :type ir_code: int
54 | :param callback: The function to run when event is detected.
55 | :type callback: function
56 | """
57 | self.ir_function_maps.append(IRFunctionMap(ir_code, callback))
58 |
59 | def activate(self):
60 | """When activated the :class:`IREventListener` will run callbacks
61 | associated with IR codes.
62 | """
63 | lirc.init(self.prog, self.lircrc)
64 | self.dispatcher.start()
65 | self.detector.start()
66 |
67 | def deactivate(self):
68 | """When deactivated the :class:`IREventListener` will not run
69 | anything.
70 | """
71 | self.event_queue.put(self.TERMINATE_SIGNAL)
72 | self.dispatcher.join()
73 | self.detector.terminate() # maybe use a message queue instead?
74 | lirc.deinit()
75 |
76 |
77 | def _event_matches_ir_function_map(event, ir_function_map):
78 | return event.ir_code == ir_function_map.ir_code
79 |
80 |
81 | def watch_ir_events(event_queue):
82 | """Waits for IR code events and places them on the event queue.
83 |
84 | :param event_queue: A queue to put events on.
85 | :type event_queue: :py:class:`multiprocessing.Queue`
86 | """
87 | while True:
88 | for ir_code in lirc.nextcode():
89 | event_queue.put(IREvent(ir_code))
90 |
--------------------------------------------------------------------------------
/pifacecad/lcd.py:
--------------------------------------------------------------------------------
1 | import math
2 | import time
3 | import pifacecommon.mcp23s17
4 |
5 |
6 | # mcp23s17 GPIOB to HD44780 pin map
7 | PH_PIN_D4 = 0
8 | PH_PIN_D5 = 1
9 | PH_PIN_D6 = 2
10 | PH_PIN_D7 = 3
11 | PH_PIN_ENABLE = 4
12 | PH_PIN_RW = 5
13 | PH_PIN_RS = 6
14 | PH_PIN_LED_EN = 7
15 |
16 | # commands
17 | LCD_CLEARDISPLAY = 0x01
18 | LCD_RETURNHOME = 0x02
19 | LCD_ENTRYMODESET = 0x04
20 | LCD_DISPLAYCONTROL = 0x08
21 | LCD_CURSORSHIFT = 0x10
22 | LCD_FUNCTIONSET = 0x20
23 | LCD_SETCGRAMADDR = 0x40
24 | LCD_SETDDRAMADDR = 0x80
25 | LCD_NEWLINE = 0xC0
26 |
27 | # flags for display entry mode
28 | LCD_ENTRYRIGHT = 0x00
29 | LCD_ENTRYLEFT = 0x02
30 | LCD_ENTRYSHIFTINCREMENT = 0x01
31 | LCD_ENTRYSHIFTDECREMENT = 0x00
32 |
33 | # flags for display on/off control
34 | LCD_DISPLAYON = 0x04
35 | LCD_DISPLAYOFF = 0x00
36 | LCD_CURSORON = 0x02
37 | LCD_CURSOROFF = 0x00
38 | LCD_BLINKON = 0x01
39 | LCD_BLINKOFF = 0x00
40 |
41 | # flags for display/cursor shift
42 | LCD_DISPLAYMOVE = 0x08
43 | LCD_CURSORMOVE = 0x00
44 |
45 | # flags for display/cursor shift
46 | LCD_DISPLAYMOVE = 0x08
47 | LCD_CURSORMOVE = 0x00
48 | LCD_MOVERIGHT = 0x04
49 | LCD_MOVELEFT = 0x00
50 |
51 | # flags for function set
52 | LCD_8BITMODE = 0x10
53 | LCD_4BITMODE = 0x00
54 | LCD_2LINE = 0x08
55 | LCD_1LINE = 0x00
56 | LCD_5x10DOTS = 0x04
57 | LCD_5x8DOTS = 0x00
58 |
59 | ROW_OFFSETS = [0x00, 0x40, 0x14, 0x54]
60 |
61 | LCD_MAX_LINES = 2
62 | LCD_WIDTH = 16
63 | LCD_RAM_WIDTH = 80
64 |
65 | # \ 1M for microseconds
66 | PULSE_DELAY = 0.45 / float(1000000) # 1us - pulse width must be > 450ns
67 | SETTLE_DELAY = 37 / float(1000000) # commands need 37us to settle
68 | CLEAR_DISPLAY_DELAY = 3000 / float(1000000)
69 |
70 | MAX_CUSTOM_BITMAPS = 8
71 |
72 |
73 | class HD44780DataPort(pifacecommon.mcp23s17.MCP23S17RegisterNibble):
74 | """Data Port for an HD44780 LCD display. Must have the following
75 | properties:
76 |
77 | - value
78 |
79 | """
80 | def __init__(self, chip):
81 | super(HD44780DataPort, self).__init__(
82 | pifacecommon.mcp23s17.LOWER_NIBBLE,
83 | pifacecommon.mcp23s17.GPIOB,
84 | chip)
85 |
86 |
87 | class HD44780ControlPort(pifacecommon.mcp23s17.MCP23S17Register):
88 | """Control Port for an HD44780 LCD display. Must have the following
89 | properties:
90 |
91 | - backlight_pin
92 | - read_write_pin
93 | - register_select_pin
94 | - enable_pin
95 |
96 | """
97 | def __init__(self, chip):
98 | super(HD44780ControlPort, self).__init__(
99 | pifacecommon.mcp23s17.GPIOB, chip)
100 |
101 | @property
102 | def backlight_pin(self):
103 | return self.bits[PH_PIN_LED_EN]
104 |
105 | @property
106 | def read_write_pin(self):
107 | return self.bits[PH_PIN_RW]
108 |
109 | @property
110 | def register_select_pin(self):
111 | return self.bits[PH_PIN_RS]
112 |
113 | @property
114 | def enable_pin(self):
115 | return self.bits[PH_PIN_ENABLE]
116 |
117 |
118 | class HD44780LCD4bitModeMixIn(object):
119 | def send_byte(self, b):
120 | """Send byte to LCD. Each nibble is sent individually followed by a
121 | clock pulse because we're in 4 bit mode.
122 |
123 | :param b: The byte to send.
124 | :type b: int
125 | """
126 | self.data_port.value = (b >> 4) & 0xF
127 | self.pulse_clock()
128 | self.data_port.value = b & 0xF
129 | self.pulse_clock()
130 |
131 | def _pre_init_sequence(self):
132 | # init sequence from p.46 of datasheet.
133 | # https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
134 | time.sleep(0.015)
135 | self.data_port.value = 0x3
136 | self.pulse_clock()
137 |
138 | time.sleep(0.0041)
139 | self.data_port.value = 0x3
140 | self.pulse_clock()
141 |
142 | time.sleep(0.0001)
143 | self.data_port.value = 0x3
144 | self.pulse_clock()
145 |
146 | self.data_port.value = 0x2
147 | self.pulse_clock()
148 |
149 |
150 | class HD44780LCD8bitModeMixIn(object):
151 | def send_byte(self, b):
152 | """Send byte to LCD. *I've not tested this!*
153 |
154 | :param b: The byte to send.
155 | :type b: int
156 | """
157 | self.data_port.value = b
158 | self.pulse_clock()
159 |
160 | def _pre_init_sequence(self):
161 | # init sequence from p.45 of datasheet.
162 | # https://www.sparkfun.com/datasheets/LCD/HD44780.pdf
163 | time.sleep(0.015)
164 | self.data_port.value = 0x30
165 | self.pulse_clock()
166 |
167 | time.sleep(0.0041)
168 | self.data_port.value = 0x30
169 | self.pulse_clock()
170 |
171 | time.sleep(0.0001)
172 | self.data_port.value = 0x30
173 | self.pulse_clock()
174 |
175 | self.data_port.value = 0x20
176 | self.pulse_clock()
177 |
178 |
179 | class HD44780LCD(object):
180 | """Component part of an HD4780, must be combined with a 4 or 8 bit mixin.
181 | """
182 | def __init__(self, control_port, data_port, init_lcd=True):
183 | self.control_port = control_port
184 | self.data_port = data_port
185 |
186 | self._cursor_position = [0, 0]
187 | self._viewport_corner = 0 # top left corner
188 |
189 | self.numrows = LCD_MAX_LINES
190 | self.numcols = LCD_RAM_WIDTH
191 |
192 | self.displaycontrol = 0
193 | self.displayfunction = 0
194 | self.displaymode = 0
195 |
196 | if init_lcd:
197 | self._pre_init_sequence() # either 4 bit or 8 bit mode
198 | self._init_sequence()
199 | self.displaycontrol = LCD_DISPLAYON | LCD_CURSORON | LCD_BLINKON
200 | self.update_display_control()
201 |
202 | def _init_sequence(self):
203 | self.displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS
204 | self.update_function_set()
205 |
206 | self.displaycontrol = LCD_DISPLAYOFF
207 | self.update_display_control()
208 |
209 | self.clear()
210 |
211 | self.displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT
212 | self.update_entry_mode()
213 |
214 | @property
215 | def viewport_corner(self):
216 | """The top left corner of the current viewport."""
217 | return self._viewport_corner
218 |
219 | @viewport_corner.setter
220 | def viewport_corner(self, position):
221 | delta = position - self._viewport_corner
222 | if delta > 0:
223 | for i in range(delta):
224 | self.move_left()
225 | elif delta < 0:
226 | for i in range(abs(delta)):
227 | self.move_right()
228 | self._viewport_corner = position
229 |
230 | def see_cursor(self, col=None):
231 | """Moves the viewport so that the cursor is visible."""
232 | if col is None:
233 | col, row = self.get_cursor()
234 |
235 | # if off screen, move screen
236 | if col < self.viewport_corner:
237 | self.viewport_corner = col
238 | elif col > (self.viewport_corner + LCD_WIDTH - 1):
239 | self.viewport_corner = col - (LCD_WIDTH - 1)
240 |
241 | def clear(self):
242 | """Clears the display."""
243 | self.send_command(LCD_CLEARDISPLAY) # command to clear display
244 | # clearing the display takes a long time
245 | time.sleep(CLEAR_DISPLAY_DELAY)
246 | self._cursor_position = [0, 0]
247 | self._viewport_corner = 0
248 |
249 | def home(self):
250 | """Moves the cursor to the home position."""
251 | self.send_command(LCD_RETURNHOME) # set cursor position to zero
252 | time.sleep(CLEAR_DISPLAY_DELAY)
253 | self._cursor_position = [0, 0]
254 | self._viewport_corner = 0
255 |
256 | # entry mode set
257 | def update_entry_mode(self):
258 | """Update entrymodeset to reflect the displaymode."""
259 | self.send_command(LCD_ENTRYMODESET | self.displaymode)
260 |
261 | def left_to_right(self):
262 | """Sets the text to flow from left to right."""
263 | self.displaymode |= LCD_ENTRYLEFT
264 | self.update_entry_mode()
265 |
266 | def right_to_left(self):
267 | """Sets the text to flow from right to left."""
268 | self.displaymode &= ~LCD_ENTRYLEFT
269 | self.update_entry_mode()
270 |
271 | def right_justify(self):
272 | """Right justifies text from the cursor."""
273 | self.displaymode |= LCD_ENTRYSHIFTINCREMENT
274 | self.update_entry_mode()
275 |
276 | def left_justify(self):
277 | """Left justifies text from the cursor."""
278 | self.displaymode &= ~LCD_ENTRYSHIFTINCREMENT
279 | self.update_entry_mode()
280 |
281 | # display control
282 | def update_display_control(self):
283 | """Update the display control to reflect the displaycontrol."""
284 | self.send_command(LCD_DISPLAYCONTROL | self.displaycontrol)
285 |
286 | def display_off(self):
287 | """Turns the display off (quickly)."""
288 | self.displaycontrol &= ~LCD_DISPLAYON
289 | self.update_display_control()
290 |
291 | def display_on(self):
292 | """Turns the display on (quickly)."""
293 | self.displaycontrol |= LCD_DISPLAYON
294 | self.update_display_control()
295 |
296 | def cursor_off(self):
297 | """Turns the underline cursor off."""
298 | self.displaycontrol &= ~LCD_CURSORON
299 | self.update_display_control()
300 |
301 | def cursor_on(self):
302 | """Turns the underline cursor on."""
303 | self.displaycontrol |= LCD_CURSORON
304 | self.update_display_control()
305 |
306 | def blink_off(self):
307 | """Turns off the blinking cursor."""
308 | self.displaycontrol &= ~LCD_BLINKON
309 | self.update_display_control()
310 |
311 | def blink_on(self):
312 | """Turns on the blinking cursor."""
313 | self.displaycontrol |= LCD_BLINKON
314 | self.update_display_control()
315 |
316 | # cursor or display shift
317 | def move_left(self):
318 | """Scrolls the display without changing the RAM."""
319 | self.send_command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT)
320 | self._viewport_corner += 1
321 |
322 | def move_right(self):
323 | """Scrolls the display without changing the RAM."""
324 | self.send_command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT)
325 | self._viewport_corner -= 1
326 |
327 | # function set
328 | def update_function_set(self):
329 | """Updates the function set to reflect the current displayfunction."""
330 | self.send_command(LCD_FUNCTIONSET | self.displayfunction)
331 |
332 | # cgram address set
333 | def set_cgram_address(self, address=0):
334 | """Start using CGRAM at the given address.
335 |
336 | :param address: The address to start at (default: 0)
337 | :type address: int
338 | """
339 | self.send_command(LCD_SETCGRAMADDR | address)
340 |
341 | # ddram address set
342 | def set_ddram_address(self, address=None):
343 | """Start using DDRAM at the given address.
344 |
345 | :param address: The address to start at (default: current)
346 | :type address: int
347 | """
348 | if address is None:
349 | col, row = self.get_cursor()
350 | address = self.colrow2address(col, row)
351 | self.send_command(LCD_SETDDRAMADDR | address)
352 |
353 | def set_cursor(self, col, row):
354 | """Places the cursor at the specified column and row.
355 |
356 | :param col: The column.
357 | :type col: int
358 | :param row: The row.
359 | :type row: int
360 | """
361 | if col == 0 and row == 1:
362 | self.send_command(LCD_NEWLINE)
363 | else:
364 | col = max(0, min(col, self.numcols - 1))
365 | row = max(0, min(row, self.numrows - 1))
366 | self.set_ddram_address(self.colrow2address(col, row))
367 | self._cursor_position = [col, row]
368 |
369 | def get_cursor(self):
370 | """Returns the current column and row of the cursor. Also fixes
371 | internal value.
372 |
373 | :returns: (int, int) -- A tuple containing the column and row.
374 | """
375 | fixed_col = self._cursor_position[0] % LCD_RAM_WIDTH
376 | fixed_row = self._cursor_position[1] + \
377 | math.floor(self._cursor_position[0] / LCD_RAM_WIDTH)
378 |
379 | self._cursor_position[0] = fixed_col
380 | self._cursor_position[1] = fixed_row
381 |
382 | return (fixed_col, fixed_row)
383 |
384 | def colrow2address(self, col, row):
385 | """Returns address of column and row.
386 |
387 | :param col: The column.
388 | :param col: int
389 | :param row: The row.
390 | :param row: int
391 | :returns: The address of the column and row.
392 | """
393 | return col + ROW_OFFSETS[int(row)]
394 |
395 | # backlight
396 | def backlight_on(self):
397 | """Turn on the backlight."""
398 | self.control_port.backlight_pin.value = 1
399 |
400 | def backlight_off(self):
401 | """Turn on the backlight."""
402 | self.control_port.backlight_pin.value = 0
403 |
404 | # send commands/characters
405 | def send_command(self, command):
406 | """Send command byte to LCD.
407 |
408 | :param command: The command byte to be sent.
409 | :type command: int
410 | """
411 | self.control_port.register_select_pin.value = 0
412 | self.send_byte(command)
413 | time.sleep(SETTLE_DELAY)
414 |
415 | def send_data(self, data):
416 | """Send data byte to LCD.
417 |
418 | :param data: The data byte to be sent.
419 | :type data: int
420 | """
421 | self.control_port.register_select_pin.value = 1
422 | self.send_byte(data)
423 | time.sleep(SETTLE_DELAY)
424 |
425 | def pulse_clock(self):
426 | """Pulse the LCD clock for reading data."""
427 | self.control_port.enable_pin.value = 1
428 | time.sleep(PULSE_DELAY)
429 | self.control_port.enable_pin.value = 0
430 | time.sleep(PULSE_DELAY)
431 |
432 | def write(self, text):
433 | """Writes a string to the LCD screen.
434 |
435 | :param text: The text that will be written.
436 | :type text: string
437 | """
438 | self.set_ddram_address()
439 | for char in text:
440 | if '\n' in char:
441 | self.set_cursor(0, 1)
442 | else:
443 | self.send_data(ord(char))
444 | self._cursor_position[0] += 1 # LCD auto increments
445 |
446 | def write_custom_bitmap(self, char_bank, bitmap=None):
447 | """Writes the custom bitmap in CGRAM stored at char_bank. If a
448 | LCDBitmap is given, store it in the CGRAM address char_bank and then
449 | write it to the screen.
450 |
451 | :param char_bank: The bitmap bank to write the bitmap from
452 | (max: {max_custom_bitmaps})
453 | :type char_bank: int
454 | :param bitmap: The bitmap to store in the CGRAM address and write.
455 | :type bitmap: :class:`LCDBitmap`
456 | """.format(max_custom_bitmaps=MAX_CUSTOM_BITMAPS)
457 |
458 | self.char_bank_in_range_or_error(char_bank)
459 | if bitmap is not None:
460 | self.store_custom_bitmap(char_bank, bitmap)
461 | col, row = self.get_cursor() # start using the correct ddram address
462 | self.set_cursor(col, row)
463 | self.send_data(char_bank)
464 | self._cursor_position[0] += 1 # LCD auto increments
465 |
466 | def store_custom_bitmap(self, char_bank, bitmap):
467 | """Stores a custom bitmap bitmap at char_bank.
468 |
469 | :param char_bank: The CGRAM address to use.
470 | :type char_bank: int
471 | :param bitmap: The bitmap to store.
472 | :type bitmap: :class:`LCDBitmap`
473 | """
474 | self.char_bank_in_range_or_error(char_bank)
475 | self.set_cgram_address(char_bank*8)
476 | for line in bitmap:
477 | self.send_data(line)
478 |
479 | def char_bank_in_range_or_error(self, char_bank):
480 | """Raises an exception if char_bank is out of bounds. Returns True
481 | otherwise.
482 |
483 | :param char_bank: The address to check.
484 | :type char_bank: int
485 | """
486 | if char_bank >= MAX_CUSTOM_BITMAPS or \
487 | char_bank < 0:
488 | raise Exception(
489 | "There are only {max} custom characters (You tried to access "
490 | "{cgramaddr}).".format(
491 | max=MAX_CUSTOM_BITMAPS,
492 | cgramaddr=char_bank,
493 | )
494 | )
495 | else:
496 | return True
497 |
498 |
499 | class LCDBitmap(bytearray):
500 | """A custom bitmap for the LCD screen."""
501 | # TODO: More efficiend to store this sideways (LCDchar: 5x8, Bitmap: 8x...)
502 | def __init__(self, lines=list()):
503 | super(LCDBitmap, self).__init__(self)
504 | for line in lines:
505 | self.append(line)
506 |
507 |
508 | class PiFaceLCD(HD44780LCD, HD44780LCD4bitModeMixIn):
509 | """An HD44780 LCD in 4-bit mode."""
510 | pass
511 |
--------------------------------------------------------------------------------
/pifacecad/tools/__init__.py:
--------------------------------------------------------------------------------
1 | from .question import LCDQuestion
2 | from .scanf import LCDScanf
3 |
--------------------------------------------------------------------------------
/pifacecad/tools/question.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import threading
3 | import pifacecad
4 | import pifacecad.lcd
5 |
6 | # Python 2 barrier hack (if you know a better way, please tell me)
7 | PY3 = sys.version_info[0] >= 3
8 | if not PY3:
9 | from time import sleep
10 |
11 | class Barrier(object):
12 | def __init__(self, n, timeout=None):
13 | self.count = 0
14 | self.n = n
15 | self.timeout = timeout
16 |
17 | def wait(self):
18 | self.count += 1
19 | while self.count < self.n:
20 | sleep(0.0001)
21 |
22 | threading.Barrier = Barrier
23 |
24 |
25 | class LCDQuestion(object):
26 | """Asks a question on the LCD"
27 |
28 | :param question: The question to be asked.
29 | :type question: string
30 | :param answers: The answers to choose from.
31 | :type answers: list
32 | :param selector: The selector displayed in front of each answer.
33 | :type selector: string
34 | :param cad: An already initialised PiFaceCAD object.
35 | :type cad: PiFaceCAD
36 | """
37 | def __init__(self, question, answers, selector=">", cad=None):
38 | self.question = question
39 | self.answers = answers
40 | self.selector = selector
41 |
42 | if cad is None:
43 | cad = pifacecad.PiFaceCAD()
44 | cad.lcd.backlight_on()
45 | cad.lcd.blink_off()
46 | cad.lcd.cursor_off()
47 | cad.lcd.display_off() # don't want to see slow printing
48 |
49 | self.cad = cad
50 |
51 | self._displayed_answer_index = 0
52 | self.wait_for_return_string = None
53 |
54 | def ask(self):
55 | """Asks the question using the LCD screen.
56 |
57 | :returns: int -- index of the answer selected.
58 | """
59 | self.cad.lcd.clear()
60 | self.cad.lcd.write(self.question)
61 | self.change_answer(self._displayed_answer_index)
62 | self.cad.lcd.display_on()
63 |
64 | # wait for user input
65 | listener = pifacecad.SwitchEventListener(self.cad)
66 | listener.register(7, pifacecad.IODIR_ON, self.next_answer)
67 | listener.register(6, pifacecad.IODIR_ON, self.previous_answer)
68 | listener.register(5,
69 | pifacecad.IODIR_ON,
70 | self.select_answer_switch_pressed)
71 |
72 | self.wait_for_return_string = threading.Barrier(2)
73 | listener.activate()
74 | self.wait_for_return_string.wait()
75 | listener.deactivate()
76 | return self._displayed_answer_index
77 |
78 | def select_answer_switch_pressed(self, event):
79 | self.wait_for_return_string.wait()
80 |
81 | def next_answer(self, event=None):
82 | answer_index = (self._displayed_answer_index + 1) % len(self.answers)
83 | self.change_answer(answer_index)
84 |
85 | def previous_answer(self, event=None):
86 | answer_index = (self._displayed_answer_index - 1) % len(self.answers)
87 | self.change_answer(answer_index)
88 |
89 | def change_answer(self, new_answer_index=None):
90 | if new_answer_index is None:
91 | new_answer_index = \
92 | (self._displayed_answer_index + 1) % len(self.answers)
93 |
94 | # pad with spaces to overwrite previous answer (ljust)
95 | prev_ans = self.answers[self._displayed_answer_index]
96 | prev_ans_len = max(len(prev_ans), pifacecad.lcd.LCD_WIDTH)
97 | answer = self.answers[new_answer_index].ljust(prev_ans_len)
98 |
99 | self.cad.lcd.set_cursor(0, 1)
100 | if self.selector is None:
101 | self.cad.lcd.write(answer)
102 | else:
103 | self.cad.lcd.write("%s%s" % (self.selector, answer))
104 |
105 | self._displayed_answer_index = new_answer_index
106 |
--------------------------------------------------------------------------------
/pifacecad/tools/scanf.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import math
3 | from abc import ABCMeta
4 | import threading
5 | import pifacecad
6 |
7 |
8 | # Python 2 barrier hack (if you know a better way, please tell me)
9 | PY3 = sys.version_info[0] >= 3
10 | if not PY3:
11 | from time import sleep
12 |
13 | class Barrier(object):
14 | def __init__(self, n, timeout=None):
15 | self.count = 0
16 | self.n = n
17 | self.timeout = timeout
18 |
19 | def wait(self):
20 | self.count += 1
21 | while self.count < self.n:
22 | sleep(0.0001)
23 |
24 | threading.Barrier = Barrier
25 |
26 |
27 | # charset from http://mil.ufl.edu/4744/docs/lcdmanual/charset.gif
28 | LCD_RETURN_CHAR = chr(126) # arrow
29 | LCD_PUNC_CHARSET = [chr(x) for x in range(0x21, 0x30)] + \
30 | [chr(x) for x in range(0x3A, 0x41)] + \
31 | [chr(x) for x in range(0x5B, 0x61)] + \
32 | [chr(x) for x in range(0x7B, 0xFF)]
33 |
34 | # character specifiers for LCDScanf (defined at bottom)
35 | # VALUE_SELECTS = {}
36 |
37 |
38 | class UnknownSpecifierError(Exception):
39 | pass
40 |
41 |
42 | class LCDScanf(object):
43 | """Allows the user to input text using the LCD.
44 |
45 | To change mode from moving and editing press switch 5 (navigation switch
46 | *in*). Move the navigation switch side to side (switches 6 and 7) to
47 | change character.
48 |
49 | The available character set is specified using a format string similar to
50 | printf. Supported character specifiers are::
51 |
52 | c: Characters
53 | C: Capital Characters
54 | i: Integers
55 | d: Integers
56 | x: Hexadecimal
57 | X: Capital Hexadecimal
58 | .: Punctuation
59 | m: Custom (specifed by ``custom_values`` in init args)
60 | r: Return (switch 5 to submit string)
61 |
62 | You must prefix them with a ``%`` symbol and you can also specify a number
63 | argument. Each specifier is returned as an element in a list.
64 |
65 | For example:
66 |
67 | >>> scanner = pifacecad.tools.LCDScanf("Text: %c%2i%.%r")
68 | >>> print(scanner.scan()) # user enters things on PiFaceCAD
69 | ['a', '13', '!']
70 |
71 | You can also specify custom values:
72 |
73 | >>> scanner = pifacecad.tools.LCDScanf(
74 | ... "Animal: %m%r",
75 | ... custom_values=('cat', 'dog', 'fish')
76 | ... )
77 | >>> print(scanner.scan())
78 | ['fish']
79 | """
80 | class ScanfMode(object):
81 | select, edit = range(2)
82 |
83 | def __init__(self, format, custom_values=None, cad=None):
84 | if cad is None:
85 | cad = pifacecad.PiFaceCAD()
86 | cad.lcd.backlight_on()
87 | cad.lcd.cursor_on()
88 | cad.lcd.display_off() # don't want to see slow printing
89 | self.custom_cad = False
90 | else:
91 | self.custom_cad = True
92 |
93 | self.start_offset = cad.lcd.get_cursor()
94 | self.cad = cad
95 | self.display_string = ValueSelectString(format, custom_values)
96 | self.mode = self.ScanfMode.select
97 | self.wait_for_return_string = None
98 |
99 | def __del__(self):
100 | if not self.custom_cad:
101 | self.cad.lcd.clear()
102 | self.cad.lcd.display_off()
103 |
104 | @property
105 | def mode(self):
106 | return self._mode
107 |
108 | @mode.setter
109 | def mode(self, newmode):
110 | if newmode == self.ScanfMode.select:
111 | self.cad.lcd.blink_off()
112 | elif newmode == self.ScanfMode.edit:
113 | self.cad.lcd.blink_on()
114 | self._mode = newmode
115 |
116 | def scan(self):
117 | #self.cad.lcd.clear()
118 | self.cad.lcd.write(str(self.display_string))
119 | # set the cursor to a sensible position
120 | try:
121 | first_value_select_index = \
122 | self.display_string.instanceindex(ValueSelect)
123 | except TypeError:
124 | # nothing to select, show the string and return
125 | self.cad.lcd.display_on()
126 | return self.display_string.selected_values
127 | else:
128 | col = first_value_select_index + self.start_offset[0]
129 | row = self.start_offset[1]
130 | self.cad.lcd.set_cursor(col, row)
131 | self.cad.lcd.display_on()
132 |
133 | # wait for user input
134 | listener = pifacecad.SwitchEventListener(self.cad)
135 | listener.register(7, pifacecad.IODIR_ON, self.right_event)
136 | listener.register(6, pifacecad.IODIR_ON, self.left_event)
137 | listener.register(5, pifacecad.IODIR_ON, self.change_mode_event)
138 | listener.register(4, pifacecad.IODIR_ON, self.return_string_event)
139 |
140 | self.wait_for_return_string = threading.Barrier(2)
141 | listener.activate()
142 | self.wait_for_return_string.wait()
143 | listener.deactivate()
144 | return self.display_string.selected_values
145 |
146 | def right_event(self, event):
147 | if self.mode == self.ScanfMode.select:
148 | self.move_cursor_right()
149 | elif self.mode == self.ScanfMode.edit:
150 | self.increment_value()
151 |
152 | def left_event(self, event):
153 | if self.mode == self.ScanfMode.select:
154 | self.move_cursor_left()
155 | elif self.mode == self.ScanfMode.edit:
156 | self.decrement_value()
157 |
158 | def change_mode_event(self, event):
159 | col, row = self.cad.lcd.get_cursor()
160 | if isinstance(
161 | self.display_string.value_at(col-self.start_offset[0]),
162 | ReturnCharacter):
163 | self.return_string_event(event)
164 | elif self.mode == self.ScanfMode.select:
165 | self.mode = self.ScanfMode.edit
166 | elif self.mode == self.ScanfMode.edit:
167 | self.mode = self.ScanfMode.select
168 |
169 | def return_string_event(self, event):
170 | self.wait_for_return_string.wait()
171 |
172 | def move_cursor_right(self):
173 | """move cursor right, roll around string, scroll screen.
174 | Only place cursor on ValueSelect, ignore plain text.
175 | """
176 | # go to next column, if past string: roll around
177 | col, row = self.cad.lcd.get_cursor()
178 | value_index = col - self.start_offset[0]
179 | value_index = (value_index + 1) % len(str(self.display_string))
180 |
181 | # make sure we land on a ValueSelect
182 | while (not is_selectable_character(self.display_string, value_index)):
183 | value_index = (value_index + 1) % len(str(self.display_string))
184 |
185 | col = value_index + self.start_offset[0]
186 | self.cad.lcd.set_cursor(col, row)
187 | self.cad.lcd.see_cursor(col)
188 |
189 | def move_cursor_left(self):
190 | """move cursor left, roll around string, scroll screen.
191 | Only place cursor on ValueSelect, ignore plain text.
192 | """
193 | # go to prev column, if before string: roll around to end
194 | col, row = self.cad.lcd.get_cursor()
195 | value_index = col - self.start_offset[0]
196 | value_index = (value_index - 1) % len(str(self.display_string))
197 |
198 | # make sure we land on a ValueSelect
199 | while (not is_selectable_character(self.display_string, value_index)):
200 | value_index = (value_index - 1) % len(str(self.display_string))
201 |
202 | col = value_index + self.start_offset[0]
203 | self.cad.lcd.set_cursor(col, row)
204 | self.cad.lcd.see_cursor()
205 |
206 | def increment_value(self):
207 | col, row = self.cad.lcd.get_cursor()
208 | value_select = self.display_string.value_at(col-self.start_offset[0])
209 | value_select.increment_value()
210 | self.write_value(value_select, col, row)
211 |
212 | def decrement_value(self):
213 | col, row = self.cad.lcd.get_cursor()
214 | value_select = self.display_string.value_at(col-self.start_offset[0])
215 | value_select.decrement_value()
216 | self.write_value(value_select, col, row)
217 |
218 | def write_value(self, value_select, col, row):
219 | string = str(value_select.value).ljust(value_select.longest_len)
220 | self.cad.lcd.write(string)
221 | self.cad.lcd.set_cursor(col, row)
222 |
223 |
224 | class ValueSelect(list):
225 | """A character in a specified range"""
226 | def __init__(self, values=list(), value_index=0):
227 | super(ValueSelect, self).__init__(values)
228 | self.value_index = value_index
229 |
230 | def __str__(self):
231 | """Returns the selected value instead of a stringified list.
232 | Values are space-padded to the max length value selector
233 | """
234 | return str(self.value).ljust(self.longest_len)
235 |
236 | @property
237 | def longest_len(self):
238 | """Return the length of the longest value in this list.
239 | Example::
240 |
241 | ValueSelect(["one", "two", "three"]).longest_len() == 5
242 | """
243 | return max([len(str(x)) for x in self])
244 |
245 | @property
246 | def value(self):
247 | if len(self) <= 0:
248 | return None
249 | else:
250 | return self[self.value_index]
251 |
252 | @value.setter
253 | def value(self, v):
254 | if len(self) <= 0:
255 | self.append(v)
256 |
257 | self.value_index = self.index(v)
258 |
259 | def increment_value(self):
260 | self.value_index = (self.value_index + 1) % len(self)
261 |
262 | def decrement_value(self):
263 | self.value_index = (self.value_index - 1) % len(self)
264 |
265 |
266 | class CharacterValueSelect(ValueSelect):
267 | def __init__(self):
268 | super(CharacterValueSelect, self).__init__(
269 | [c for c in char_range("a", "z")])
270 |
271 |
272 | class CapsCharacterValueSelect(ValueSelect):
273 | def __init__(self):
274 | super(CapsCharacterValueSelect, self).__init__(
275 | [c for c in char_range("A", "Z")])
276 |
277 |
278 | class PunctuationValueSelect(ValueSelect):
279 | def __init__(self):
280 | super(PunctuationValueSelect, self).__init__(
281 | [p for p in LCD_PUNC_CHARSET])
282 |
283 |
284 | class NumericValue(object):
285 | __metaclass__ = ABCMeta
286 | base = 10
287 |
288 | def __int__(self):
289 | try:
290 | return int(self.value, self.base)
291 | except TypeError:
292 | return int(self.value)
293 |
294 |
295 | class IntegerValueSelect(ValueSelect, NumericValue):
296 | def __init__(self):
297 | super(IntegerValueSelect, self).__init__([i for i in range(10)])
298 | self.base = 10
299 |
300 |
301 | class HexadecimalValueSelect(ValueSelect, NumericValue):
302 | def __init__(self):
303 | super(HexadecimalValueSelect, self).__init__(
304 | [numeric for numeric in range(10)] +
305 | [alpha for alpha in char_range('A', 'F')]
306 | )
307 | self.base = 16
308 |
309 |
310 | class ReturnCharacter(ValueSelect):
311 | def __init__(self):
312 | super(ReturnCharacter, self).__init__()
313 | self.append(LCD_RETURN_CHAR)
314 |
315 |
316 | class MultiValueSelect(list):
317 | """A list of ValueSelects representing a single value."""
318 | def __init__(self, multiplier, value_select, custom_values=None):
319 | if value_select is ValueSelect:
320 | x = [value_select(custom_values) for i in range(multiplier)]
321 | else:
322 | x = [value_select() for i in range(multiplier)]
323 | super(MultiValueSelect, self).__init__(x)
324 |
325 | def __str__(self):
326 | return str(self.value)
327 |
328 | def __int__(self):
329 | return int(self.value)
330 |
331 | @property
332 | def value(self):
333 | if isinstance(self[0], NumericValue):
334 | number = 0
335 | for i, value in enumerate(self[::-1]):
336 | number += int(value) * math.pow(self[0].base, i)
337 | return int(number)
338 | else:
339 | string = ""
340 | for value_select in self:
341 | string += value_select.value # not calling str which ljust's
342 | return string
343 |
344 |
345 | class ValueSelectString(list):
346 | """A list of ValueSelect's and characters, representing a string."""
347 | def __init__(self, format, custom_values=None):
348 | super(ValueSelectString, self).__init__()
349 | self.format = format
350 | self.values = list() # lists of value selects representing a value
351 |
352 | multiplier = 1
353 | char_spec = False
354 | for character in self.format:
355 | if not char_spec:
356 | if character == "%":
357 | char_spec = True
358 | multiplier = 1
359 | else:
360 | #multiplier = 1
361 | self.append(character)
362 | continue
363 |
364 | # this is a character specifier
365 | if is_number(character):
366 | multiplier = int(character)
367 | continue
368 |
369 | try:
370 | value_select = VALUE_SELECTS[character]
371 | except KeyError:
372 | raise UnknownSpecifierError(
373 | "'%s' is an unknown specifier." % character
374 | )
375 | else:
376 | if value_select is ReturnCharacter:
377 | self.append(ReturnCharacter())
378 | else:
379 | mvs = MultiValueSelect(
380 | multiplier, value_select, custom_values
381 | )
382 | self.values.append(mvs)
383 | self.extend(mvs)
384 | finally:
385 | char_spec = False
386 |
387 | def __str__(self):
388 | values = [str(char) for char in self]
389 | return "".join(values)
390 |
391 | @property
392 | def selected_values(self):
393 | """Returns a list of currently selected values."""
394 | return [v.value for v in self.values]
395 |
396 | def instanceindex(self, instance_type):
397 | """Returns the first index of an instance of `instance_type`"""
398 | for i, vs in enumerate(self):
399 | if isinstance(vs, instance_type):
400 | return i
401 |
402 | raise TypeError(
403 | "%s is not in %s (%s)" % (instance_type, type(self), self.format)
404 | )
405 |
406 | def value_at(self, string_index):
407 | """Returns the ValueSelect or character at the string index.
408 | The string index is the index of this ValueSelectString as if it is
409 | being treated as a string.
410 |
411 | Example::
412 |
413 | cs0 == CustomValueSelect == ["bb", "cc"]
414 | cs1 == CustomValueSelect == ["dd", "ee"]
415 | vss == ValueSelectString == ["a", cs0, cs1, "f"]
416 | vss == ["a", ["bb", "cc"], ["dd", "ee"], "f"]
417 | str(vss) == "abbddf"
418 | vss.value_at(0) == "a"
419 | vss.value_at(1) == cs0
420 | vss.value_at(2) == cs0
421 | vss.value_at(3) == cs1
422 | vss.value_at(5) == "f"
423 | str(vss.value_at(2)) == "bb"
424 | """
425 | # keep looping until we're at the index we want
426 | char_index = vs_index = 0
427 | while string_index > 0:
428 | string_index -= 1
429 | # if it's not a value select then go to the next index
430 | if not isinstance(self[vs_index], ValueSelect):
431 | char_index = 0
432 | vs_index += 1
433 | continue
434 |
435 | char_index += 1
436 | if char_index >= self[vs_index].longest_len:
437 | char_index = 0
438 | vs_index += 1
439 |
440 | # return state
441 | return self[vs_index]
442 |
443 |
444 | def char_range(c1, c2):
445 | """Generates the characters from `c1` to `c2`, inclusive."""
446 | for c in range(ord(c1), ord(c2) + 1):
447 | yield chr(c)
448 |
449 |
450 | def is_number(character):
451 | """Returns True if given character is a number, False otherwise."""
452 | try:
453 | int(character)
454 | return True
455 | except ValueError:
456 | return False
457 |
458 |
459 | def is_selectable_character(display_string, col):
460 | """Returns True if the character can be selected"""
461 | # keep looping until we're at the index we want
462 | char_index = vs_index = 0
463 | while col > 0:
464 | col -= 1
465 |
466 | char_index += 1
467 | if isinstance(display_string[vs_index], ValueSelect):
468 | width = display_string[vs_index].longest_len
469 | else:
470 | width = 1
471 | if char_index >= width:
472 | char_index = 0
473 | vs_index += 1
474 |
475 | # selectable if we're at the start of a value select or returnchar
476 | return char_index == 0 and (
477 | isinstance(display_string[vs_index], ValueSelect) or
478 | isinstance(display_string[vs_index], ReturnCharacter)
479 | )
480 |
481 |
482 | # defined here because we need to build classes first
483 | VALUE_SELECTS = {
484 | 'c': CharacterValueSelect,
485 | 'C': CapsCharacterValueSelect,
486 | 'i': IntegerValueSelect,
487 | 'd': IntegerValueSelect,
488 | 'x': HexadecimalValueSelect,
489 | 'X': HexadecimalValueSelect,
490 | '.': PunctuationValueSelect,
491 | 'm': ValueSelect,
492 | 'r': ReturnCharacter,
493 | }
494 |
--------------------------------------------------------------------------------
/pifacecad/version.py:
--------------------------------------------------------------------------------
1 | __version__ = '2.0.8'
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pifacecommon==4.0.0
2 | python-lirc==1.2.1
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from distutils.core import setup
3 |
4 |
5 | #PY3 = sys.version_info.major >= 3
6 | PY3 = sys.version_info[0] >= 3
7 | VERSION_FILE = "pifacecad/version.py"
8 |
9 |
10 | def get_version():
11 | if PY3:
12 | version_vars = {}
13 | with open(VERSION_FILE) as f:
14 | code = compile(f.read(), VERSION_FILE, 'exec')
15 | exec(code, None, version_vars)
16 | return version_vars['__version__']
17 | else:
18 | execfile(VERSION_FILE)
19 | return __version__
20 |
21 |
22 | setup(
23 | name='pifacecad',
24 | version=get_version(),
25 | description='The PiFace Control And Display module.',
26 | author='Thomas Preston',
27 | author_email='thomas.preston@openlx.org.uk',
28 | license='GPLv3+',
29 | url='http://piface.github.io/pifacecad/',
30 | packages=['pifacecad', 'pifacecad.tools'],
31 | long_description=open('README.md').read() + open('CHANGELOG').read(),
32 | classifiers=[
33 | "License :: OSI Approved :: GNU Affero General Public License v3 or "
34 | "later (AGPLv3+)",
35 | "Programming Language :: Python :: 3",
36 | "Development Status :: 5 - Production/Stable",
37 | "Intended Audience :: Developers",
38 | "Topic :: Software Development :: Libraries :: Python Modules",
39 | ],
40 | keywords='piface cad control display raspberrypi openlx',
41 | requires=['pifacecommon', 'lirc'],
42 | )
43 |
--------------------------------------------------------------------------------
/tests/test_custombitmap.py:
--------------------------------------------------------------------------------
1 | # gen bitmaps with: http://www.quinapalus.com/hd44780udg.html
2 | # import sys
3 | # import os
4 | # sys.path.insert(0, os.path.abspath('..'))
5 | import time
6 |
7 | import os
8 | import sys
9 | parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
10 | sys.path.insert(0, parentdir)
11 | import pifacecad
12 |
13 |
14 | batt_char = pifacecad.LCDBitmap(
15 | [0x0E, 0x1B, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F]
16 | )
17 | checkerboard = pifacecad.LCDBitmap(
18 | [0x15, 0xa, 0x15, 0xa, 0x15, 0xa, 0x15, 0xa]
19 | )
20 |
21 | left_holder = pifacecad.LCDBitmap(
22 | [0x3, 0xc, 0x8, 0x10, 0x10, 0x8, 0xc, 0x3]
23 | )
24 | right_holder = pifacecad.LCDBitmap(
25 | [0x18, 0x6, 0x2, 0x1, 0x1, 0x2, 0x6, 0x18]
26 | )
27 | middle_holder = pifacecad.LCDBitmap(
28 | [0x1f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f]
29 | )
30 | left_heart = pifacecad.LCDBitmap(
31 | [0x6, 0xf, 0x1f, 0xf, 0xf, 0x7, 0x3, 0x1]
32 | )
33 | right_heart = pifacecad.LCDBitmap(
34 | [0xc, 0x1e, 0x1f, 0x1f, 0x1e, 0x1c, 0x18, 0x10]
35 | )
36 |
37 | loading = list()
38 | loading.append(pifacecad.LCDBitmap(
39 | [0x1f, 0x11, 0xa, 0x4, 0x4, 0xa, 0x11, 0x1f]
40 | ))
41 | loading.append(pifacecad.LCDBitmap(
42 | [0x1f, 0x11, 0xa, 0x4, 0x4, 0xa, 0x1f, 0x1f]
43 | ))
44 | loading.append(pifacecad.LCDBitmap(
45 | [0x1f, 0x11, 0xa, 0x4, 0x4, 0xe, 0x1f, 0x1f]
46 | ))
47 | loading.append(pifacecad.LCDBitmap(
48 | [0x1f, 0x11, 0xe, 0x4, 0x4, 0xe, 0x1f, 0x1f]
49 | ))
50 | loading.append(pifacecad.LCDBitmap(
51 | [0x1f, 0x1f, 0xe, 0x4, 0x4, 0xe, 0x1f, 0x1f]
52 | ))
53 |
54 |
55 | print("Creating PiFaceCAD object")
56 | pc = pifacecad.PiFaceCAD()
57 |
58 | print("Storing")
59 | pc.lcd.store_custom_bitmap(0, batt_char)
60 | pc.lcd.store_custom_bitmap(1, checkerboard)
61 | pc.lcd.store_custom_bitmap(2, left_holder)
62 | pc.lcd.store_custom_bitmap(3, middle_holder)
63 | pc.lcd.store_custom_bitmap(4, right_holder)
64 | pc.lcd.store_custom_bitmap(5, left_heart)
65 | pc.lcd.store_custom_bitmap(6, right_heart)
66 |
67 | print("Printing")
68 | pc.lcd.write("abc")
69 | for i in range(7):
70 | pc.lcd.write_custom_bitmap(i)
71 | #pc.lcd.write("def")
72 |
73 | input("next? (press enter")
74 | #pc.lcd.clear()
75 | pc.lcd.blink_off()
76 | pc.lcd.cursor_off()
77 | # loading
78 | for i in range(len(loading)):
79 | pc.lcd.store_custom_bitmap(i, loading[i])
80 |
81 | # i = 0
82 | # while True:
83 | # i = (i + 1) % len(loading)
84 | # pc.lcd.write_custom_bitmap(i)
85 | # pc.lcd.home()
86 | # time.sleep(1)
87 |
--------------------------------------------------------------------------------
/tests/test_irandswitches.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import sys
4 | import threading
5 | import multiprocessing
6 | parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 | sys.path.insert(0, parentdir)
8 | import pifacecommon
9 | import pifacecad
10 |
11 |
12 | def input0(event):
13 | print("input 0 was pressed")
14 |
15 |
16 | def input1(event):
17 | print("input 1 was pressed")
18 |
19 |
20 | def input2(event):
21 | print("input 2 was pressed")
22 |
23 |
24 | def input3(event):
25 | print("input 3 was pressed")
26 |
27 |
28 | def ir1(event):
29 | print("ir 1 pressed")
30 |
31 |
32 | def ir2(event):
33 | print("ir 2 pressed")
34 |
35 |
36 | def ir3(event):
37 | print("ir 3 pressed")
38 |
39 |
40 | def ir4(event):
41 | print("ir 4 pressed")
42 |
43 | pifacecad.init()
44 |
45 | sl = pifacecad.SwitchEventListener()
46 | sl.register(0, pifacecad.IODIR_ON, input0)
47 | sl.register(1, pifacecad.IODIR_ON, input1)
48 | sl.register(2, pifacecad.IODIR_ON, input2)
49 | sl.register(3, pifacecad.IODIR_ON, input3)
50 |
51 | irl = pifacecad.IREventListener(
52 | prog="pifacecadtest", lircrc="./tests/testlircrc")
53 | irl.register('1', ir1)
54 | irl.register('2', ir2)
55 | irl.register('3', ir3)
56 | irl.register('4', ir4)
57 |
58 | sl.activate()
59 | irl.activate()
60 |
--------------------------------------------------------------------------------
/tests/test_irint.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import sys
4 | parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 | sys.path.insert(0, parentdir)
6 | import pifacecad
7 |
8 |
9 | pifacecad.init()
10 | pfcad = pifacecad.PiFaceCAD()
11 |
12 |
13 | def turn_backlight_on(ircode):
14 | pfcad.lcd.backlight_on()
15 | return True
16 |
17 |
18 | def turn_backlight_off(ircode):
19 | pfcad.lcd.backlight_off()
20 | return True
21 |
22 |
23 | def write_message(test):
24 | pfcad.lcd.write("Hello, World!")
25 | return True
26 |
27 |
28 | def exit(test):
29 | pfcad.lcd.clear()
30 | pfcad.lcd.write("exiting")
31 | return False
32 |
33 |
34 | irfm = pifacecad.IRFunctionMap()
35 | irfm.register(ir_code="1", callback=turn_backlight_on)
36 | irfm.register(ir_code="2", callback=turn_backlight_off)
37 | irfm.register(ir_code="3", callback=write_message)
38 | irfm.register(ir_code="4", callback=exit)
39 |
40 | pifacecad.wait_for_ir(
41 | ir_func_map=irfm,
42 | ir_config="./lircconf",
43 | prog="pifacecadtest",
44 | )
45 |
--------------------------------------------------------------------------------
/tests/test_pifacecadtools.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import unittest
4 | parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 | sys.path.insert(0, parentdir)
6 | import pifacecad
7 | import pifacecad.tools
8 |
9 |
10 | class TestLCDScanf(unittest.TestCase):
11 |
12 | def setUp(self):
13 | self.cad = pifacecad.PiFaceCAD()
14 | self.cad.lcd.set_cursor(1, 1)
15 |
16 | def test_scanner(self):
17 | correct_answer = [2, "bc", "apple"]
18 | print("Enter", correct_answer)
19 | scanner = pifacecad.tools.LCDScanf(format="%i %2c %m%r",
20 | custom_values=("orange", "apple"),
21 | cad=self.cad)
22 | answer = scanner.scan()
23 | self.assertEqual(answer, correct_answer)
24 |
25 | def tearDown(self):
26 | self.cad.lcd.clear()
27 |
28 |
29 | class TestLCDQuestion(unittest.TestCase):
30 |
31 | def test_question(self):
32 | correct_answer = "Why not?"
33 | print("Enter", correct_answer)
34 |
35 | question = pifacecad.tools.LCDQuestion
36 | the_answers = ("Because I said so",
37 | "Why not?",
38 | "Maybe",
39 | "42")
40 |
41 | question = pifacecad.tools.LCDQuestion(question="Why?",
42 | answers=the_answers)
43 | answer_index = question.ask()
44 | self.assertEqual(answer_index, the_answers.index(correct_answer))
45 |
46 |
47 | if __name__ == "__main__":
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/tests/test_switches.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import sys
4 | parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 | sys.path.insert(0, parentdir)
6 | import pifacecad
7 |
8 |
9 | def input_callback(event):
10 | print("input {} was pressed".format(event.pin_num))
11 | print(pifacecad.PiFaceCAD().switch_port.value)
12 |
13 | pifacecad.init()
14 |
15 | sl = pifacecad.SwitchEventListener()
16 | for i in range(8):
17 | sl.register(i, pifacecad.IODIR_ON, input_callback)
18 |
19 | sl.activate()
20 |
--------------------------------------------------------------------------------
/tests/testlircrc:
--------------------------------------------------------------------------------
1 | begin
2 | remote = *
3 | button = 0
4 | prog = pifacecadtest
5 | config = 0
6 | end
7 |
8 | begin
9 | remote = *
10 | button = 1
11 | prog = pifacecadtest
12 | config = 1
13 | end
14 |
15 | begin
16 | remote = *
17 | button = 2
18 | prog = pifacecadtest
19 | config = 2
20 | end
21 |
22 | begin
23 | remote = *
24 | button = 3
25 | prog = pifacecadtest
26 | config = 3
27 | end
28 |
29 | begin
30 | remote = *
31 | button = 4
32 | prog = pifacecadtest
33 | config = 4
34 | end
35 |
36 | begin
37 | remote = *
38 | button = 5
39 | prog = pifacecadtest
40 | config = 5
41 | end
42 |
43 | begin
44 | remote = *
45 | button = 6
46 | prog = pifacecadtest
47 | config = 6
48 | end
49 |
50 | begin
51 | remote = *
52 | button = 7
53 | prog = pifacecadtest
54 | config = 7
55 | end
56 |
57 | begin
58 | remote = *
59 | button = 8
60 | prog = pifacecadtest
61 | config = 8
62 | end
63 |
64 | begin
65 | remote = *
66 | button = 9
67 | prog = pifacecadtest
68 | config = 9
69 | end
70 |
71 | begin
72 | remote = *
73 | button = V.+
74 | prog = pifacecadtest
75 | config = V.+
76 | end
77 |
--------------------------------------------------------------------------------
/tests/tests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import sys
4 | import unittest
5 | import threading
6 | parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 | sys.path.insert(0, parentdir)
8 | import pifacecommon
9 | import pifacecad
10 |
11 |
12 | PY3 = sys.version_info.major >= 3
13 | if not PY3:
14 | input = raw_input
15 |
16 | from time import sleep
17 |
18 | class Barrier(object):
19 | def __init__(self, n, timeout=None):
20 | self.count = 0
21 | self.n = n
22 | self.timeout = timeout
23 |
24 | def wait(self):
25 | self.count += 1
26 | while self.count < self.n:
27 | sleep(0.0001)
28 |
29 | threading.Barrier = Barrier
30 |
31 |
32 | SWITCH_RANGE = 8
33 |
34 |
35 | # @unittest.skip
36 | class TestPiFaceCADSwitches(unittest.TestCase):
37 | """General use tests (not really in the spirit of unittesting)."""
38 | def setUp(self):
39 | self.cad = pifacecad.PiFaceCAD()
40 |
41 | def test_switches(self):
42 | for a, b in ((0, 7), (1, 6), (2, 5), (3, 4)):
43 | input(
44 | "Hold switch {a} and {b}, then press enter.".format(a=a, b=b))
45 | self.assertEqual(self.cad.switches[a].value, 1)
46 | self.assertEqual(self.cad.switches[a].value, 1)
47 |
48 | # and the switch port
49 | bit_pattern = (1 << a) ^ (1 << b)
50 | self.assertEqual(self.cad.switch_port.value, bit_pattern)
51 | # for i in range(SWITCH_RANGE):
52 | # input(
53 | # "Hold switch {}, then press enter.".format(i))
54 | # self.assertEqual(self.cad.switches[i].value, 1)
55 | # self.assertEqual(self.cad.switches[i].value, 1)
56 |
57 | # # and the switch port
58 | # bit_pattern = (1 << i)
59 | # self.assertEqual(self.cad.switch_port.value, bit_pattern)
60 |
61 |
62 | class TestInterrupts(unittest.TestCase):
63 | def setUp(self):
64 | self.barrier = threading.Barrier(2, timeout=5)
65 | self.test_passed = False
66 | self.direction = pifacecad.IODIR_ON
67 | self.listener = pifacecad.SwitchEventListener()
68 | self.listener.register(0, self.direction, self.interrupts_test_helper)
69 |
70 | def test_interrupt(self):
71 | self.listener.activate()
72 | print("Press switch 0")
73 | self.barrier.wait()
74 | self.assertTrue(self.test_passed)
75 |
76 | def interrupts_test_helper(self, event):
77 | self.assertEqual(event.interrupt_flag, 0x1)
78 | self.assertEqual(event.interrupt_capture, 0xfe)
79 | self.assertEqual(event.pin_num, 0)
80 | self.assertEqual(event.direction, self.direction)
81 | self.test_passed = True
82 | self.barrier.wait()
83 |
84 | def tearDown(self):
85 | self.listener.deactivate()
86 |
87 |
88 | @unittest.skip
89 | class TestIR(unittest.TestCase):
90 | def setUp(self):
91 | self.barrier = threading.Barrier(2, timeout=5)
92 | self.listener = pifacecad.IREventListener(
93 | prog="pifacecadtest",
94 | lircrc="./tests/testlircrc")
95 | self.listener.register('1', self.ir_test_helper)
96 | self.test_passed = False
97 |
98 | def test_interrupt(self):
99 | self.listener.activate()
100 | print("Press remote button 1")
101 | self.barrier.wait()
102 | self.assertTrue(self.test_passed)
103 |
104 | def ir_test_helper(self, event):
105 | self.assertEqual(event.ir_code, '1')
106 | self.test_passed = True
107 | self.barrier.wait()
108 |
109 | def tearDown(self):
110 | self.listener.deactivate()
111 |
112 |
113 | # @unittest.skip
114 | class TestLCD(unittest.TestCase):
115 | def setUp(self):
116 | self.cad = pifacecad.PiFaceCAD()
117 |
118 | def test_normal_display(self):
119 | message = "1234567890123456\nabcdefghijklmnop"
120 | self.cad.lcd.write(message)
121 | question = "Does the screen say:\n{}\n".format(message)
122 | self.assertTrue(yes_no_question(question))
123 | self.cad.lcd.clear()
124 |
125 | def test_backlight(self):
126 | self.cad.lcd.backlight_on()
127 | self.assertTrue(yes_no_question("Is the backlight on?"))
128 | self.cad.lcd.backlight_off()
129 | self.assertTrue(yes_no_question("Is the backlight off?"))
130 |
131 | def test_scroll(self):
132 | message = "0000000000000000A\n0000000000000000A"
133 | self.cad.lcd.write(message)
134 | self.cad.lcd.viewport_corner = 1
135 | shiftedmessage = message = "000000000000000A\n000000000000000A"
136 | question = "Does the screen say:\n{}\n".format(shiftedmessage)
137 | self.assertTrue(yes_no_question(question))
138 | self.cad.lcd.clear()
139 |
140 | def test_set_cursor(self):
141 | self.cad.lcd.set_cursor(15, 1)
142 | self.assertTrue(
143 | yes_no_question("Is the cursor at the bottom right corner?"))
144 | self.cad.lcd.home()
145 |
146 |
147 | def yes_no_question(question):
148 | answer = input("{} [Y/n] ".format(question))
149 | correct_answers = ("y", "yes", "Y", "")
150 | return answer in correct_answers
151 |
152 |
153 | if __name__ == "__main__":
154 | unittest.main()
155 |
--------------------------------------------------------------------------------