diff --git a/badger2040/badge-image.bin b/badger2040/badge-image.bin new file mode 100644 index 0000000..a0e7bdb Binary files /dev/null and b/badger2040/badge-image.bin differ diff --git a/badger2040/badge.txt b/badger2040/badge.txt new file mode 100644 index 0000000..65c0513 --- /dev/null +++ b/badger2040/badge.txt @@ -0,0 +1,6 @@ +Fringe Division +Diane Ducastel +role +Special agent +universe +Blue \ No newline at end of file diff --git a/badger2040/capucine-badge.png b/badger2040/capucine-badge.png new file mode 100644 index 0000000..7778d33 Binary files /dev/null and b/badger2040/capucine-badge.png differ diff --git a/badger2040/diane-badge-image.bin b/badger2040/diane-badge-image.bin new file mode 100644 index 0000000..ed354c5 Binary files /dev/null and b/badger2040/diane-badge-image.bin differ diff --git a/badger2040/diane-badge-image.png b/badger2040/diane-badge-image.png new file mode 100644 index 0000000..be4f515 Binary files /dev/null and b/badger2040/diane-badge-image.png differ diff --git a/badger2040/ferreol-badge-2.png b/badger2040/ferreol-badge-2.png new file mode 100644 index 0000000..1e48727 Binary files /dev/null and b/badger2040/ferreol-badge-2.png differ diff --git a/badger2040/ferreol-badge-image.bin b/badger2040/ferreol-badge-image.bin new file mode 100644 index 0000000..689c034 Binary files /dev/null and b/badger2040/ferreol-badge-image.bin differ diff --git a/badger2040/ferreol-badge.png b/badger2040/ferreol-badge.png new file mode 100644 index 0000000..9bfc013 Binary files /dev/null and b/badger2040/ferreol-badge.png differ diff --git a/badger2040/images/QR-code-buzz-1.bin b/badger2040/images/QR-code-buzz-1.bin new file mode 100644 index 0000000..6d37789 Binary files /dev/null and b/badger2040/images/QR-code-buzz-1.bin differ diff --git a/badger2040/images/QR-code-buzz-2.bin b/badger2040/images/QR-code-buzz-2.bin new file mode 100644 index 0000000..4fabf69 Binary files /dev/null and b/badger2040/images/QR-code-buzz-2.bin differ diff --git a/badger2040/images/badgerpunk.bin b/badger2040/images/badgerpunk.bin new file mode 100644 index 0000000..34953f6 Binary files /dev/null and b/badger2040/images/badgerpunk.bin differ diff --git a/badger2040/images/readme.txt b/badger2040/images/readme.txt new file mode 100644 index 0000000..75907dc --- /dev/null +++ b/badger2040/images/readme.txt @@ -0,0 +1,8 @@ + +Images must be 296x128 pixel with 1bit colour depth. + +You can use examples/badger2040/image_converter/convert.py to convert them: + +python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png + +Create a new "images" directory via Thonny, and upload the .bin files there. diff --git a/badger2040/launcher.json b/badger2040/launcher.json new file mode 100644 index 0000000..89da7ec --- /dev/null +++ b/badger2040/launcher.json @@ -0,0 +1 @@ +{"font_size": 1, "running": "badge", "page": 1, "inverted": false} \ No newline at end of file diff --git a/badger2040/libs/image_converter/convert.py b/badger2040/libs/image_converter/convert.py new file mode 100755 index 0000000..d8613cc --- /dev/null +++ b/badger2040/libs/image_converter/convert.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +Converts images into a format suitable for display on Badger 2040. + +Optionally resizes images to 296x128 to fit the display. + +Crunches images down to dithered, 1bit colour depth. + +Outputs either in raw binary format or as a .py file for embedding into MicroPython. + +Output to py functionality is borrwed from data_to_py.py, Copyright (c) 2016 Peter Hinch +""" + +import io +import argparse +from PIL import Image, ImageEnhance +from pathlib import Path + + +PY_HEADER = """# Code generated by convert.py. +""" + +PY_FOOTER = """_mvdata = memoryview(_data) + +def data(): + return _mvdata + +""" + + +parser = argparse.ArgumentParser(description='Converts images into the format used by Badger2040.') +parser.add_argument('file', nargs="+", help='input files to convert') +parser.add_argument('--out_dir', type=Path, default=None, help='output directory') +parser.add_argument('--binary', action="store_true", help='output binary file for MicroPython') +parser.add_argument('--py', action="store_true", help='output .py file for MicroPython embedding') +parser.add_argument('--resize', action="store_true", help='force images to 296x128 pixels') + +options = parser.parse_args() + + +class ByteWriter(object): + bytes_per_line = 16 + + def __init__(self, stream, varname): + self.stream = stream + self.stream.write('{} =\\\n'.format(varname)) + self.bytecount = 0 # For line breaks + + def _eol(self): + self.stream.write("'\\\n") + + def _eot(self): + self.stream.write("'\n") + + def _bol(self): + self.stream.write("b'") + + # Output a single byte + def obyte(self, data): + if not self.bytecount: + self._bol() + self.stream.write('\\x{:02x}'.format(data)) + self.bytecount += 1 + self.bytecount %= self.bytes_per_line + if not self.bytecount: + self._eol() + + # Output from a sequence + def odata(self, bytelist): + for byt in bytelist: + self.obyte(byt) + + # ensure a correct final line + def eot(self): # User force EOL if one hasn't occurred + if self.bytecount: + self._eot() + self.stream.write('\n') + + +def convert_image(img): + if options.resize: + img = img.resize((296, 128)) # resize + try: + enhancer = ImageEnhance.Contrast(img) + img = enhancer.enhance(2.0) + except ValueError: + pass + img = img.convert("1") # convert to black and white + return img + + +def write_stream(header, footer, ip_stream, op_stream): + op_stream.write(header) + op_stream.write('\n') + data = ip_stream.read() + bw_data = ByteWriter(op_stream, '_data') + bw_data.odata(data) + bw_data.eot() + op_stream.write(footer) + + +# create map of images based on input filenames +for input_filename in options.file: + with Image.open(input_filename) as img: + img = convert_image(img) + + image_name = Path(input_filename).stem + + w, h = img.size + + output_data = [~b & 0xff for b in list(img.tobytes())] + + if options.binary: + if options.out_dir is not None: + output_filename = (options.out_dir / image_name).with_suffix(".bin") + else: + output_filename = Path(input_filename).with_suffix(".bin") + print(f"Saving to {output_filename}, {w}x{h}") + with open(output_filename, "wb") as out: + out.write(bytearray(output_data)) + elif options.py: + if options.out_dir is not None: + output_filename = (options.out_dir / image_name).with_suffix(".py") + else: + output_filename = Path(input_filename).with_suffix(".py") + print(f"Saving to {output_filename}, {w}x{h}") + with open(output_filename, "w") as out: + write_stream(PY_HEADER, PY_FOOTER, io.BytesIO(bytes(output_data)), out) + else: + image_code = '''\ +static const uint8_t {image_name}[{count}] = {{ + {byte_data} +}}; + '''.format(image_name=image_name, count=len(output_data), byte_data=", ".join(str(b) for b in output_data)) + + print(image_code) diff --git a/badger2040/libs/image_converter/data_to_py.py b/badger2040/libs/image_converter/data_to_py.py new file mode 100644 index 0000000..e5515f1 --- /dev/null +++ b/badger2040/libs/image_converter/data_to_py.py @@ -0,0 +1,154 @@ +#! /usr/bin/python3 +# -*- coding: utf-8 -*- + +# The MIT License (MIT) +# +# Copyright (c) 2016 Peter Hinch +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import argparse +import sys +import os + +# UTILITIES FOR WRITING PYTHON SOURCECODE TO A FILE + +# ByteWriter takes as input a variable name and data values and writes +# Python source to an output stream of the form +# my_variable = b'\x01\x02\x03\x04\x05\x06\x07\x08'\ + +# Lines are broken with \ for readability. + + +class ByteWriter(object): + bytes_per_line = 16 + + def __init__(self, stream, varname): + self.stream = stream + self.stream.write('{} =\\\n'.format(varname)) + self.bytecount = 0 # For line breaks + + def _eol(self): + self.stream.write("'\\\n") + + def _eot(self): + self.stream.write("'\n") + + def _bol(self): + self.stream.write("b'") + + # Output a single byte + def obyte(self, data): + if not self.bytecount: + self._bol() + self.stream.write('\\x{:02x}'.format(data)) + self.bytecount += 1 + self.bytecount %= self.bytes_per_line + if not self.bytecount: + self._eol() + + # Output from a sequence + def odata(self, bytelist): + for byt in bytelist: + self.obyte(byt) + + # ensure a correct final line + def eot(self): # User force EOL if one hasn't occurred + if self.bytecount: + self._eot() + self.stream.write('\n') + + +# PYTHON FILE WRITING + +STR01 = """# Code generated by data_to_py.py. +version = '0.1' +""" + +STR02 = """_mvdata = memoryview(_data) + +def data(): + return _mvdata + +""" + + +def write_func(stream, name, arg): + stream.write('def {}():\n return {}\n\n'.format(name, arg)) + + +def write_data(op_path, ip_path): + try: + with open(ip_path, 'rb') as ip_stream: + try: + with open(op_path, 'w') as op_stream: + write_stream(ip_stream, op_stream) + except OSError: + print("Can't open", op_path, 'for writing') + return False + except OSError: + print("Can't open", ip_path) + return False + return True + + +def write_stream(ip_stream, op_stream): + op_stream.write(STR01) + op_stream.write('\n') + data = ip_stream.read() + bw_data = ByteWriter(op_stream, '_data') + bw_data.odata(data) + bw_data.eot() + op_stream.write(STR02) + + +# PARSE COMMAND LINE ARGUMENTS + +def quit(msg): + print(msg) + sys.exit(1) + + +DESC = """data_to_py.py +Utility to convert an arbitrary binary file to Python source. +Sample usage: +data_to_py.py image.jpg image.py + +""" + +if __name__ == "__main__": + parser = argparse.ArgumentParser(__file__, description=DESC, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('infile', type=str, help='Input file path') + parser.add_argument('outfile', type=str, + help='Path and name of output file. Must have .py extension.') + + args = parser.parse_args() + + if not os.path.isfile(args.infile): + quit("Data filename does not exist") + + if not os.path.splitext(args.outfile)[1].upper() == '.PY': + quit('Output filename must have a .py extension.') + + print('Writing Python file.') + if not write_data(args.outfile, args.infile): + sys.exit(1) + + print(args.outfile, 'written successfully.') diff --git a/badger2040/libs/image_converter/test-images/adam.png b/badger2040/libs/image_converter/test-images/adam.png new file mode 100644 index 0000000..0ab4244 Binary files /dev/null and b/badger2040/libs/image_converter/test-images/adam.png differ diff --git a/badger2040/libs/image_converter/test-images/paul.png b/badger2040/libs/image_converter/test-images/paul.png new file mode 100644 index 0000000..4015b41 Binary files /dev/null and b/badger2040/libs/image_converter/test-images/paul.png differ diff --git a/badger2040/libs/image_converter/test-images/shaun.png b/badger2040/libs/image_converter/test-images/shaun.png new file mode 100644 index 0000000..1d80751 Binary files /dev/null and b/badger2040/libs/image_converter/test-images/shaun.png differ diff --git a/badger2040/os/badger2040/README.md b/badger2040/os/badger2040/README.md new file mode 100644 index 0000000..adfa116 --- /dev/null +++ b/badger2040/os/badger2040/README.md @@ -0,0 +1,92 @@ +# Badger 2040 Examples + +- [Function Examples](#function-examples) + - [Battery](#battery) + - [Button Test](#button-test) + - [LED](#led) + - [Pin interrupt](#pin-interrupt) +- [Application Examples](#application-examples) + - [Badge](#badge) + - [Checklist](#checklist) + - [Clock](#clock) + - [E-Book](#e-book) + - [Fonts](#fonts) + - [Image](#image) + - [QR gen](#qr-gen) + - [Launcher](#launcher) + + +## Function Examples + +### Battery +[battery.py](battery.py) + +An example of how to read the battery voltage and display a battery level indicator. + +### Button Test +[button_test.py](button_test.py) + +An example of how to read Badger2040's buttons and display a unique message for each. + +### LED +[led.py](led.py) + +Blinks Badger's LED on and off. + +### Pin interrupt +[pin_interrupt.py](pin_interrupt.py) + +An example of drawing text and graphics and using the buttons. + +## Application Examples + +### Badge +[badge.py](badge.py) + +Create your own name badge! This application looks for two files on your MicroPython drive: +* `badge.txt` - A text file containing 6 lines, corresponding to the 6 different pieces of text on the badge +* `badge-image.bin` - A 108x128px 1-bit colour depth image to display alongside the text. You can use `examples/badger2040/image_converter/convert.py` to convert them: + +```shell +python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png +``` + +### Checklist +[list.py](list.py) + +A checklist application, letting you navigate through items and tick each of them off. + +### Clock +[clock.py](clock.py) + +A simple clock showing the time and date, that uses the E Ink's fast speed to update every second + +### E-Book +[ebook.py](ebook.py) + +A mini text file e-reader. Comes pre-loaded with an excerpt of The Wind In the Willows. + +### Fonts +[fonts.py](fonts.py) + +A demonstration of the various fonts that can be used in your programs. + +### Image +[image.py](image.py) + +An image gallery. Displays and lets you cycle through any images stored within the MicroPython device's `/images` directory. Images must be 296x128 pixels with 1-bit colour depth. You can use `examples/badger2040/image_converter/convert.py` to convert them: + +```shell +python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png +``` + +### QR gen +[qrgen.py](qrgen.py) + +This application looks for a file on your MicroPython drive: +- `qrcode.txt` - A text file containing 9 lines. The first line should be a URL which will be converted into and displayed as a QR code. Up to 8 more lines of information can be added, which will be shown as plain text to the right of the QR code. + +### Launcher +[launcher.py](launcher.py) + +A launcher-style application, that provide a menu of other applications that can be loaded, as well as information such as battery level. diff --git a/badger2040/os/badger2040/assets/289-0-wind-in-the-willows-abridged.txt b/badger2040/os/badger2040/assets/289-0-wind-in-the-willows-abridged.txt new file mode 100644 index 0000000..f6243fd --- /dev/null +++ b/badger2040/os/badger2040/assets/289-0-wind-in-the-willows-abridged.txt @@ -0,0 +1,1341 @@ +The Project Gutenberg eBook of The Wind in the Willows, by Kenneth Grahame + +This eBook is for the use of anyone anywhere in the United States and +most other parts of the world at no cost and with almost no restrictions +whatsoever. You may copy it, give it away or re-use it under the terms +of the Project Gutenberg License included with this eBook or online at +www.gutenberg.org. If you are not located in the United States, you +will have to check the laws of the country where you are located before +using this eBook. + +Title: The Wind in the Willows + +Author: Kenneth Grahame + +Release Date: July, 1995 [eBook #289] +[Most recently updated: May 15, 2021] + +Language: English + +Character set encoding: UTF-8 + +Produced by: Mike Lough and David Widger + +*** START OF THE PROJECT GUTENBERG EBOOK THE WIND IN THE WILLOWS *** + +[Illustration] + + + + +The Wind in the Willows + +by Kenneth Grahame + +Author Of “The Golden Age,” “Dream Days,” Etc. + + +Contents + + CHAPTER I. THE RIVER BANK + CHAPTER II. THE OPEN ROAD + + + + +I. +THE RIVER BANK + + +The Mole had been working very hard all the morning, spring-cleaning +his little home. First with brooms, then with dusters; then on ladders +and steps and chairs, with a brush and a pail of whitewash; till he had +dust in his throat and eyes, and splashes of whitewash all over his +black fur, and an aching back and weary arms. Spring was moving in the +air above and in the earth below and around him, penetrating even his +dark and lowly little house with its spirit of divine discontent and +longing. It was small wonder, then, that he suddenly flung down his +brush on the floor, said “Bother!” and “O blow!” and also “Hang +spring-cleaning!” and bolted out of the house without even waiting to +put on his coat. Something up above was calling him imperiously, and he +made for the steep little tunnel which answered in his case to the +gravelled carriage-drive owned by animals whose residences are nearer +to the sun and air. So he scraped and scratched and scrabbled and +scrooged and then he scrooged again and scrabbled and scratched and +scraped, working busily with his little paws and muttering to himself, +“Up we go! Up we go!” till at last, pop! his snout came out into the +sunlight, and he found himself rolling in the warm grass of a great +meadow. + +“This is fine!” he said to himself. “This is better than whitewashing!” +The sunshine struck hot on his fur, soft breezes caressed his heated +brow, and after the seclusion of the cellarage he had lived in so long +the carol of happy birds fell on his dulled hearing almost like a +shout. Jumping off all his four legs at once, in the joy of living and +the delight of spring without its cleaning, he pursued his way across +the meadow till he reached the hedge on the further side. + +“Hold up!” said an elderly rabbit at the gap. “Sixpence for the +privilege of passing by the private road!” He was bowled over in an +instant by the impatient and contemptuous Mole, who trotted along the +side of the hedge chaffing the other rabbits as they peeped hurriedly +from their holes to see what the row was about. “Onion-sauce! +Onion-sauce!” he remarked jeeringly, and was gone before they could +think of a thoroughly satisfactory reply. Then they all started +grumbling at each other. “How _stupid_ you are! Why didn’t you tell +him——” “Well, why didn’t _you_ say——” “You might have reminded him——” +and so on, in the usual way; but, of course, it was then much too late, +as is always the case. + +It all seemed too good to be true. Hither and thither through the +meadows he rambled busily, along the hedgerows, across the copses, +finding everywhere birds building, flowers budding, leaves +thrusting—everything happy, and progressive, and occupied. And instead +of having an uneasy conscience pricking him and whispering “whitewash!” +he somehow could only feel how jolly it was to be the only idle dog +among all these busy citizens. After all, the best part of a holiday is +perhaps not so much to be resting yourself, as to see all the other +fellows busy working. + +He thought his happiness was complete when, as he meandered aimlessly +along, suddenly he stood by the edge of a full-fed river. Never in his +life had he seen a river before—this sleek, sinuous, full-bodied +animal, chasing and chuckling, gripping things with a gurgle and +leaving them with a laugh, to fling itself on fresh playmates that +shook themselves free, and were caught and held again. All was a-shake +and a-shiver—glints and gleams and sparkles, rustle and swirl, chatter +and bubble. The Mole was bewitched, entranced, fascinated. By the side +of the river he trotted as one trots, when very small, by the side of a +man who holds one spell-bound by exciting stories; and when tired at +last, he sat on the bank, while the river still chattered on to him, a +babbling procession of the best stories in the world, sent from the +heart of the earth to be told at last to the insatiable sea. + +As he sat on the grass and looked across the river, a dark hole in the +bank opposite, just above the water’s edge, caught his eye, and +dreamily he fell to considering what a nice snug dwelling-place it +would make for an animal with few wants and fond of a bijou riverside +residence, above flood level and remote from noise and dust. As he +gazed, something bright and small seemed to twinkle down in the heart +of it, vanished, then twinkled once more like a tiny star. But it could +hardly be a star in such an unlikely situation; and it was too +glittering and small for a glow-worm. Then, as he looked, it winked at +him, and so declared itself to be an eye; and a small face began +gradually to grow up round it, like a frame round a picture. + +A brown little face, with whiskers. + +A grave round face, with the same twinkle in its eye that had first +attracted his notice. + +Small neat ears and thick silky hair. + +It was the Water Rat! + +Then the two animals stood and regarded each other cautiously. + +“Hullo, Mole!” said the Water Rat. + +“Hullo, Rat!” said the Mole. + +“Would you like to come over?” enquired the Rat presently. + +“Oh, its all very well to _talk_,” said the Mole, rather pettishly, he +being new to a river and riverside life and its ways. + +The Rat said nothing, but stooped and unfastened a rope and hauled on +it; then lightly stepped into a little boat which the Mole had not +observed. It was painted blue outside and white within, and was just +the size for two animals; and the Mole’s whole heart went out to it at +once, even though he did not yet fully understand its uses. + +The Rat sculled smartly across and made fast. Then he held up his +forepaw as the Mole stepped gingerly down. “Lean on that!” he said. +“Now then, step lively!” and the Mole to his surprise and rapture found +himself actually seated in the stern of a real boat. + +“This has been a wonderful day!” said he, as the Rat shoved off and +took to the sculls again. “Do you know, I’ve never been in a boat +before in all my life.” + +“What?” cried the Rat, open-mouthed: “Never been in a—you never—well +I—what have you been doing, then?” + +“Is it so nice as all that?” asked the Mole shyly, though he was quite +prepared to believe it as he leant back in his seat and surveyed the +cushions, the oars, the rowlocks, and all the fascinating fittings, and +felt the boat sway lightly under him. + +“Nice? It’s the _only_ thing,” said the Water Rat solemnly, as he leant +forward for his stroke. “Believe me, my young friend, there is +_nothing_—absolute nothing—half so much worth doing as simply messing +about in boats. Simply messing,” he went on dreamily: +“messing—about—in—boats; messing——” + +“Look ahead, Rat!” cried the Mole suddenly. + +It was too late. The boat struck the bank full tilt. The dreamer, the +joyous oarsman, lay on his back at the bottom of the boat, his heels in +the air. + +“—about in boats—or _with_ boats,” the Rat went on composedly, picking +himself up with a pleasant laugh. “In or out of ’em, it doesn’t matter. +Nothing seems really to matter, that’s the charm of it. Whether you get +away, or whether you don’t; whether you arrive at your destination or +whether you reach somewhere else, or whether you never get anywhere at +all, you’re always busy, and you never do anything in particular; and +when you’ve done it there’s always something else to do, and you can do +it if you like, but you’d much better not. Look here! If you’ve really +nothing else on hand this morning, supposing we drop down the river +together, and have a long day of it?” + +The Mole waggled his toes from sheer happiness, spread his chest with a +sigh of full contentment, and leaned back blissfully into the soft +cushions. “_What_ a day I’m having!” he said. “Let us start at once!” + +“Hold hard a minute, then!” said the Rat. He looped the painter through +a ring in his landing-stage, climbed up into his hole above, and after +a short interval reappeared staggering under a fat, wicker +luncheon-basket. + +“Shove that under your feet,” he observed to the Mole, as he passed it +down into the boat. Then he untied the painter and took the sculls +again. + +“What’s inside it?” asked the Mole, wriggling with curiosity. + +“There’s cold chicken inside it,” replied the Rat briefly; “ +coldtonguecoldhamcoldbeefpickledgherkinssaladfrenchrollscresssandwiches +pottedme atgingerbeerlemonadesodawater——” + +“O stop, stop,” cried the Mole in ecstacies: “This is too much!” + +“Do you really think so?” enquired the Rat seriously. “It’s only what I +always take on these little excursions; and the other animals are +always telling me that I’m a mean beast and cut it _very_ fine!” + +The Mole never heard a word he was saying. Absorbed in the new life he +was entering upon, intoxicated with the sparkle, the ripple, the scents +and the sounds and the sunlight, he trailed a paw in the water and +dreamed long waking dreams. The Water Rat, like the good little fellow +he was, sculled steadily on and forebore to disturb him. + +“I like your clothes awfully, old chap,” he remarked after some half an +hour or so had passed. “I’m going to get a black velvet smoking-suit +myself some day, as soon as I can afford it.” + +“I beg your pardon,” said the Mole, pulling himself together with an +effort. “You must think me very rude; but all this is so new to me. +So—this—is—a—River!” + +“_The_ River,” corrected the Rat. + +“And you really live by the river? What a jolly life!” + +“By it and with it and on it and in it,” said the Rat. “It’s brother +and sister to me, and aunts, and company, and food and drink, and +(naturally) washing. It’s my world, and I don’t want any other. What it +hasn’t got is not worth having, and what it doesn’t know is not worth +knowing. Lord! the times we’ve had together! Whether in winter or +summer, spring or autumn, it’s always got its fun and its excitements. +When the floods are on in February, and my cellars and basement are +brimming with drink that’s no good to me, and the brown water runs by +my best bedroom window; or again when it all drops away and, shows +patches of mud that smells like plum-cake, and the rushes and weed clog +the channels, and I can potter about dry shod over most of the bed of +it and find fresh food to eat, and things careless people have dropped +out of boats!” + +“But isn’t it a bit dull at times?” the Mole ventured to ask. “Just you +and the river, and no one else to pass a word with?” + +“No one else to—well, I mustn’t be hard on you,” said the Rat with +forbearance. “You’re new to it, and of course you don’t know. The bank +is so crowded nowadays that many people are moving away altogether: O +no, it isn’t what it used to be, at all. Otters, kingfishers, +dabchicks, moorhens, all of them about all day long and always wanting +you to _do_ something—as if a fellow had no business of his own to +attend to!” + +“What lies over _there?_” asked the Mole, waving a paw towards a +background of woodland that darkly framed the water-meadows on one side +of the river. + +“That? O, that’s just the Wild Wood,” said the Rat shortly. “We don’t +go there very much, we river-bankers.” + +“Aren’t they—aren’t they very _nice_ people in there?” said the Mole, a +trifle nervously. + +“W-e-ll,” replied the Rat, “let me see. The squirrels are all right. +_And_ the rabbits—some of ’em, but rabbits are a mixed lot. And then +there’s Badger, of course. He lives right in the heart of it; wouldn’t +live anywhere else, either, if you paid him to do it. Dear old Badger! +Nobody interferes with _him_. They’d better not,” he added +significantly. + +“Why, who _should_ interfere with him?” asked the Mole. + +“Well, of course—there—are others,” explained the Rat in a hesitating +sort of way. + +“Weasels—and stoats—and foxes—and so on. They’re all right in a way—I’m +very good friends with them—pass the time of day when we meet, and all +that—but they break out sometimes, there’s no denying it, and +then—well, you can’t really trust them, and that’s the fact.” + +The Mole knew well that it is quite against animal-etiquette to dwell +on possible trouble ahead, or even to allude to it; so he dropped the +subject. + +“And beyond the Wild Wood again?” he asked: “Where it’s all blue and +dim, and one sees what may be hills or perhaps they mayn’t, and +something like the smoke of towns, or is it only cloud-drift?” + +“Beyond the Wild Wood comes the Wide World,” said the Rat. “And that’s +something that doesn’t matter, either to you or me. I’ve never been +there, and I’m never going, nor you either, if you’ve got any sense at +all. Don’t ever refer to it again, please. Now then! Here’s our +backwater at last, where we’re going to lunch.” + +Leaving the main stream, they now passed into what seemed at first +sight like a little land-locked lake. Green turf sloped down to either +edge, brown snaky tree-roots gleamed below the surface of the quiet +water, while ahead of them the silvery shoulder and foamy tumble of a +weir, arm-in-arm with a restless dripping mill-wheel, that held up in +its turn a grey-gabled mill-house, filled the air with a soothing +murmur of sound, dull and smothery, yet with little clear voices +speaking up cheerfully out of it at intervals. It was so very beautiful +that the Mole could only hold up both forepaws and gasp, “O my! O my! O +my!” + +The Rat brought the boat alongside the bank, made her fast, helped the +still awkward Mole safely ashore, and swung out the luncheon-basket. +The Mole begged as a favour to be allowed to unpack it all by himself; +and the Rat was very pleased to indulge him, and to sprawl at full +length on the grass and rest, while his excited friend shook out the +table-cloth and spread it, took out all the mysterious packets one by +one and arranged their contents in due order, still gasping, “O my! O +my!” at each fresh revelation. When all was ready, the Rat said, “Now, +pitch in, old fellow!” and the Mole was indeed very glad to obey, for +he had started his spring-cleaning at a very early hour that morning, +as people _will_ do, and had not paused for bite or sup; and he had +been through a very great deal since that distant time which now seemed +so many days ago. + +“What are you looking at?” said the Rat presently, when the edge of +their hunger was somewhat dulled, and the Mole’s eyes were able to +wander off the table-cloth a little. + +“I am looking,” said the Mole, “at a streak of bubbles that I see +travelling along the surface of the water. That is a thing that strikes +me as funny.” + +“Bubbles? Oho!” said the Rat, and chirruped cheerily in an inviting +sort of way. + +A broad glistening muzzle showed itself above the edge of the bank, and +the Otter hauled himself out and shook the water from his coat. + +“Greedy beggars!” he observed, making for the provender. “Why didn’t +you invite me, Ratty?” + +“This was an impromptu affair,” explained the Rat. “By the way—my +friend Mr. Mole.” + +“Proud, I’m sure,” said the Otter, and the two animals were friends +forthwith. + +“Such a rumpus everywhere!” continued the Otter. “All the world seems +out on the river to-day. I came up this backwater to try and get a +moment’s peace, and then stumble upon you fellows!—At least—I beg +pardon—I don’t exactly mean that, you know.” + +There was a rustle behind them, proceeding from a hedge wherein last +year’s leaves still clung thick, and a stripy head, with high shoulders +behind it, peered forth on them. + +“Come on, old Badger!” shouted the Rat. + +The Badger trotted forward a pace or two; then grunted, “H’m! Company,” +and turned his back and disappeared from view. + +“That’s _just_ the sort of fellow he is!” observed the disappointed +Rat. “Simply hates Society! Now we shan’t see any more of him to-day. +Well, tell us, _who’s_ out on the river?” + +“Toad’s out, for one,” replied the Otter. “In his brand-new wager-boat; +new togs, new everything!” + +The two animals looked at each other and laughed. + +“Once, it was nothing but sailing,” said the Rat, “Then he tired of +that and took to punting. Nothing would please him but to punt all day +and every day, and a nice mess he made of it. Last year it was +house-boating, and we all had to go and stay with him in his +house-boat, and pretend we liked it. He was going to spend the rest of +his life in a house-boat. It’s all the same, whatever he takes up; he +gets tired of it, and starts on something fresh.” + +“Such a good fellow, too,” remarked the Otter reflectively: “But no +stability—especially in a boat!” + +From where they sat they could get a glimpse of the main stream across +the island that separated them; and just then a wager-boat flashed into +view, the rower—a short, stout figure—splashing badly and rolling a +good deal, but working his hardest. The Rat stood up and hailed him, +but Toad—for it was he—shook his head and settled sternly to his work. + +“He’ll be out of the boat in a minute if he rolls like that,” said the +Rat, sitting down again. + +“Of course he will,” chuckled the Otter. “Did I ever tell you that good +story about Toad and the lock-keeper? It happened this way. Toad....” + +An errant May-fly swerved unsteadily athwart the current in the +intoxicated fashion affected by young bloods of May-flies seeing life. +A swirl of water and a “cloop!” and the May-fly was visible no more. + +Neither was the Otter. + +The Mole looked down. The voice was still in his ears, but the turf +whereon he had sprawled was clearly vacant. Not an Otter to be seen, as +far as the distant horizon. + +But again there was a streak of bubbles on the surface of the river. + +The Rat hummed a tune, and the Mole recollected that animal-etiquette +forbade any sort of comment on the sudden disappearance of one’s +friends at any moment, for any reason or no reason whatever. + +“Well, well,” said the Rat, “I suppose we ought to be moving. I wonder +which of us had better pack the luncheon-basket?” He did not speak as +if he was frightfully eager for the treat. + +“O, please let me,” said the Mole. So, of course, the Rat let him. + +Packing the basket was not quite such pleasant work as unpacking the +basket. It never is. But the Mole was bent on enjoying everything, and +although just when he had got the basket packed and strapped up tightly +he saw a plate staring up at him from the grass, and when the job had +been done again the Rat pointed out a fork which anybody ought to have +seen, and last of all, behold! the mustard pot, which he had been +sitting on without knowing it—still, somehow, the thing got finished at +last, without much loss of temper. + +The afternoon sun was getting low as the Rat sculled gently homewards +in a dreamy mood, murmuring poetry-things over to himself, and not +paying much attention to Mole. But the Mole was very full of lunch, and +self-satisfaction, and pride, and already quite at home in a boat (so +he thought) and was getting a bit restless besides: and presently he +said, “Ratty! Please, _I_ want to row, now!” + +The Rat shook his head with a smile. “Not yet, my young friend,” he +said—“wait till you’ve had a few lessons. It’s not so easy as it +looks.” + +The Mole was quiet for a minute or two. But he began to feel more and +more jealous of Rat, sculling so strongly and so easily along, and his +pride began to whisper that he could do it every bit as well. He jumped +up and seized the sculls, so suddenly, that the Rat, who was gazing out +over the water and saying more poetry-things to himself, was taken by +surprise and fell backwards off his seat with his legs in the air for +the second time, while the triumphant Mole took his place and grabbed +the sculls with entire confidence. + +“Stop it, you _silly_ ass!” cried the Rat, from the bottom of the boat. +“You can’t do it! You’ll have us over!” + +The Mole flung his sculls back with a flourish, and made a great dig at +the water. He missed the surface altogether, his legs flew up above his +head, and he found himself lying on the top of the prostrate Rat. +Greatly alarmed, he made a grab at the side of the boat, and the next +moment—Sploosh! + +Over went the boat, and he found himself struggling in the river. + +O my, how cold the water was, and O, how _very_ wet it felt. How it +sang in his ears as he went down, down, down! How bright and welcome +the sun looked as he rose to the surface coughing and spluttering! How +black was his despair when he felt himself sinking again! Then a firm +paw gripped him by the back of his neck. It was the Rat, and he was +evidently laughing—the Mole could _feel_ him laughing, right down his +arm and through his paw, and so into his—the Mole’s—neck. + +The Rat got hold of a scull and shoved it under the Mole’s arm; then he +did the same by the other side of him and, swimming behind, propelled +the helpless animal to shore, hauled him out, and set him down on the +bank, a squashy, pulpy lump of misery. + +When the Rat had rubbed him down a bit, and wrung some of the wet out +of him, he said, “Now, then, old fellow! Trot up and down the +towing-path as hard as you can, till you’re warm and dry again, while I +dive for the luncheon-basket.” + +So the dismal Mole, wet without and ashamed within, trotted about till +he was fairly dry, while the Rat plunged into the water again, +recovered the boat, righted her and made her fast, fetched his floating +property to shore by degrees, and finally dived successfully for the +luncheon-basket and struggled to land with it. + +When all was ready for a start once more, the Mole, limp and dejected, +took his seat in the stern of the boat; and as they set off, he said in +a low voice, broken with emotion, “Ratty, my generous friend! I am very +sorry indeed for my foolish and ungrateful conduct. My heart quite +fails me when I think how I might have lost that beautiful +luncheon-basket. Indeed, I have been a complete ass, and I know it. +Will you overlook it this once and forgive me, and let things go on as +before?” + +“That’s all right, bless you!” responded the Rat cheerily. “What’s a +little wet to a Water Rat? I’m more in the water than out of it most +days. Don’t you think any more about it; and, look here! I really think +you had better come and stop with me for a little time. It’s very plain +and rough, you know—not like Toad’s house at all—but you haven’t seen +that yet; still, I can make you comfortable. And I’ll teach you to row, +and to swim, and you’ll soon be as handy on the water as any of us.” + +The Mole was so touched by his kind manner of speaking that he could +find no voice to answer him; and he had to brush away a tear or two +with the back of his paw. But the Rat kindly looked in another +direction, and presently the Mole’s spirits revived again, and he was +even able to give some straight back-talk to a couple of moorhens who +were sniggering to each other about his bedraggled appearance. + +When they got home, the Rat made a bright fire in the parlour, and +planted the Mole in an arm-chair in front of it, having fetched down a +dressing-gown and slippers for him, and told him river stories till +supper-time. Very thrilling stories they were, too, to an +earth-dwelling animal like Mole. Stories about weirs, and sudden +floods, and leaping pike, and steamers that flung hard bottles—at least +bottles were certainly flung, and _from_ steamers, so presumably _by_ +them; and about herons, and how particular they were whom they spoke +to; and about adventures down drains, and night-fishings with Otter, or +excursions far a-field with Badger. Supper was a most cheerful meal; +but very shortly afterwards a terribly sleepy Mole had to be escorted +upstairs by his considerate host, to the best bedroom, where he soon +laid his head on his pillow in great peace and contentment, knowing +that his new-found friend the River was lapping the sill of his window. + +This day was only the first of many similar ones for the emancipated +Mole, each of them longer and full of interest as the ripening summer +moved onward. He learnt to swim and to row, and entered into the joy of +running water; and with his ear to the reed-stems he caught, at +intervals, something of what the wind went whispering so constantly +among them. + + + + +II. +THE OPEN ROAD + + +“Ratty,” said the Mole suddenly, one bright summer morning, “if you +please, I want to ask you a favour.” + +The Rat was sitting on the river bank, singing a little song. He had +just composed it himself, so he was very taken up with it, and would +not pay proper attention to Mole or anything else. Since early morning +he had been swimming in the river, in company with his friends the +ducks. And when the ducks stood on their heads suddenly, as ducks will, +he would dive down and tickle their necks, just under where their chins +would be if ducks had chins, till they were forced to come to the +surface again in a hurry, spluttering and angry and shaking their +feathers at him, for it is impossible to say quite _all_ you feel when +your head is under water. At last they implored him to go away and +attend to his own affairs and leave them to mind theirs. So the Rat +went away, and sat on the river bank in the sun, and made up a song +about them, which he called + +“DUCKS’ DITTY.” + +All along the backwater, +Through the rushes tall, +Ducks are a-dabbling, +Up tails all! +Ducks’ tails, drakes’ tails, +Yellow feet a-quiver, +Yellow bills all out of sight +Busy in the river! + +Slushy green undergrowth +Where the roach swim— +Here we keep our larder, +Cool and full and dim. + +Everyone for what he likes! +_We_ like to be +Heads down, tails up, +Dabbling free! + +High in the blue above +Swifts whirl and call— +_We_ are down a-dabbling +Uptails all! + + +“I don’t know that I think so _very_ much of that little song, Rat,” +observed the Mole cautiously. He was no poet himself and didn’t care +who knew it; and he had a candid nature. + +“Nor don’t the ducks neither,” replied the Rat cheerfully. “They say, +‘_Why_ can’t fellows be allowed to do what they like _when_ they like +and _as_ they like, instead of other fellows sitting on banks and +watching them all the time and making remarks and poetry and things +about them? What _nonsense_ it all is!’ That’s what the ducks say.” + +“So it is, so it is,” said the Mole, with great heartiness. + +“No, it isn’t!” cried the Rat indignantly. + +“Well then, it isn’t, it isn’t,” replied the Mole soothingly. “But what +I wanted to ask you was, won’t you take me to call on Mr. Toad? I’ve +heard so much about him, and I do so want to make his acquaintance.” + +“Why, certainly,” said the good-natured Rat, jumping to his feet and +dismissing poetry from his mind for the day. “Get the boat out, and +we’ll paddle up there at once. It’s never the wrong time to call on +Toad. Early or late he’s always the same fellow. Always good-tempered, +always glad to see you, always sorry when you go!” + +“He must be a very nice animal,” observed the Mole, as he got into the +boat and took the sculls, while the Rat settled himself comfortably in +the stern. + +“He is indeed the best of animals,” replied Rat. “So simple, so +good-natured, and so affectionate. Perhaps he’s not very clever—we +can’t all be geniuses; and it may be that he is both boastful and +conceited. But he has got some great qualities, has Toady.” + +Rounding a bend in the river, they came in sight of a handsome, +dignified old house of mellowed red brick, with well-kept lawns +reaching down to the water’s edge. + +“There’s Toad Hall,” said the Rat; “and that creek on the left, where +the notice-board says, ‘Private. No landing allowed,’ leads to his +boat-house, where we’ll leave the boat. The stables are over there to +the right. That’s the banqueting-hall you’re looking at now—very old, +that is. Toad is rather rich, you know, and this is really one of the +nicest houses in these parts, though we never admit as much to Toad.” + +They glided up the creek, and the Mole shipped his sculls as they +passed into the shadow of a large boat-house. Here they saw many +handsome boats, slung from the cross beams or hauled up on a slip, but +none in the water; and the place had an unused and a deserted air. + +The Rat looked around him. “I understand,” said he. “Boating is played +out. He’s tired of it, and done with it. I wonder what new fad he has +taken up now? Come along and let’s look him up. We shall hear all about +it quite soon enough.” + +They disembarked, and strolled across the gay flower-decked lawns in +search of Toad, whom they presently happened upon resting in a wicker +garden-chair, with a pre-occupied expression of face, and a large map +spread out on his knees. + +“Hooray!” he cried, jumping up on seeing them, “this is splendid!” He +shook the paws of both of them warmly, never waiting for an +introduction to the Mole. “How _kind_ of you!” he went on, dancing +round them. “I was just going to send a boat down the river for you, +Ratty, with strict orders that you were to be fetched up here at once, +whatever you were doing. I want you badly—both of you. Now what will +you take? Come inside and have something! You don’t know how lucky it +is, your turning up just now!” + +“Let’s sit quiet a bit, Toady!” said the Rat, throwing himself into an +easy chair, while the Mole took another by the side of him and made +some civil remark about Toad’s “delightful residence.” + +“Finest house on the whole river,” cried Toad boisterously. “Or +anywhere else, for that matter,” he could not help adding. + +Here the Rat nudged the Mole. Unfortunately the Toad saw him do it, and +turned very red. There was a moment’s painful silence. Then Toad burst +out laughing. “All right, Ratty,” he said. “It’s only my way, you know. +And it’s not such a very bad house, is it? You know you rather like it +yourself. Now, look here. Let’s be sensible. You are the very animals I +wanted. You’ve got to help me. It’s most important!” + +“It’s about your rowing, I suppose,” said the Rat, with an innocent +air. “You’re getting on fairly well, though you splash a good bit +still. With a great deal of patience, and any quantity of coaching, you +may——” + +“O, pooh! boating!” interrupted the Toad, in great disgust. “Silly +boyish amusement. I’ve given that up _long_ ago. Sheer waste of time, +that’s what it is. It makes me downright sorry to see you fellows, who +ought to know better, spending all your energies in that aimless +manner. No, I’ve discovered the real thing, the only genuine occupation +for a life time. I propose to devote the remainder of mine to it, and +can only regret the wasted years that lie behind me, squandered in +trivialities. Come with me, dear Ratty, and your amiable friend also, +if he will be so very good, just as far as the stable-yard, and you +shall see what you shall see!” + +He led the way to the stable-yard accordingly, the Rat following with a +most mistrustful expression; and there, drawn out of the coach house +into the open, they saw a gipsy caravan, shining with newness, painted +a canary-yellow picked out with green, and red wheels. + +“There you are!” cried the Toad, straddling and expanding himself. +“There’s real life for you, embodied in that little cart. The open +road, the dusty highway, the heath, the common, the hedgerows, the +rolling downs! Camps, villages, towns, cities! Here to-day, up and off +to somewhere else to-morrow! Travel, change, interest, excitement! The +whole world before you, and a horizon that’s always changing! And mind! +this is the very finest cart of its sort that was ever built, without +any exception. Come inside and look at the arrangements. Planned ’em +all myself, I did!” + +The Mole was tremendously interested and excited, and followed him +eagerly up the steps and into the interior of the caravan. The Rat only +snorted and thrust his hands deep into his pockets, remaining where he +was. + +It was indeed very compact and comfortable. Little sleeping bunks—a +little table that folded up against the wall—a cooking-stove, lockers, +bookshelves, a bird-cage with a bird in it; and pots, pans, jugs and +kettles of every size and variety. + +“All complete!” said the Toad triumphantly, pulling open a locker. “You +see—biscuits, potted lobster, sardines—everything you can possibly +want. Soda-water here—baccy there—letter-paper, bacon, jam, cards and +dominoes—you’ll find,” he continued, as they descended the steps again, +“you’ll find that nothing what ever has been forgotten, when we make +our start this afternoon.” + +“I beg your pardon,” said the Rat slowly, as he chewed a straw, “but +did I overhear you say something about ‘_we_,’ and ‘_start_,’ and +‘_this afternoon?_’” + +“Now, you dear good old Ratty,” said Toad, imploringly, “don’t begin +talking in that stiff and sniffy sort of way, because you know you’ve +_got_ to come. I can’t possibly manage without you, so please consider +it settled, and don’t argue—it’s the one thing I can’t stand. You +surely don’t mean to stick to your dull fusty old river all your life, +and just live in a hole in a bank, and _boat?_ I want to show you the +world! I’m going to make an _animal_ of you, my boy!” + +“I don’t care,” said the Rat, doggedly. “I’m not coming, and that’s +flat. And I _am_ going to stick to my old river, _and_ live in a hole, +_and_ boat, as I’ve always done. And what’s more, Mole’s going to stick +to me and do as I do, aren’t you, Mole?” + +“Of course I am,” said the Mole, loyally. “I’ll always stick to you, +Rat, and what you say is to be—has got to be. All the same, it sounds +as if it might have been—well, rather fun, you know!” he added, +wistfully. Poor Mole! The Life Adventurous was so new a thing to him, +and so thrilling; and this fresh aspect of it was so tempting; and he +had fallen in love at first sight with the canary-coloured cart and all +its little fitments. + +The Rat saw what was passing in his mind, and wavered. He hated +disappointing people, and he was fond of the Mole, and would do almost +anything to oblige him. Toad was watching both of them closely. + +“Come along in, and have some lunch,” he said, diplomatically, “and +we’ll talk it over. We needn’t decide anything in a hurry. Of course, +_I_ don’t really care. I only want to give pleasure to you fellows. +‘Live for others!’ That’s my motto in life.” + +During luncheon—which was excellent, of course, as everything at Toad +Hall always was—the Toad simply let himself go. Disregarding the Rat, +he proceeded to play upon the inexperienced Mole as on a harp. +Naturally a voluble animal, and always mastered by his imagination, he +painted the prospects of the trip and the joys of the open life and the +roadside in such glowing colours that the Mole could hardly sit in his +chair for excitement. Somehow, it soon seemed taken for granted by all +three of them that the trip was a settled thing; and the Rat, though +still unconvinced in his mind, allowed his good-nature to over-ride his +personal objections. He could not bear to disappoint his two friends, +who were already deep in schemes and anticipations, planning out each +day’s separate occupation for several weeks ahead. + +When they were quite ready, the now triumphant Toad led his companions +to the paddock and set them to capture the old grey horse, who, without +having been consulted, and to his own extreme annoyance, had been told +off by Toad for the dustiest job in this dusty expedition. He frankly +preferred the paddock, and took a deal of catching. Meantime Toad +packed the lockers still tighter with necessaries, and hung nosebags, +nets of onions, bundles of hay, and baskets from the bottom of the +cart. At last the horse was caught and harnessed, and they set off, all +talking at once, each animal either trudging by the side of the cart or +sitting on the shaft, as the humour took him. It was a golden +afternoon. The smell of the dust they kicked up was rich and +satisfying; out of thick orchards on either side the road, birds called +and whistled to them cheerily; good-natured wayfarers, passing them, +gave them “Good-day,” or stopped to say nice things about their +beautiful cart; and rabbits, sitting at their front doors in the +hedgerows, held up their fore-paws, and said, “O my! O my! O my!” + +Late in the evening, tired and happy and miles from home, they drew up +on a remote common far from habitations, turned the horse loose to +graze, and ate their simple supper sitting on the grass by the side of +the cart. Toad talked big about all he was going to do in the days to +come, while stars grew fuller and larger all around them, and a yellow +moon, appearing suddenly and silently from nowhere in particular, came +to keep them company and listen to their talk. At last they turned in +to their little bunks in the cart; and Toad, kicking out his legs, +sleepily said, “Well, good night, you fellows! This is the real life +for a gentleman! Talk about your old river!” + +“I _don’t_ talk about my river,” replied the patient Rat. “You _know_ I +don’t, Toad. But I _think_ about it,” he added pathetically, in a lower +tone: “I think about it—all the time!” + +The Mole reached out from under his blanket, felt for the Rat’s paw in +the darkness, and gave it a squeeze. “I’ll do whatever you like, +Ratty,” he whispered. “Shall we run away to-morrow morning, quite +early—_very_ early—and go back to our dear old hole on the river?” + +“No, no, we’ll see it out,” whispered back the Rat. “Thanks awfully, +but I ought to stick by Toad till this trip is ended. It wouldn’t be +safe for him to be left to himself. It won’t take very long. His fads +never do. Good night!” + +The end was indeed nearer than even the Rat suspected. + +After so much open air and excitement the Toad slept very soundly, and +no amount of shaking could rouse him out of bed next morning. So the +Mole and Rat turned to, quietly and manfully, and while the Rat saw to +the horse, and lit a fire, and cleaned last night’s cups and platters, +and got things ready for breakfast, the Mole trudged off to the nearest +village, a long way off, for milk and eggs and various necessaries the +Toad had, of course, forgotten to provide. The hard work had all been +done, and the two animals were resting, thoroughly exhausted, by the +time Toad appeared on the scene, fresh and gay, remarking what a +pleasant easy life it was they were all leading now, after the cares +and worries and fatigues of housekeeping at home. + +They had a pleasant ramble that day over grassy downs and along narrow +by-lanes, and camped as before, on a common, only this time the two +guests took care that Toad should do his fair share of work. In +consequence, when the time came for starting next morning, Toad was by +no means so rapturous about the simplicity of the primitive life, and +indeed attempted to resume his place in his bunk, whence he was hauled +by force. Their way lay, as before, across country by narrow lanes, and +it was not till the afternoon that they came out on the high-road, +their first high-road; and there disaster, fleet and unforeseen, sprang +out on them—disaster momentous indeed to their expedition, but simply +overwhelming in its effect on the after-career of Toad. + +They were strolling along the high-road easily, the Mole by the horse’s +head, talking to him, since the horse had complained that he was being +frightfully left out of it, and nobody considered him in the least; the +Toad and the Water Rat walking behind the cart talking together—at +least Toad was talking, and Rat was saying at intervals, “Yes, +precisely; and what did _you_ say to _him?_”—and thinking all the time +of something very different, when far behind them they heard a faint +warning hum; like the drone of a distant bee. Glancing back, they saw a +small cloud of dust, with a dark centre of energy, advancing on them at +incredible speed, while from out the dust a faint “Poop-poop!” wailed +like an uneasy animal in pain. Hardly regarding it, they turned to +resume their conversation, when in an instant (as it seemed) the +peaceful scene was changed, and with a blast of wind and a whirl of +sound that made them jump for the nearest ditch, It was on them! The +“Poop-poop” rang with a brazen shout in their ears, they had a moment’s +glimpse of an interior of glittering plate-glass and rich morocco, and +the magnificent motor-car, immense, breath-snatching, passionate, with +its pilot tense and hugging his wheel, possessed all earth and air for +the fraction of a second, flung an enveloping cloud of dust that +blinded and enwrapped them utterly, and then dwindled to a speck in the +far distance, changed back into a droning bee once more. + +The old grey horse, dreaming, as he plodded along, of his quiet +paddock, in a new raw situation such as this simply abandoned himself +to his natural emotions. Rearing, plunging, backing steadily, in spite +of all the Mole’s efforts at his head, and all the Mole’s lively +language directed at his better feelings, he drove the cart backwards +towards the deep ditch at the side of the road. It wavered an +instant—then there was a heartrending crash—and the canary-coloured +cart, their pride and their joy, lay on its side in the ditch, an +irredeemable wreck. + +The Rat danced up and down in the road, simply transported with +passion. “You villains!” he shouted, shaking both fists, “You +scoundrels, you highwaymen, you—you—roadhogs!—I’ll have the law of you! +I’ll report you! I’ll take you through all the Courts!” His +home-sickness had quite slipped away from him, and for the moment he +was the skipper of the canary-coloured vessel driven on a shoal by the +reckless jockeying of rival mariners, and he was trying to recollect +all the fine and biting things he used to say to masters of +steam-launches when their wash, as they drove too near the bank, used +to flood his parlour-carpet at home. + +Toad sat straight down in the middle of the dusty road, his legs +stretched out before him, and stared fixedly in the direction of the +disappearing motor-car. He breathed short, his face wore a placid +satisfied expression, and at intervals he faintly murmured “Poop-poop!” + +The Mole was busy trying to quiet the horse, which he succeeded in +doing after a time. Then he went to look at the cart, on its side in +the ditch. It was indeed a sorry sight. Panels and windows smashed, +axles hopelessly bent, one wheel off, sardine-tins scattered over the +wide world, and the bird in the bird-cage sobbing pitifully and calling +to be let out. + +The Rat came to help him, but their united efforts were not sufficient +to right the cart. “Hi! Toad!” they cried. “Come and bear a hand, can’t +you!” + +The Toad never answered a word, or budged from his seat in the road; so +they went to see what was the matter with him. They found him in a sort +of a trance, a happy smile on his face, his eyes still fixed on the +dusty wake of their destroyer. At intervals he was still heard to +murmur “Poop-poop!” + +The Rat shook him by the shoulder. “Are you coming to help us, Toad?” +he demanded sternly. + +“Glorious, stirring sight!” murmured Toad, never offering to move. “The +poetry of motion! The _real_ way to travel! The _only_ way to travel! +Here to-day—in next week to-morrow! Villages skipped, towns and cities +jumped—always somebody else’s horizon! O bliss! O poop-poop! O my! O +my!” + +“O _stop_ being an ass, Toad!” cried the Mole despairingly. + +“And to think I never _knew!_” went on the Toad in a dreamy monotone. +“All those wasted years that lie behind me, I never knew, never even +_dreamt!_ But _now_—but now that I know, now that I fully realise! O +what a flowery track lies spread before me, henceforth! What +dust-clouds shall spring up behind me as I speed on my reckless way! +What carts I shall fling carelessly into the ditch in the wake of my +magnificent onset! Horrid little carts—common carts—canary-coloured +carts!” + +“What are we to do with him?” asked the Mole of the Water Rat. + +“Nothing at all,” replied the Rat firmly. “Because there is really +nothing to be done. You see, I know him from of old. He is now +possessed. He has got a new craze, and it always takes him that way, in +its first stage. He’ll continue like that for days now, like an animal +walking in a happy dream, quite useless for all practical purposes. +Never mind him. Let’s go and see what there is to be done about the +cart.” + +A careful inspection showed them that, even if they succeeded in +righting it by themselves, the cart would travel no longer. The axles +were in a hopeless state, and the missing wheel was shattered into +pieces. + +The Rat knotted the horse’s reins over his back and took him by the +head, carrying the bird cage and its hysterical occupant in the other +hand. “Come on!” he said grimly to the Mole. “It’s five or six miles to +the nearest town, and we shall just have to walk it. The sooner we make +a start the better.” + +“But what about Toad?” asked the Mole anxiously, as they set off +together. “We can’t leave him here, sitting in the middle of the road +by himself, in the distracted state he’s in! It’s not safe. Supposing +another Thing were to come along?” + +“O, _bother_ Toad,” said the Rat savagely; “I’ve done with him!” + +They had not proceeded very far on their way, however, when there was a +pattering of feet behind them, and Toad caught them up and thrust a paw +inside the elbow of each of them; still breathing short and staring +into vacancy. + +“Now, look here, Toad!” said the Rat sharply: “as soon as we get to the +town, you’ll have to go straight to the police-station, and see if they +know anything about that motor-car and who it belongs to, and lodge a +complaint against it. And then you’ll have to go to a blacksmith’s or a +wheelwright’s and arrange for the cart to be fetched and mended and put +to rights. It’ll take time, but it’s not quite a hopeless smash. +Meanwhile, the Mole and I will go to an inn and find comfortable rooms +where we can stay till the cart’s ready, and till your nerves have +recovered their shock.” + +“Police-station! Complaint!” murmured Toad dreamily. “Me _complain_ of +that beautiful, that heavenly vision that has been vouchsafed me! +_Mend_ the _cart!_ I’ve done with carts for ever. I never want to see +the cart, or to hear of it, again. O, Ratty! You can’t think how +obliged I am to you for consenting to come on this trip! I wouldn’t +have gone without you, and then I might never have seen that—that swan, +that sunbeam, that thunderbolt! I might never have heard that +entrancing sound, or smelt that bewitching smell! I owe it all to you, +my best of friends!” + +The Rat turned from him in despair. “You see what it is?” he said to +the Mole, addressing him across Toad’s head: “He’s quite hopeless. I +give it up—when we get to the town we’ll go to the railway station, and +with luck we may pick up a train there that’ll get us back to riverbank +to-night. And if ever you catch me going a-pleasuring with this +provoking animal again!”—He snorted, and during the rest of that weary +trudge addressed his remarks exclusively to Mole. + +On reaching the town they went straight to the station and deposited +Toad in the second-class waiting-room, giving a porter twopence to keep +a strict eye on him. They then left the horse at an inn stable, and +gave what directions they could about the cart and its contents. +Eventually, a slow train having landed them at a station not very far +from Toad Hall, they escorted the spell-bound, sleep-walking Toad to +his door, put him inside it, and instructed his housekeeper to feed +him, undress him, and put him to bed. Then they got out their boat from +the boat-house, sculled down the river home, and at a very late hour +sat down to supper in their own cosy riverside parlour, to the Rat’s +great joy and contentment. + +The following evening the Mole, who had risen late and taken things +very easy all day, was sitting on the bank fishing, when the Rat, who +had been looking up his friends and gossiping, came strolling along to +find him. “Heard the news?” he said. “There’s nothing else being talked +about, all along the river bank. Toad went up to Town by an early train +this morning. And he has ordered a large and very expensive motor-car.” + + + +*** END OF THE PROJECT GUTENBERG EBOOK THE WIND IN THE WILLOWS *** + +***** This file should be named 289-0.txt or 289-0.zip ***** +This and all associated files of various formats will be found in: + https://www.gutenberg.org/2/8/289/ + +Updated editions will replace the previous one--the old editions will +be renamed. + +Creating the works from print editions not protected by U.S. copyright +law means that no one owns a United States copyright in these works, +so the Foundation (and you!) can copy and distribute it in the +United States without permission and without paying copyright +royalties. Special rules, set forth in the General Terms of Use part +of this license, apply to copying and distributing Project +Gutenberg-tm electronic works to protect the PROJECT GUTENBERG-tm +concept and trademark. Project Gutenberg is a registered trademark, +and may not be used if you charge for an eBook, except by following +the terms of the trademark license, including paying royalties for use +of the Project Gutenberg trademark. If you do not charge anything for +copies of this eBook, complying with the trademark license is very +easy. You may use this eBook for nearly any purpose such as creation +of derivative works, reports, performances and research. Project +Gutenberg eBooks may be modified and printed and given away--you may +do practically ANYTHING in the United States with eBooks not protected +by U.S. copyright law. Redistribution is subject to the trademark +license, especially commercial redistribution. + +START: FULL LICENSE + +THE FULL PROJECT GUTENBERG LICENSE +PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK + +To protect the Project Gutenberg-tm mission of promoting the free +distribution of electronic works, by using or distributing this work +(or any other work associated in any way with the phrase "Project +Gutenberg"), you agree to comply with all the terms of the Full +Project Gutenberg-tm License available with this file or online at +www.gutenberg.org/license. + +Section 1. General Terms of Use and Redistributing Project +Gutenberg-tm electronic works + +1.A. By reading or using any part of this Project Gutenberg-tm +electronic work, you indicate that you have read, understand, agree to +and accept all the terms of this license and intellectual property +(trademark/copyright) agreement. If you do not agree to abide by all +the terms of this agreement, you must cease using and return or +destroy all copies of Project Gutenberg-tm electronic works in your +possession. If you paid a fee for obtaining a copy of or access to a +Project Gutenberg-tm electronic work and you do not agree to be bound +by the terms of this agreement, you may obtain a refund from the +person or entity to whom you paid the fee as set forth in paragraph +1.E.8. + +1.B. "Project Gutenberg" is a registered trademark. It may only be +used on or associated in any way with an electronic work by people who +agree to be bound by the terms of this agreement. There are a few +things that you can do with most Project Gutenberg-tm electronic works +even without complying with the full terms of this agreement. See +paragraph 1.C below. There are a lot of things you can do with Project +Gutenberg-tm electronic works if you follow the terms of this +agreement and help preserve free future access to Project Gutenberg-tm +electronic works. See paragraph 1.E below. + +1.C. The Project Gutenberg Literary Archive Foundation ("the +Foundation" or PGLAF), owns a compilation copyright in the collection +of Project Gutenberg-tm electronic works. Nearly all the individual +works in the collection are in the public domain in the United +States. If an individual work is unprotected by copyright law in the +United States and you are located in the United States, we do not +claim a right to prevent you from copying, distributing, performing, +displaying or creating derivative works based on the work as long as +all references to Project Gutenberg are removed. Of course, we hope +that you will support the Project Gutenberg-tm mission of promoting +free access to electronic works by freely sharing Project Gutenberg-tm +works in compliance with the terms of this agreement for keeping the +Project Gutenberg-tm name associated with the work. You can easily +comply with the terms of this agreement by keeping this work in the +same format with its attached full Project Gutenberg-tm License when +you share it without charge with others. + +1.D. The copyright laws of the place where you are located also govern +what you can do with this work. Copyright laws in most countries are +in a constant state of change. If you are outside the United States, +check the laws of your country in addition to the terms of this +agreement before downloading, copying, displaying, performing, +distributing or creating derivative works based on this work or any +other Project Gutenberg-tm work. The Foundation makes no +representations concerning the copyright status of any work in any +country other than the United States. + +1.E. Unless you have removed all references to Project Gutenberg: + +1.E.1. The following sentence, with active links to, or other +immediate access to, the full Project Gutenberg-tm License must appear +prominently whenever any copy of a Project Gutenberg-tm work (any work +on which the phrase "Project Gutenberg" appears, or with which the +phrase "Project Gutenberg" is associated) is accessed, displayed, +performed, viewed, copied or distributed: + + This eBook is for the use of anyone anywhere in the United States and + most other parts of the world at no cost and with almost no + restrictions whatsoever. You may copy it, give it away or re-use it + under the terms of the Project Gutenberg License included with this + eBook or online at www.gutenberg.org. If you are not located in the + United States, you will have to check the laws of the country where + you are located before using this eBook. + +1.E.2. If an individual Project Gutenberg-tm electronic work is +derived from texts not protected by U.S. copyright law (does not +contain a notice indicating that it is posted with permission of the +copyright holder), the work can be copied and distributed to anyone in +the United States without paying any fees or charges. If you are +redistributing or providing access to a work with the phrase "Project +Gutenberg" associated with or appearing on the work, you must comply +either with the requirements of paragraphs 1.E.1 through 1.E.7 or +obtain permission for the use of the work and the Project Gutenberg-tm +trademark as set forth in paragraphs 1.E.8 or 1.E.9. + +1.E.3. If an individual Project Gutenberg-tm electronic work is posted +with the permission of the copyright holder, your use and distribution +must comply with both paragraphs 1.E.1 through 1.E.7 and any +additional terms imposed by the copyright holder. Additional terms +will be linked to the Project Gutenberg-tm License for all works +posted with the permission of the copyright holder found at the +beginning of this work. + +1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm +License terms from this work, or any files containing a part of this +work or any other work associated with Project Gutenberg-tm. + +1.E.5. Do not copy, display, perform, distribute or redistribute this +electronic work, or any part of this electronic work, without +prominently displaying the sentence set forth in paragraph 1.E.1 with +active links or immediate access to the full terms of the Project +Gutenberg-tm License. + +1.E.6. You may convert to and distribute this work in any binary, +compressed, marked up, nonproprietary or proprietary form, including +any word processing or hypertext form. However, if you provide access +to or distribute copies of a Project Gutenberg-tm work in a format +other than "Plain Vanilla ASCII" or other format used in the official +version posted on the official Project Gutenberg-tm website +(www.gutenberg.org), you must, at no additional cost, fee or expense +to the user, provide a copy, a means of exporting a copy, or a means +of obtaining a copy upon request, of the work in its original "Plain +Vanilla ASCII" or other form. Any alternate format must include the +full Project Gutenberg-tm License as specified in paragraph 1.E.1. + +1.E.7. Do not charge a fee for access to, viewing, displaying, +performing, copying or distributing any Project Gutenberg-tm works +unless you comply with paragraph 1.E.8 or 1.E.9. + +1.E.8. You may charge a reasonable fee for copies of or providing +access to or distributing Project Gutenberg-tm electronic works +provided that: + +* You pay a royalty fee of 20% of the gross profits you derive from + the use of Project Gutenberg-tm works calculated using the method + you already use to calculate your applicable taxes. The fee is owed + to the owner of the Project Gutenberg-tm trademark, but he has + agreed to donate royalties under this paragraph to the Project + Gutenberg Literary Archive Foundation. Royalty payments must be paid + within 60 days following each date on which you prepare (or are + legally required to prepare) your periodic tax returns. Royalty + payments should be clearly marked as such and sent to the Project + Gutenberg Literary Archive Foundation at the address specified in + Section 4, "Information about donations to the Project Gutenberg + Literary Archive Foundation." + +* You provide a full refund of any money paid by a user who notifies + you in writing (or by e-mail) within 30 days of receipt that s/he + does not agree to the terms of the full Project Gutenberg-tm + License. You must require such a user to return or destroy all + copies of the works possessed in a physical medium and discontinue + all use of and all access to other copies of Project Gutenberg-tm + works. + +* You provide, in accordance with paragraph 1.F.3, a full refund of + any money paid for a work or a replacement copy, if a defect in the + electronic work is discovered and reported to you within 90 days of + receipt of the work. + +* You comply with all other terms of this agreement for free + distribution of Project Gutenberg-tm works. + +1.E.9. If you wish to charge a fee or distribute a Project +Gutenberg-tm electronic work or group of works on different terms than +are set forth in this agreement, you must obtain permission in writing +from the Project Gutenberg Literary Archive Foundation, the manager of +the Project Gutenberg-tm trademark. Contact the Foundation as set +forth in Section 3 below. + +1.F. + +1.F.1. Project Gutenberg volunteers and employees expend considerable +effort to identify, do copyright research on, transcribe and proofread +works not protected by U.S. copyright law in creating the Project +Gutenberg-tm collection. Despite these efforts, Project Gutenberg-tm +electronic works, and the medium on which they may be stored, may +contain "Defects," such as, but not limited to, incomplete, inaccurate +or corrupt data, transcription errors, a copyright or other +intellectual property infringement, a defective or damaged disk or +other medium, a computer virus, or computer codes that damage or +cannot be read by your equipment. + +1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right +of Replacement or Refund" described in paragraph 1.F.3, the Project +Gutenberg Literary Archive Foundation, the owner of the Project +Gutenberg-tm trademark, and any other party distributing a Project +Gutenberg-tm electronic work under this agreement, disclaim all +liability to you for damages, costs and expenses, including legal +fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT +LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE +PROVIDED IN PARAGRAPH 1.F.3. YOU AGREE THAT THE FOUNDATION, THE +TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE +LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR +INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH +DAMAGE. + +1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a +defect in this electronic work within 90 days of receiving it, you can +receive a refund of the money (if any) you paid for it by sending a +written explanation to the person you received the work from. If you +received the work on a physical medium, you must return the medium +with your written explanation. The person or entity that provided you +with the defective work may elect to provide a replacement copy in +lieu of a refund. If you received the work electronically, the person +or entity providing it to you may choose to give you a second +opportunity to receive the work electronically in lieu of a refund. If +the second copy is also defective, you may demand a refund in writing +without further opportunities to fix the problem. + +1.F.4. Except for the limited right of replacement or refund set forth +in paragraph 1.F.3, this work is provided to you 'AS-IS', WITH NO +OTHER WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PURPOSE. + +1.F.5. Some states do not allow disclaimers of certain implied +warranties or the exclusion or limitation of certain types of +damages. If any disclaimer or limitation set forth in this agreement +violates the law of the state applicable to this agreement, the +agreement shall be interpreted to make the maximum disclaimer or +limitation permitted by the applicable state law. The invalidity or +unenforceability of any provision of this agreement shall not void the +remaining provisions. + +1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the +trademark owner, any agent or employee of the Foundation, anyone +providing copies of Project Gutenberg-tm electronic works in +accordance with this agreement, and any volunteers associated with the +production, promotion and distribution of Project Gutenberg-tm +electronic works, harmless from all liability, costs and expenses, +including legal fees, that arise directly or indirectly from any of +the following which you do or cause to occur: (a) distribution of this +or any Project Gutenberg-tm work, (b) alteration, modification, or +additions or deletions to any Project Gutenberg-tm work, and (c) any +Defect you cause. + +Section 2. Information about the Mission of Project Gutenberg-tm + +Project Gutenberg-tm is synonymous with the free distribution of +electronic works in formats readable by the widest variety of +computers including obsolete, old, middle-aged and new computers. It +exists because of the efforts of hundreds of volunteers and donations +from people in all walks of life. + +Volunteers and financial support to provide volunteers with the +assistance they need are critical to reaching Project Gutenberg-tm's +goals and ensuring that the Project Gutenberg-tm collection will +remain freely available for generations to come. In 2001, the Project +Gutenberg Literary Archive Foundation was created to provide a secure +and permanent future for Project Gutenberg-tm and future +generations. To learn more about the Project Gutenberg Literary +Archive Foundation and how your efforts and donations can help, see +Sections 3 and 4 and the Foundation information page at +www.gutenberg.org + +Section 3. Information about the Project Gutenberg Literary +Archive Foundation + +The Project Gutenberg Literary Archive Foundation is a non-profit +501(c)(3) educational corporation organized under the laws of the +state of Mississippi and granted tax exempt status by the Internal +Revenue Service. The Foundation's EIN or federal tax identification +number is 64-6221541. Contributions to the Project Gutenberg Literary +Archive Foundation are tax deductible to the full extent permitted by +U.S. federal laws and your state's laws. + +The Foundation's business office is located at 809 North 1500 West, +Salt Lake City, UT 84116, (801) 596-1887. Email contact links and up +to date contact information can be found at the Foundation's website +and official page at www.gutenberg.org/contact + +Section 4. Information about Donations to the Project Gutenberg +Literary Archive Foundation + +Project Gutenberg-tm depends upon and cannot survive without +widespread public support and donations to carry out its mission of +increasing the number of public domain and licensed works that can be +freely distributed in machine-readable form accessible by the widest +array of equipment including outdated equipment. Many small donations +($1 to $5,000) are particularly important to maintaining tax exempt +status with the IRS. + +The Foundation is committed to complying with the laws regulating +charities and charitable donations in all 50 states of the United +States. Compliance requirements are not uniform and it takes a +considerable effort, much paperwork and many fees to meet and keep up +with these requirements. We do not solicit donations in locations +where we have not received written confirmation of compliance. To SEND +DONATIONS or determine the status of compliance for any particular +state visit www.gutenberg.org/donate + +While we cannot and do not solicit contributions from states where we +have not met the solicitation requirements, we know of no prohibition +against accepting unsolicited donations from donors in such states who +approach us with offers to donate. + +International donations are gratefully accepted, but we cannot make +any statements concerning tax treatment of donations received from +outside the United States. U.S. laws alone swamp our small staff. + +Please check the Project Gutenberg web pages for current donation +methods and addresses. Donations are accepted in a number of other +ways including checks, online payments and credit card donations. To +donate, please visit: www.gutenberg.org/donate + +Section 5. General Information About Project Gutenberg-tm electronic works + +Professor Michael S. Hart was the originator of the Project +Gutenberg-tm concept of a library of electronic works that could be +freely shared with anyone. For forty years, he produced and +distributed Project Gutenberg-tm eBooks with only a loose network of +volunteer support. + +Project Gutenberg-tm eBooks are often created from several printed +editions, all of which are confirmed as not protected by copyright in +the U.S. unless a copyright notice is included. Thus, we do not +necessarily keep eBooks in compliance with any particular paper +edition. + +Most people start at our website which has the main PG search +facility: www.gutenberg.org + +This website includes information about Project Gutenberg-tm, +including how to make donations to the Project Gutenberg Literary +Archive Foundation, how to help produce our new eBooks, and how to +subscribe to our email newsletter to hear about new eBooks. + + diff --git a/badger2040/os/badger2040/assets/badge_image.png b/badger2040/os/badger2040/assets/badge_image.png new file mode 100644 index 0000000..b5147dc Binary files /dev/null and b/badger2040/os/badger2040/assets/badge_image.png differ diff --git a/badger2040/os/badger2040/assets/badgerpunk.png b/badger2040/os/badger2040/assets/badgerpunk.png new file mode 100644 index 0000000..f7568a2 Binary files /dev/null and b/badger2040/os/badger2040/assets/badgerpunk.png differ diff --git a/badger2040/os/badger2040/assets/boot.py b/badger2040/os/badger2040/assets/boot.py new file mode 100644 index 0000000..76b5ef6 --- /dev/null +++ b/badger2040/os/badger2040/assets/boot.py @@ -0,0 +1,6 @@ +try: + open("main.py", "r") +except OSError: + with open("main.py", "w") as f: + f.write("import _launcher") + f.flush() diff --git a/badger2040/os/badger2040/assets/launchericons.png b/badger2040/os/badger2040/assets/launchericons.png new file mode 100644 index 0000000..da93e0a Binary files /dev/null and b/badger2040/os/badger2040/assets/launchericons.png differ diff --git a/badger2040/os/badger2040/badge.py b/badger2040/os/badger2040/badge.py new file mode 100644 index 0000000..fdeae15 --- /dev/null +++ b/badger2040/os/badger2040/badge.py @@ -0,0 +1,267 @@ +import badger2040 +import machine +import time + +# Global Constants +WIDTH = badger2040.WIDTH +HEIGHT = badger2040.HEIGHT + +IMAGE_WIDTH = 104 + +COMPANY_HEIGHT = 30 +DETAILS_HEIGHT = 20 +NAME_HEIGHT = HEIGHT - COMPANY_HEIGHT - (DETAILS_HEIGHT * 2) - 2 +TEXT_WIDTH = WIDTH - IMAGE_WIDTH - 1 + +COMPANY_TEXT_SIZE = 0.6 +DETAILS_TEXT_SIZE = 0.5 + +LEFT_PADDING = 5 +NAME_PADDING = 20 +DETAIL_SPACING = 10 + +OVERLAY_BORDER = 40 +OVERLAY_SPACING = 20 +OVERLAY_TEXT_SIZE = 0.6 + +DEFAULT_TEXT = """mustelid inc +H. Badger +RP2040 +2MB Flash +E ink +296x128px""" + +BADGE_IMAGE = bytearray(int(IMAGE_WIDTH * HEIGHT / 8)) + +try: + open("badge-image.bin", "rb").readinto(BADGE_IMAGE) +except OSError: + try: + import badge_image + BADGE_IMAGE = bytearray(badge_image.data()) + del badge_image + except ImportError: + pass + + +# ------------------------------ +# Utility functions +# ------------------------------ + +# Reduce the size of a string until it fits within a given width +def truncatestring(text, text_size, width): + while True: + length = display.measure_text(text, text_size) + if length > 0 and length > width: + text = text[:-1] + else: + text += "" + return text + + +# ------------------------------ +# Drawing functions +# ------------------------------ + +# Draw an overlay box with a given message within it +def draw_overlay(message, width, height, line_spacing, text_size): + + # Draw a light grey background + display.pen(12) + display.rectangle((WIDTH - width) // 2, (HEIGHT - height) // 2, width, height) + + # Take the provided message and split it up into + # lines that fit within the specified width + words = message.split(" ") + lines = [] + line = "" + appended_line = "" + for word in words: + if len(word) > 0: + appended_line += " " + appended_line += word + if display.measure_text(appended_line, text_size) >= width: + lines.append(line) + appended_line = word + else: + line = appended_line + if len(line) != 0: + lines.append(line) + + display.pen(0) + display.thickness(2) + + # Display each line of text from the message, centre-aligned + num_lines = len(lines) + for i in range(num_lines): + length = display.measure_text(lines[i], text_size) + current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2 + display.text(lines[i], (WIDTH - length) // 2, (HEIGHT // 2) + current_line, text_size) + + +# Draw the badge, including user text +def draw_badge(): + display.pen(0) + display.clear() + + # Draw badge image + display.image(BADGE_IMAGE, IMAGE_WIDTH, HEIGHT, WIDTH - IMAGE_WIDTH, 0) + + # Draw a border around the image + display.pen(0) + display.thickness(1) + display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - 1, 0) + display.line(WIDTH - IMAGE_WIDTH, 0, WIDTH - IMAGE_WIDTH, HEIGHT - 1) + display.line(WIDTH - IMAGE_WIDTH, HEIGHT - 1, WIDTH - 1, HEIGHT - 1) + display.line(WIDTH - 1, 0, WIDTH - 1, HEIGHT - 1) + + # Uncomment this if a white background is wanted behind the company + # display.pen(15) + # display.rectangle(1, 1, TEXT_WIDTH, COMPANY_HEIGHT - 1) + + # Draw the company + display.pen(15) # Change this to 0 if a white background is used + display.font("serif") + display.thickness(3) + display.text(company, LEFT_PADDING, (COMPANY_HEIGHT // 2) + 1, COMPANY_TEXT_SIZE) + + # Draw a white background behind the name + display.pen(15) + display.thickness(1) + display.rectangle(1, COMPANY_HEIGHT + 1, TEXT_WIDTH, NAME_HEIGHT) + + # Draw the name, scaling it based on the available width + display.pen(0) + display.font("sans") + display.thickness(4) + name_size = 2.0 # A sensible starting scale + while True: + name_length = display.measure_text(name, name_size) + if name_length >= (TEXT_WIDTH - NAME_PADDING) and name_size >= 0.1: + name_size -= 0.01 + else: + display.text(name, (TEXT_WIDTH - name_length) // 2, (NAME_HEIGHT // 2) + COMPANY_HEIGHT + 1, name_size) + break + + # Draw a white backgrounds behind the details + display.pen(15) + display.thickness(1) + display.rectangle(1, HEIGHT - DETAILS_HEIGHT * 2, TEXT_WIDTH, DETAILS_HEIGHT - 1) + display.rectangle(1, HEIGHT - DETAILS_HEIGHT, TEXT_WIDTH, DETAILS_HEIGHT - 1) + + # Draw the first detail's title and text + display.pen(0) + display.font("sans") + display.thickness(3) + name_length = display.measure_text(detail1_title, DETAILS_TEXT_SIZE) + display.text(detail1_title, LEFT_PADDING, HEIGHT - ((DETAILS_HEIGHT * 3) // 2), DETAILS_TEXT_SIZE) + display.thickness(2) + display.text(detail1_text, 5 + name_length + DETAIL_SPACING, HEIGHT - ((DETAILS_HEIGHT * 3) // 2), DETAILS_TEXT_SIZE) + + # Draw the second detail's title and text + display.thickness(3) + name_length = display.measure_text(detail2_title, DETAILS_TEXT_SIZE) + display.text(detail2_title, LEFT_PADDING, HEIGHT - (DETAILS_HEIGHT // 2), DETAILS_TEXT_SIZE) + display.thickness(2) + display.text(detail2_text, LEFT_PADDING + name_length + DETAIL_SPACING, HEIGHT - (DETAILS_HEIGHT // 2), DETAILS_TEXT_SIZE) + + +# ------------------------------ +# Program setup +# ------------------------------ + +# Global variables +show_overlay = False + +# Create a new Badger and set it to update NORMAL +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_NORMAL) + +# Open the badge file +try: + badge = open("badge.txt", "r") +except OSError: + badge = open("badge.txt", "w") + badge.write(DEFAULT_TEXT) + badge.flush() + badge.seek(0) + +# Read in the next 6 lines +company = badge.readline() # "mustelid inc" +name = badge.readline() # "H. Badger" +detail1_title = badge.readline() # "RP2040" +detail1_text = badge.readline() # "2MB Flash" +detail2_title = badge.readline() # "E ink" +detail2_text = badge.readline() # "296x128px" + +# Truncate all of the text (except for the name as that is scaled) +company = truncatestring(company, COMPANY_TEXT_SIZE, TEXT_WIDTH) + +detail1_title = truncatestring(detail1_title, DETAILS_TEXT_SIZE, TEXT_WIDTH) +detail1_text = truncatestring(detail1_text, DETAILS_TEXT_SIZE, + TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail1_title, DETAILS_TEXT_SIZE)) + +detail2_title = truncatestring(detail2_title, DETAILS_TEXT_SIZE, TEXT_WIDTH) +detail2_text = truncatestring(detail2_text, DETAILS_TEXT_SIZE, + TEXT_WIDTH - DETAIL_SPACING - display.measure_text(detail2_title, DETAILS_TEXT_SIZE)) + +# Set up the buttons +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) + + +# Button handling function +def button(pin): + global show_overlay + + if pin == button_a: + show_overlay = True + return + + if pin == button_b: + show_overlay = True + return + + if pin == button_c: + show_overlay = True + return + + if pin == button_up: + show_overlay = True + return + + if pin == button_down: + show_overlay = True + return + + +# Register the button handling function with the buttons +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + + +# ------------------------------ +# Main program loop +# ------------------------------ + +draw_badge() +display.update() + +while True: + if show_overlay: + draw_overlay("To change the text, connect Badger2040 to a PC, load up Thonny, and modify badge.txt", + WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, OVERLAY_TEXT_SIZE) + display.update() + time.sleep(4) + + draw_badge() + display.update() + show_overlay = False + + time.sleep(0.1) diff --git a/badger2040/os/badger2040/battery.py b/badger2040/os/badger2040/battery.py new file mode 100644 index 0000000..eaa97bb --- /dev/null +++ b/badger2040/os/badger2040/battery.py @@ -0,0 +1,163 @@ +import badger2040 +from machine import Pin, ADC +import time + +# Global Constants +# for e.g. 2xAAA batteries, try max 3.4 min 3.0 +MAX_BATTERY_VOLTAGE = 4.0 +MIN_BATTERY_VOLTAGE = 3.2 + +WIDTH = badger2040.WIDTH +HEIGHT = badger2040.HEIGHT + +BATT_WIDTH = 200 +BATT_HEIGHT = 100 +BATT_BORDER = 10 +BATT_TERM_WIDTH = 20 +BATT_TERM_HEIGHT = 50 +BATT_BAR_PADDING = 10 +BATT_BAR_HEIGHT = BATT_HEIGHT - (BATT_BORDER * 2) - (BATT_BAR_PADDING * 2) +BATT_BAR_START = ((WIDTH - BATT_WIDTH) // 2) + BATT_BORDER + BATT_BAR_PADDING +BATT_BAR_END = ((WIDTH + BATT_WIDTH) // 2) - BATT_BORDER - BATT_BAR_PADDING + +NUM_BATT_BARS = 4 + + +# ------------------------------ +# Utility functions +# ------------------------------ + + +def map_value(input, in_min, in_max, out_min, out_max): + return (((input - in_min) * (out_max - out_min)) / (in_max - in_min)) + out_min + + +# ------------------------------ +# Drawing functions +# ------------------------------ + +# Draw the frame of the reader +def draw_battery(level, resolution): + display.pen(15) + display.clear() + + display.thickness(1) + + # Draw the battery outline + display.pen(0) + display.rectangle( + (WIDTH - BATT_WIDTH) // 2, (HEIGHT - BATT_HEIGHT) // 2, BATT_WIDTH, BATT_HEIGHT + ) + + display.rectangle( + (WIDTH + BATT_WIDTH) // 2, + (HEIGHT - BATT_TERM_HEIGHT) // 2, + BATT_TERM_WIDTH, + BATT_TERM_HEIGHT, + ) + + display.pen(15) + display.rectangle( + (WIDTH - BATT_WIDTH) // 2 + BATT_BORDER, + (HEIGHT - BATT_HEIGHT) // 2 + BATT_BORDER, + BATT_WIDTH - BATT_BORDER * 2, + BATT_HEIGHT - BATT_BORDER * 2, + ) + + # Add a special check for no battery + if level < 1: + X = WIDTH // 2 + Y = HEIGHT // 2 + + display.pen(0) + display.thickness(1) + thickness = (BATT_BORDER * 3) // 2 + start_extra = thickness // 3 + end_extra = (thickness * 2) // 3 + for i in range(0, thickness): + excess = i // 2 + display.line( + X - (BATT_HEIGHT // 2) + i - excess - start_extra, + Y - (BATT_HEIGHT // 2) - excess - start_extra, + X + (BATT_HEIGHT // 2) + i - excess + end_extra, + Y + (BATT_HEIGHT // 2) - excess + end_extra, + ) + display.pen(15) + for i in range(0 - thickness, 0): + display.line( + X - (BATT_HEIGHT // 2) + i, + Y - (BATT_HEIGHT // 2), + X + (BATT_HEIGHT // 2) + i, + Y + (BATT_HEIGHT // 2), + ) + else: + # Draw the battery bars + display.pen(0) + length = ( + BATT_BAR_END - BATT_BAR_START - ((NUM_BATT_BARS - 1) * BATT_BAR_PADDING) + ) // NUM_BATT_BARS + current_level = 0.0 + normalised_level = level / resolution + for i in range(NUM_BATT_BARS): + current_level = (1.0 * i) / NUM_BATT_BARS + if normalised_level > current_level: + pos = i * (length + BATT_BAR_PADDING) + display.rectangle( + BATT_BAR_START + pos, + (HEIGHT - BATT_BAR_HEIGHT) // 2, + length, + BATT_BAR_HEIGHT, + ) + + display.update() + + +# ------------------------------ +# Program setup +# ------------------------------ + +# Create a new Badger and set it to update FAST +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_FAST) + +# Set up the ADCs for measuring battery voltage +vbat_adc = ADC(badger2040.PIN_BATTERY) +vref_adc = ADC(badger2040.PIN_1V2_REF) +vref_en = Pin(badger2040.PIN_VREF_POWER) +vref_en.init(Pin.OUT) +vref_en.value(0) + + +# ------------------------------ +# Main program loop +# ------------------------------ + +last_level = -1 + +while True: + # Enable the onboard voltage reference + vref_en.value(1) + + # Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries + vdd = 1.24 * (65535 / vref_adc.read_u16()) + vbat = ( + (vbat_adc.read_u16() / 65535) * 3 * vdd + ) # 3 in this is a gain, not rounding of 3.3V + + # Disable the onboard voltage reference + vref_en.value(0) + + # Print out the voltage + print("Battery Voltage = ", vbat, "V", sep="") + + # Convert the voltage to a level to display onscreen + level = int( + map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, NUM_BATT_BARS) + ) + + # Only draw if the battery level has changed significantly + if level != last_level: + draw_battery(level, NUM_BATT_BARS) + last_level = level + + time.sleep(1) diff --git a/badger2040/os/badger2040/button_test.py b/badger2040/os/badger2040/button_test.py new file mode 100644 index 0000000..2395402 --- /dev/null +++ b/badger2040/os/badger2040/button_test.py @@ -0,0 +1,67 @@ +import badger2040 +import machine +import time + +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_TURBO) +display.pen(15) +display.clear() +display.update() + +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) +# the User button (boot/usr on back of board) is inverted from the others +button_user = machine.Pin(badger2040.BUTTON_USER, machine.Pin.IN, machine.Pin.PULL_UP) + + +message = None +message_y = 60 + + +def button(pin): + global message + if message is not None: + return + if pin == button_a: + message = "Button a" + return + if pin == button_b: + message = "Button b" + return + if pin == button_c: + message = "Button c" + return + if pin == button_up: + message = "Button Up" + return + if pin == button_down: + message = "Button Down" + return + if pin == button_user: + message = "Button Usr" + return + + +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + +button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_user.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + + +while True: + if message is not None: + display.pen(15) + display.clear() + display.pen(0) + display.thickness(4) + display.text(message, 6, message_y, 1.4) + for _ in range(2): + display.update() + message = None + time.sleep(0.1) diff --git a/badger2040/os/badger2040/clock.py b/badger2040/os/badger2040/clock.py new file mode 100644 index 0000000..834ab5e --- /dev/null +++ b/badger2040/os/badger2040/clock.py @@ -0,0 +1,148 @@ +import time +import machine +import badger2040 + + +rtc = machine.RTC() +screen = badger2040.Badger2040() +screen.update_speed(badger2040.UPDATE_TURBO) +screen.font("gothic") + +cursors = ["year", "month", "day", "hour", "minute"] +set_clock = False +cursor = 0 +last = 0 + +# Set up the buttons +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) + +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) + + +def days_in_month(month, year): + if month == 2 and ((year % 4 == 0 and year % 100 != 0) or year % 400 == 0): + return 29 + return (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month - 1] + + +# Button handling function +def button(pin): + global last, set_clock, cursor, year, month, day, hour, minute + + time.sleep(0.05) + if not pin.value(): + return + + adjust = 0 + changed = False + + if pin == button_b: + set_clock = not set_clock + changed = True + if not set_clock: + rtc.datetime((year, month, day, 0, hour, minute, second, 0)) + + if set_clock: + if pin == button_c: + cursor += 1 + cursor %= len(cursors) + + if pin == button_a: + cursor -= 1 + cursor %= len(cursors) + + if pin == button_up: + adjust = 1 + + if pin == button_down: + adjust = -1 + + if cursors[cursor] == "year": + year += adjust + year = max(year, 2022) + day = min(day, days_in_month(month, year)) + if cursors[cursor] == "month": + month += adjust + month = min(max(month, 1), 12) + day = min(day, days_in_month(month, year)) + if cursors[cursor] == "day": + day += adjust + day = min(max(day, 1), days_in_month(month, year)) + if cursors[cursor] == "hour": + hour += adjust + hour %= 24 + if cursors[cursor] == "minute": + minute += adjust + minute %= 60 + + if set_clock or changed: + draw_clock() + + +# Register the button handling function with the buttons +button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + + +def draw_clock(): + hms = "{:02}:{:02}:{:02}".format(hour, minute, second) + ymd = "{:04}/{:02}/{:02}".format(year, month, day) + + hms_width = screen.measure_text(hms, 1.8) + hms_offset = int((badger2040.WIDTH / 2) - (hms_width / 2)) + h_width = screen.measure_text(hms[0:2], 1.8) + mi_width = screen.measure_text(hms[3:5], 1.8) + mi_offset = screen.measure_text(hms[0:3], 1.8) + + ymd_width = screen.measure_text(ymd, 1.0) + ymd_offset = int((badger2040.WIDTH / 2) - (ymd_width / 2)) + y_width = screen.measure_text(ymd[0:4], 1.0) + m_width = screen.measure_text(ymd[5:7], 1.0) + m_offset = screen.measure_text(ymd[0:5], 1.0) + d_width = screen.measure_text(ymd[8:10], 1.0) + d_offset = screen.measure_text(ymd[0:8], 1.0) + + screen.pen(15) + screen.clear() + screen.pen(0) + screen.thickness(5) + screen.text(hms, hms_offset, 40, 1.8) + screen.thickness(3) + screen.text(ymd, ymd_offset, 100, 1.0) + + if set_clock: + if cursors[cursor] == "year": + screen.line(ymd_offset, 120, ymd_offset + y_width, 120) + if cursors[cursor] == "month": + screen.line(ymd_offset + m_offset, 120, ymd_offset + m_offset + m_width, 120) + if cursors[cursor] == "day": + screen.line(ymd_offset + d_offset, 120, ymd_offset + d_offset + d_width, 120) + + if cursors[cursor] == "hour": + screen.line(hms_offset, 70, hms_offset + h_width, 70) + if cursors[cursor] == "minute": + screen.line(hms_offset + mi_offset, 70, hms_offset + mi_offset + mi_width, 70) + + screen.update() + + +year, month, day, wd, hour, minute, second, _ = rtc.datetime() + +if (year, month, day) == (2021, 1, 1): + rtc.datetime((2022, 2, 28, 0, 12, 0, 0, 0)) + +last_second = second + +while True: + if not set_clock: + year, month, day, wd, hour, minute, second, _ = rtc.datetime() + if second != last_second: + draw_clock() + last_second = second + time.sleep(0.1) diff --git a/badger2040/os/badger2040/conway.py b/badger2040/os/badger2040/conway.py new file mode 100644 index 0000000..7bdb23d --- /dev/null +++ b/badger2040/os/badger2040/conway.py @@ -0,0 +1,211 @@ +import math +import time +from random import random + +import machine + +import badger2040 + +# ------------------------------ +# Program setup +# ------------------------------ + +# Global constants +CELL_SIZE = 6 # Size of cell in pixels +INITIAL_DENSITY = 0.3 # Density of cells at start + +# Create a new Badger and set it to update TURBO +screen = badger2040.Badger2040() +screen.update_speed(badger2040.UPDATE_TURBO) + +restart = False # should sim be restarted + +# ------------------------------ +# Button functions +# ------------------------------ + + +# Button handling function +def button(pin): + global restart + # if 'a' button is pressed, restart the sim + if pin == button_a: + restart = True + + +# Set up button +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + +# ------------------------------ +# Screen functions +# ------------------------------ + + +# Remove everything from the screen +def init_screen(): + screen.update_speed(badger2040.UPDATE_NORMAL) + screen.pen(15) + screen.clear() + screen.update() + screen.update_speed(badger2040.UPDATE_TURBO) + + +# ------------------------------ +# Classes +# ------------------------------ + +# Define a 'cell' +class Cell: + def __init__(self): + self._alive = False + + def make_alive(self): + self._alive = True + + def make_dead(self): + self._alive = False + + def is_alive(self): + return self._alive + + +# Define the whole board +class Board: + def __init__(self): + self._rows = math.floor(badger2040.WIDTH / CELL_SIZE) + self._columns = math.floor(badger2040.HEIGHT / CELL_SIZE) + self._grid = [[Cell() for _ in range(self._columns)] for _ in range(self._rows)] + + self._initialise_board() + + # Draw the board to the screen + def draw_board(self): + row_idx = 0 + column_idx = 0 + + for row in self._grid: + column_idx = 0 + for cell in row: + if cell.is_alive(): + screen.pen(0) + else: + screen.pen(15) + screen.rectangle( + row_idx * CELL_SIZE, column_idx * CELL_SIZE, CELL_SIZE, CELL_SIZE + ) + column_idx += 1 + row_idx += 1 + + screen.update() + + # Generate the first iteration of the board + def _initialise_board(self): + for row in self._grid: + for cell in row: + if random() <= INITIAL_DENSITY: + cell.make_alive() + + # Get the neighbour cells for a given cell + def get_neighbours(self, current_row, current_column): + # Cells either side of current cell + neighbour_min = -1 + neighbour_max = 2 + neighbours = [] + + for row in range(neighbour_min, neighbour_max): + for column in range(neighbour_min, neighbour_max): + neighbour_row = current_row + row + neighbour_column = current_column + column + # Don't count the current cell + if not ( + neighbour_row == current_row and neighbour_column == current_column + ): + # It's a toroidal world so go all the way round if necessary + if (neighbour_row) < 0: + neighbour_row = self._rows - 1 + elif (neighbour_row) >= self._rows: + neighbour_row = 0 + + if (neighbour_column) < 0: + neighbour_column = self._columns - 1 + elif (neighbour_column) >= self._columns: + neighbour_column = 0 + + neighbours.append(self._grid[neighbour_row][neighbour_column]) + return neighbours + + # Calculate the next generation + def create_next_generation(self): + to_alive = [] + to_dead = [] + changed = False + + for row in range(len(self._grid)): + for column in range(len(self._grid[row])): + # Get all the neighours that are alive + alive_neighbours = [] + for neighbour_cell in self.get_neighbours(row, column): + if neighbour_cell.is_alive(): + alive_neighbours.append(neighbour_cell) + + current_cell = self._grid[row][column] + # Apply the Conway GoL rules (B3/S23) + if current_cell.is_alive(): + if len(alive_neighbours) < 2 or len(alive_neighbours) > 3: + to_dead.append(current_cell) + if len(alive_neighbours) == 3 or len(alive_neighbours) == 2: + to_alive.append(current_cell) + else: + if len(alive_neighbours) == 3: + to_alive.append(current_cell) + + for cell in to_alive: + if not cell.is_alive(): + # The board has changed since the previous generation + changed = True + cell.make_alive() + + for cell in to_dead: + if cell.is_alive(): + # The board has changed since the previous generation + changed = True + cell.make_dead() + + return changed + + +# ------------------------------ +# Main program loop +# ------------------------------ + + +def main(): + global restart + + init_screen() + board = Board() + board.draw_board() + time.sleep(0.5) + + while True: + # The 'a' button has been pressed so restart sim + if restart: + init_screen() + restart = False + board = Board() + board.draw_board() + time.sleep(0.5) + # The board didn't update since the previous generation + if not board.create_next_generation(): + screen.update_speed(badger2040.UPDATE_NORMAL) + board.draw_board() + screen.update_speed(badger2040.UPDATE_TURBO) + time.sleep(5) + restart = True + # Draw the next generation + else: + board.draw_board() + + +main() diff --git a/badger2040/os/badger2040/ebook.py b/badger2040/os/badger2040/ebook.py new file mode 100644 index 0000000..c2bb289 --- /dev/null +++ b/badger2040/os/badger2040/ebook.py @@ -0,0 +1,276 @@ +import badger2040 +import machine +import time +import gc + + +# **** Put the name of your text file here ***** +text_file = "book.txt" # File must be on the MicroPython device + + +try: + open(text_file, "r") +except OSError: + try: + # If the specified file doesn't exist, + # pre-populate with Wind In The Willows + import witw + open(text_file, "wb").write(witw.data()) + del witw + except ImportError: + pass + + +gc.collect() + +# Global Constants +WIDTH = badger2040.WIDTH +HEIGHT = badger2040.HEIGHT + +ARROW_THICKNESS = 3 +ARROW_WIDTH = 18 +ARROW_HEIGHT = 14 +ARROW_PADDING = 2 + +TEXT_PADDING = 4 + +TEXT_SIZE = 0.5 +TEXT_SPACING = int(34 * TEXT_SIZE) +TEXT_WIDTH = WIDTH - TEXT_PADDING - TEXT_PADDING - ARROW_WIDTH + +FONTS = ["sans", "gothic", "cursive", "serif"] +FONT_THICKNESSES = [2, 1, 1, 2] +# ------------------------------ +# Drawing functions +# ------------------------------ + + +# Draw a upward arrow +def draw_up(x, y, width, height, thickness, padding): + border = (thickness // 4) + padding + display.line(x + border, y + height - border, + x + (width // 2), y + border) + display.line(x + (width // 2), y + border, + x + width - border, y + height - border) + + +# Draw a downward arrow +def draw_down(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + border, y + border, + x + (width // 2), y + height - border) + display.line(x + (width // 2), y + height - border, + x + width - border, y + border) + + +# Draw the frame of the reader +def draw_frame(): + display.pen(15) + display.clear() + display.pen(12) + display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT) + display.pen(0) + display.thickness(ARROW_THICKNESS) + if current_page > 1: + draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2), + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2), + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + + +# ------------------------------ +# Program setup +# ------------------------------ + +# Global variables +next_page = True +prev_page = False +change_font_size = False +change_font = False +last_offset = 0 +current_page = 0 + +# Create a new Badger and set it to update FAST +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_FAST) + +# Set up the buttons +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) + +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) + +# Set up the activity LED +led = machine.Pin(badger2040.PIN_LED, machine.Pin.OUT) + +offsets = [] + + +# Button handling function +def button(pin): + global next_page, prev_page, change_font_size, change_font + + if pin == button_down: + next_page = True + + if pin == button_up: + prev_page = True + + if pin == button_a: + change_font_size = True + + if pin == button_b: + change_font = True + + +# Register the button handling function with the buttons +button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + + +# ------------------------------ +# Render page +# ------------------------------ + +def render_page(): + row = 0 + line = "" + pos = ebook.tell() + next_pos = pos + add_newline = False + display.font(FONTS[0]) + + while True: + # Read a full line and split it into words + words = ebook.readline().split(" ") + + # Take the length of the first word and advance our position + next_word = words[0] + if len(words) > 1: + next_pos += len(next_word) + 1 + else: + next_pos += len(next_word) # This is the last word on the line + + # Advance our position further if the word contains special characters + if '\u201c' in next_word: + next_word = next_word.replace('\u201c', '\"') + next_pos += 2 + if '\u201d' in next_word: + next_word = next_word.replace('\u201d', '\"') + next_pos += 2 + if '\u2019' in next_word: + next_word = next_word.replace('\u2019', '\'') + next_pos += 2 + + # Rewind the file back from the line end to the start of the next word + ebook.seek(next_pos) + + # Strip out any new line characters from the word + next_word = next_word.strip() + + # If an empty word is encountered assume that means there was a blank line + if len(next_word) == 0: + add_newline = True + + # Append the word to the current line and measure its length + appended_line = line + if len(line) > 0 and len(next_word) > 0: + appended_line += " " + appended_line += next_word + appended_length = display.measure_text(appended_line, TEXT_SIZE) + + # Would this appended line be longer than the text display area, or was a blank line spotted? + if appended_length >= TEXT_WIDTH or add_newline: + + # Yes, so write out the line prior to the append + print(line) + display.pen(0) + display.thickness(FONT_THICKNESSES[0]) + display.text(line, TEXT_PADDING, (row * TEXT_SPACING) + (TEXT_SPACING // 2) + TEXT_PADDING, TEXT_SIZE) + + # Clear the line and move on to the next row + line = "" + row += 1 + + # Have we reached the end of the page? + if (row * TEXT_SPACING) + TEXT_SPACING >= HEIGHT: + print("+++++") + display.update() + + # Reset the position to the start of the word that made this line too long + ebook.seek(pos) + return + else: + # Set the line to the word and advance the current position + line = next_word + pos = next_pos + + # A new line was spotted, so advance a row + if add_newline: + print("") + row += 1 + if (row * TEXT_SPACING) + TEXT_SPACING >= HEIGHT: + print("+++++") + display.update() + return + add_newline = False + else: + # The appended line was not too long, so set it as the line and advance the current position + line = appended_line + pos = next_pos + + +# ------------------------------ +# Main program loop +# ------------------------------ + +# Open the book file +ebook = open(text_file, "r") + +while True: + # Was the next page button pressed? + if next_page: + current_page += 1 + + # Is the next page one we've not displayed before? + if current_page > len(offsets): + offsets.append(ebook.tell()) # Add its start position to the offsets list + draw_frame() + render_page() + next_page = False # Clear the next page button flag + + # Was the previous page button pressed? + if prev_page: + if current_page > 1: + current_page -= 1 + ebook.seek(offsets[current_page - 1]) # Retrieve the start position of the last page + draw_frame() + render_page() + prev_page = False # Clear the prev page button flag + + if change_font_size: + TEXT_SIZE += 0.1 + if TEXT_SIZE > 0.8: + TEXT_SIZE = 0.5 + TEXT_SPACING = int(34 * TEXT_SIZE) + offsets = [0] + ebook.seek(0) + current_page = 1 + draw_frame() + render_page() + change_font_size = False + + if change_font: + FONTS.append(FONTS.pop(0)) + FONT_THICKNESSES.append(FONT_THICKNESSES.pop(0)) + offsets = [0] + ebook.seek(0) + current_page = 1 + draw_frame() + render_page() + change_font = False + + time.sleep(0.1) diff --git a/badger2040/os/badger2040/fonts.py b/badger2040/os/badger2040/fonts.py new file mode 100644 index 0000000..499bda4 --- /dev/null +++ b/badger2040/os/badger2040/fonts.py @@ -0,0 +1,141 @@ +import badger2040 +import machine +import time + +# Global Constants +FONT_NAMES = ("sans", "gothic", "cursive", "serif", "serif_italic") + +WIDTH = badger2040.WIDTH +HEIGHT = badger2040.HEIGHT + +MENU_TEXT_SIZE = 0.5 +MENU_SPACING = 20 +MENU_WIDTH = 84 +MENU_PADDING = 2 + +TEXT_SIZE = 0.8 +TEXT_INDENT = MENU_WIDTH + 10 + +ARROW_THICKNESS = 3 +ARROW_WIDTH = 18 +ARROW_HEIGHT = 14 +ARROW_PADDING = 2 + + +# ------------------------------ +# Drawing functions +# ------------------------------ + +# Draw a upward arrow +def draw_up(x, y, width, height, thickness, padding): + border = (thickness // 4) + padding + display.line(x + border, y + height - border, + x + (width // 2), y + border) + display.line(x + (width // 2), y + border, + x + width - border, y + height - border) + + +# Draw a downward arrow +def draw_down(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + border, y + border, + x + (width // 2), y + height - border) + display.line(x + (width // 2), y + height - border, + x + width - border, y + border) + + +# Draw the frame of the reader +def draw_frame(): + display.pen(15) + display.clear() + display.pen(12) + display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT) + display.pen(0) + display.thickness(ARROW_THICKNESS) + draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2), + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2), + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + + +# Draw the fonts and menu +def draw_fonts(): + display.font("sans") + display.thickness(1) + for i in range(len(FONT_NAMES)): + name = FONT_NAMES[i] + display.pen(0) + if i == selected_font: + display.rectangle(0, i * MENU_SPACING, MENU_WIDTH, MENU_SPACING) + display.pen(15) + + display.text(name, MENU_PADDING, (i * MENU_SPACING) + (MENU_SPACING // 2), MENU_TEXT_SIZE) + + display.font(FONT_NAMES[selected_font]) + display.thickness(2) + + display.pen(0) + display.text("The quick", TEXT_INDENT, 10, TEXT_SIZE) + display.text("brown fox", TEXT_INDENT, 32, TEXT_SIZE) + display.text("jumped over", TEXT_INDENT, 54, TEXT_SIZE) + display.text("the lazy dog.", TEXT_INDENT, 76, TEXT_SIZE) + display.text("0123456789", TEXT_INDENT, 98, TEXT_SIZE) + display.text("!\"£$%^&*()", TEXT_INDENT, 120, TEXT_SIZE) + display.thickness(1) + + display.update() + + +# ------------------------------ +# Program setup +# ------------------------------ + +# Global variables +selected_font = 0 +pressed = False + +# Create a new Badger and set it to update FAST +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_FAST) + +# Set up the buttons +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) + + +# Button handling function +def button(pin): + global pressed + global selected_font + + if not pressed: + if pin == button_up: + selected_font -= 1 + if selected_font < 0: + selected_font = len(FONT_NAMES) - 1 + pressed = True + return + if pin == button_down: + selected_font += 1 + if selected_font >= len(FONT_NAMES): + selected_font = 0 + pressed = True + return + + +# Register the button handling function with the buttons +button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + + +# ------------------------------ +# Main program loop +# ------------------------------ + +while True: + draw_frame() + draw_fonts() + + pressed = False + while not pressed: + time.sleep(0.1) diff --git a/badger2040/os/badger2040/help.py b/badger2040/os/badger2040/help.py new file mode 100644 index 0000000..ff6102b --- /dev/null +++ b/badger2040/os/badger2040/help.py @@ -0,0 +1,40 @@ +import badger2040 +import time +from badger2040 import WIDTH + +TEXT_SIZE = 0.45 +LINE_HEIGHT = 16 + +display = badger2040.Badger2040() +display.pen(0) +display.rectangle(0, 0, WIDTH, 16) +display.thickness(1) +display.pen(15) +display.text("badgerOS", 3, 8, 0.4) + +display.pen(0) + +y = 16 + int(LINE_HEIGHT / 2) + +display.thickness(2) +display.text("Normal:", 0, y, TEXT_SIZE) +display.thickness(1) +y += LINE_HEIGHT +display.text("Up / Down - Change launcher page", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +display.text("a, b or c - Launch app", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +y += LINE_HEIGHT + +display.thickness(2) +display.text("Holding USER button:", 0, y, TEXT_SIZE) +display.thickness(1) +y += LINE_HEIGHT +display.text("Up / Down - Change font size", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +display.text("a - Toggle invert", 0, y, TEXT_SIZE) +y += LINE_HEIGHT + +display.update() +while True: + time.sleep(1) diff --git a/badger2040/os/badger2040/image.py b/badger2040/os/badger2040/image.py new file mode 100644 index 0000000..d8956a2 --- /dev/null +++ b/badger2040/os/badger2040/image.py @@ -0,0 +1,154 @@ +import os +import sys +import time +import machine +import badger2040 +from badger2040 import WIDTH, HEIGHT + + +REAMDE = """ +Images must be 296x128 pixel with 1bit colour depth. + +You can use examples/badger2040/image_converter/convert.py to convert them: + +python3 convert.py --binary --resize image_file_1.png image_file_2.png image_file_3.png + +Create a new "images" directory via Thonny, and upload the .bin files there. +""" + +OVERLAY_BORDER = 40 +OVERLAY_SPACING = 20 +OVERLAY_TEXT_SIZE = 0.5 + +TOTAL_IMAGES = 0 + +# Try to preload BadgerPunk image +try: + os.mkdir("images") +except OSError: + pass + +try: + import badgerpunk + with open("images/badgerpunk.bin", "wb") as f: + f.write(badgerpunk.data()) + f.flush() + with open("images/readme.txt", "w") as f: + f.write(REAMDE) + f.flush() + del badgerpunk +except (OSError, ImportError): + pass + +try: + IMAGES = [f for f in os.listdir("/images") if f.endswith(".bin")] + TOTAL_IMAGES = len(IMAGES) +except OSError: + pass + + +display = badger2040.Badger2040() + +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) + +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) + +image = bytearray(int(296 * 128 / 8)) +current_image = 0 +show_info = True + + +# Draw an overlay box with a given message within it +def draw_overlay(message, width, height, line_spacing, text_size): + + # Draw a light grey background + display.pen(12) + display.rectangle((WIDTH - width) // 2, (HEIGHT - height) // 2, width, height) + + # Take the provided message and split it up into + # lines that fit within the specified width + words = message.split(" ") + + lines = [] + current_line = "" + for word in words: + if display.measure_text(current_line + word + " ", text_size) < width: + current_line += word + " " + else: + lines.append(current_line.strip()) + current_line = word + " " + lines.append(current_line.strip()) + + display.pen(0) + display.thickness(2) + + # Display each line of text from the message, centre-aligned + num_lines = len(lines) + for i in range(num_lines): + length = display.measure_text(lines[i], text_size) + current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2 + display.text(lines[i], (WIDTH - length) // 2, (HEIGHT // 2) + current_line, text_size) + + +def show_image(n): + file = IMAGES[n] + name = file.split(".")[0] + open("images/{}".format(file), "r").readinto(image) + display.image(image) + + if show_info: + name_length = display.measure_text(name, 0.5) + display.pen(0) + display.rectangle(0, HEIGHT - 21, name_length + 11, 21) + display.pen(15) + display.rectangle(0, HEIGHT - 20, name_length + 10, 20) + display.pen(0) + display.text(name, 5, HEIGHT - 10, 0.5) + + for i in range(TOTAL_IMAGES): + x = 286 + y = int((128 / 2) - (TOTAL_IMAGES * 10 / 2) + (i * 10)) + display.pen(0) + display.rectangle(x, y, 8, 8) + if current_image != i: + display.pen(15) + display.rectangle(x + 1, y + 1, 6, 6) + + display.update() + + +if TOTAL_IMAGES == 0: + display.pen(15) + display.clear() + draw_overlay("To run this demo, create an /images directory on your device and upload some 1bit 296x128 pixel images.", WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, OVERLAY_TEXT_SIZE) + display.update() + sys.exit() + + +show_image(current_image) + + +while True: + if button_up.value(): + if current_image > 0: + current_image -= 1 + show_image(current_image) + if button_down.value(): + if current_image < TOTAL_IMAGES - 1: + current_image += 1 + show_image(current_image) + if button_a.value(): + show_info = not show_info + show_image(current_image) + if button_b.value() or button_c.value(): + display.pen(15) + display.clear() + draw_overlay("To add images connect Badger2040 to a PC, load up Thonny, and see readme.txt in images/", WIDTH - OVERLAY_BORDER, HEIGHT - OVERLAY_BORDER, OVERLAY_SPACING, 0.5) + display.update() + time.sleep(4) + show_image(current_image) + + time.sleep(0.01) diff --git a/badger2040/os/badger2040/info.py b/badger2040/os/badger2040/info.py new file mode 100644 index 0000000..94c3b1a --- /dev/null +++ b/badger2040/os/badger2040/info.py @@ -0,0 +1,38 @@ +import badger2040 +import time +from badger2040 import WIDTH + +TEXT_SIZE = 0.45 +LINE_HEIGHT = 16 + +display = badger2040.Badger2040() +display.pen(0) +display.rectangle(0, 0, WIDTH, 16) +display.thickness(1) +display.pen(15) +display.text("badgerOS", 3, 8, 0.4) + +display.pen(0) + +y = 16 + int(LINE_HEIGHT / 2) + +display.text("Made by Pimoroni, powered by MicroPython", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +display.text("Dual-core RP2040, 133MHz, 264KB RAM", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +display.text("2MB Flash (1MB OS, 1MB Storage)", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +display.text("296x128 pixel Black/White e-Ink", 0, y, TEXT_SIZE) +y += LINE_HEIGHT +y += LINE_HEIGHT + +display.thickness(2) +display.text("For more info:", 0, y, TEXT_SIZE) +display.thickness(1) +y += LINE_HEIGHT +display.text("https://pimoroni.com/badger2040", 0, y, TEXT_SIZE) + +display.update() + +while True: + time.sleep(1) diff --git a/badger2040/os/badger2040/launcher.py b/badger2040/os/badger2040/launcher.py new file mode 100644 index 0000000..0f0c108 --- /dev/null +++ b/badger2040/os/badger2040/launcher.py @@ -0,0 +1,260 @@ +import gc +import os +import time +import math +import machine +import badger2040 +from badger2040 import WIDTH +import launchericons + +# for e.g. 2xAAA batteries, try max 3.4 min 3.0 +MAX_BATTERY_VOLTAGE = 4.0 +MIN_BATTERY_VOLTAGE = 3.2 + + +page = 0 +font_size = 1 +inverted = False + +icons = bytearray(launchericons.data()) +icons_width = 576 + +examples = [ + ("_clock", 0), + ("_fonts", 1), + ("_ebook", 2), + ("_image", 3), + ("_list", 4), + ("_badge", 5), + ("_qrgen", 8), + ("_info", 6), + ("_help", 7), +] + +font_sizes = (0.5, 0.7, 0.9) + +# Approximate center lines for buttons A, B and C +centers = (41, 147, 253) + +MAX_PAGE = math.ceil(len(examples) / 3) + +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) +# Inverted. For reasons. +button_user = machine.Pin(badger2040.BUTTON_USER, machine.Pin.IN, machine.Pin.PULL_UP) + +# Battery measurement +vbat_adc = machine.ADC(badger2040.PIN_BATTERY) +vref_adc = machine.ADC(badger2040.PIN_1V2_REF) +vref_en = machine.Pin(badger2040.PIN_VREF_POWER) +vref_en.init(machine.Pin.OUT) +vref_en.value(0) + + +display = badger2040.Badger2040() + + +def map_value(input, in_min, in_max, out_min, out_max): + return (((input - in_min) * (out_max - out_min)) / (in_max - in_min)) + out_min + + +def get_battery_level(): + # Enable the onboard voltage reference + vref_en.value(1) + + # Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries + vdd = 1.24 * (65535 / vref_adc.read_u16()) + vbat = ( + (vbat_adc.read_u16() / 65535) * 3 * vdd + ) # 3 in this is a gain, not rounding of 3.3V + + # Disable the onboard voltage reference + vref_en.value(0) + + # Convert the voltage to a level to display onscreen + return int(map_value(vbat, MIN_BATTERY_VOLTAGE, MAX_BATTERY_VOLTAGE, 0, 4)) + + +def draw_battery(level, x, y): + # Outline + display.thickness(1) + display.pen(15) + display.rectangle(x, y, 19, 10) + # Terminal + display.rectangle(x + 19, y + 3, 2, 4) + display.pen(0) + display.rectangle(x + 1, y + 1, 17, 8) + if level < 1: + display.pen(0) + display.line(x + 3, y, x + 3 + 10, y + 10) + display.line(x + 3 + 1, y, x + 3 + 11, y + 10) + display.pen(15) + display.line(x + 2 + 2, y - 1, x + 4 + 12, y + 11) + display.line(x + 2 + 3, y - 1, x + 4 + 13, y + 11) + return + # Battery Bars + display.pen(15) + for i in range(4): + if level / 4 > (1.0 * i) / 4: + display.rectangle(i * 4 + x + 2, y + 2, 3, 6) + + +def draw_disk_usage(x): + # f_bfree and f_bavail should be the same? + # f_files, f_ffree, f_favail and f_flag are unsupported. + f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs( + "/") + + f_total_size = f_frsize * f_blocks + f_total_free = f_bsize * f_bfree + f_total_used = f_total_size - f_total_free + + f_used = 100 / f_total_size * f_total_used + # f_free = 100 / f_total_size * f_total_free + + display.image( + bytearray( + ( + 0b00000000, + 0b00111100, + 0b00111100, + 0b00111100, + 0b00111000, + 0b00000000, + 0b00000000, + 0b00000001, + ) + ), + 8, + 8, + x, + 4, + ) + display.pen(15) + display.rectangle(x + 10, 3, 80, 10) + display.pen(0) + display.rectangle(x + 11, 4, 78, 8) + display.pen(15) + display.rectangle(x + 12, 5, int(76 / 100.0 * f_used), 6) + display.text("{:.2f}%".format(f_used), x + 91, 8, 0.4) + + +def render(): + display.pen(15) + display.clear() + display.pen(0) + display.thickness(2) + + max_icons = min(3, len(examples[(page * 3):])) + + for i in range(max_icons): + x = centers[i] + label, icon = examples[i + (page * 3)] + label = label[1:].replace("_", " ") + display.pen(0) + display.icon(icons, icon, icons_width, 64, x - 32, 24) + w = display.measure_text(label, font_sizes[font_size]) + display.text(label, x - int(w / 2), 16 + 80, font_sizes[font_size]) + + for i in range(MAX_PAGE): + x = 286 + y = int((128 / 2) - (MAX_PAGE * 10 / 2) + (i * 10)) + display.pen(0) + display.rectangle(x, y, 8, 8) + if page != i: + display.pen(15) + display.rectangle(x + 1, y + 1, 6, 6) + + display.pen(0) + display.rectangle(0, 0, WIDTH, 16) + display.thickness(1) + draw_disk_usage(90) + draw_battery(get_battery_level(), WIDTH - 22 - 3, 3) + display.pen(15) + display.text("badgerOS", 3, 8, 0.4) + + display.update() + + +def launch(file): + for k in locals().keys(): + if k not in ("gc", "file", "machine"): + del locals()[k] + gc.collect() + try: + __import__(file[1:]) # Try to import _[file] (drop underscore prefix) + except ImportError: + __import__(file) # Failover to importing [_file] + machine.reset() # Exit back to launcher + + +def launch_example(index): + try: + launch(examples[(page * 3) + index][0]) + return True + except IndexError: + return False + + +def button(pin): + global page, font_size, inverted + + if button_user.value(): # User button is NOT held down + if pin == button_a: + launch_example(0) + if pin == button_b: + launch_example(1) + if pin == button_c: + launch_example(2) + if pin == button_up: + if page > 0: + page -= 1 + render() + if pin == button_down: + if page < MAX_PAGE - 1: + page += 1 + render() + else: # User button IS held down + if pin == button_up: + font_size += 1 + if font_size == len(font_sizes): + font_size = 0 + render() + if pin == button_down: + font_size -= 1 + if font_size < 0: + font_size = 0 + render() + if pin == button_a: + inverted = not inverted + display.invert(inverted) + render() + + +display.update_speed(badger2040.UPDATE_MEDIUM) +render() +display.update_speed(badger2040.UPDATE_FAST) + + +# Wait for wakeup button to be released +while button_a.value() or button_b.value() or button_c.value() or button_up.value() or button_down.value(): + pass + + +while True: + if button_a.value(): + button(button_a) + if button_b.value(): + button(button_b) + if button_c.value(): + button(button_c) + + if button_up.value(): + button(button_up) + if button_down.value(): + button(button_down) + + time.sleep(0.01) diff --git a/badger2040/os/badger2040/led.py b/badger2040/os/badger2040/led.py new file mode 100644 index 0000000..811d294 --- /dev/null +++ b/badger2040/os/badger2040/led.py @@ -0,0 +1,12 @@ +# Blinky badger fun! + +import badger2040 +import time + +badger = badger2040.Badger2040() + +while True: + badger.led(255) + time.sleep(1) + badger.led(0) + time.sleep(1) diff --git a/badger2040/os/badger2040/list.py b/badger2040/os/badger2040/list.py new file mode 100644 index 0000000..a2846ca --- /dev/null +++ b/badger2040/os/badger2040/list.py @@ -0,0 +1,311 @@ +import badger2040 +import machine +import time + +# **** Put your list title and contents here ***** +list_title = "Checklist" +list_content = ["Badger", "Badger", "Badger", "Badger", "Badger", "Mushroom", "Mushroom", "Snake"] +list_states = [False] * len(list_content) +list_file = "checklist.txt" + + +# Global Constants +WIDTH = badger2040.WIDTH +HEIGHT = badger2040.HEIGHT + +ARROW_THICKNESS = 3 +ARROW_WIDTH = 18 +ARROW_HEIGHT = 14 +ARROW_PADDING = 2 + +MAX_ITEM_CHARS = 26 +TITLE_TEXT_SIZE = 0.7 +ITEM_TEXT_SIZE = 0.6 +ITEM_SPACING = 20 + +LIST_START = 40 +LIST_PADDING = 2 +LIST_WIDTH = WIDTH - LIST_PADDING - LIST_PADDING - ARROW_WIDTH +LIST_HEIGHT = HEIGHT - LIST_START - LIST_PADDING - ARROW_HEIGHT + + +def save_list(): + with open(list_file, "w") as f: + f.write(list_title + "\n") + for i in range(len(list_content)): + list_item = list_content[i] + if list_states[i]: + list_item += " X" + f.write(list_item + "\n") + + +# ------------------------------ +# Drawing functions +# ------------------------------ + +# Draw the list of items +def draw_list(items, item_states, start_item, highlighted_item, x, y, width, height, item_height, columns): + item_x = 0 + item_y = 0 + current_col = 0 + for i in range(start_item, len(items)): + if i == highlighted_item: + display.pen(12) + display.rectangle(item_x, item_y + y - (item_height // 2), width // columns, item_height) + display.pen(0) + display.text(items[i], item_x + x + item_height, item_y + y, ITEM_TEXT_SIZE) + draw_checkbox(item_x, item_y + y - (item_height // 2), item_height, 15, 0, 2, item_states[i], 2) + item_y += item_height + if item_y >= height - (item_height // 2): + item_x += width // columns + item_y = 0 + current_col += 1 + if current_col >= columns: + return + + +# Draw a upward arrow +def draw_up(x, y, width, height, thickness, padding): + border = (thickness // 4) + padding + display.line(x + border, y + height - border, + x + (width // 2), y + border) + display.line(x + (width // 2), y + border, + x + width - border, y + height - border) + + +# Draw a downward arrow +def draw_down(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + border, y + border, + x + (width // 2), y + height - border) + display.line(x + (width // 2), y + height - border, + x + width - border, y + border) + + +# Draw a left arrow +def draw_left(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + width - border, y + border, + x + border, y + (height // 2)) + display.line(x + border, y + (height // 2), + x + width - border, y + height - border) + + +# Draw a right arrow +def draw_right(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + border, y + border, + x + width - border, y + (height // 2)) + display.line(x + width - border, y + (height // 2), + x + border, y + height - border) + + +# Draw a tick +def draw_tick(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + border, y + ((height * 2) // 3), + x + (width // 2), y + height - border) + display.line(x + (width // 2), y + height - border, + x + width - border, y + border) + + +# Draw a cross +def draw_cross(x, y, width, height, thickness, padding): + border = (thickness // 2) + padding + display.line(x + border, y + border, x + width - border, y + height - border) + display.line(x + width - border, y + border, x + border, y + height - border) + + +# Draw a checkbox with or without a tick +def draw_checkbox(x, y, size, background, foreground, thickness, tick, padding): + border = (thickness // 2) + padding + display.pen(background) + display.rectangle(x + border, y + border, size - (border * 2), size - (border * 2)) + display.pen(foreground) + display.thickness(thickness) + display.line(x + border, y + border, x + size - border, y + border) + display.line(x + border, y + border, x + border, y + size - border) + display.line(x + size - border, y + border, x + size - border, y + size - border) + display.line(x + border, y + size - border, x + size - border, y + size - border) + if tick: + draw_tick(x, y, size, size, thickness, 2 + border) + + +# ------------------------------ +# Program setup +# ------------------------------ + +# Global variables +update = True +needs_save = False +current_item = 0 +items_per_page = 0 + +# Create a new Badger and set it to update FAST +display = badger2040.Badger2040() +display.update_speed(badger2040.UPDATE_FAST) + +# Set up the buttons +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_up = machine.Pin(badger2040.BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_down = machine.Pin(badger2040.BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN) + +# Find out what the longest item is +longest_item = 0 +for i in range(len(list_content)): + while True: + item = list_content[i] + item_length = display.measure_text(item, ITEM_TEXT_SIZE) + if item_length > 0 and item_length > LIST_WIDTH - ITEM_SPACING: + list_content[i] = item[:-1] + else: + break + longest_item = max(longest_item, display.measure_text(list_content[i], ITEM_TEXT_SIZE)) + + +# And use that to calculate the number of columns we can fit onscreen and how many items that would give +list_columns = 1 +while longest_item + ITEM_SPACING < (LIST_WIDTH // (list_columns + 1)): + list_columns += 1 + +items_per_page = ((LIST_HEIGHT // ITEM_SPACING) + 1) * list_columns + + +# Button handling function +def button(pin): + global update, current_item, needs_save + + if len(list_content) > 0 and not update: + if pin == button_a: + if current_item > 0: + current_item = max(current_item - (items_per_page) // list_columns, 0) + update = True + return + if pin == button_b: + list_states[current_item] = not list_states[current_item] + needs_save = True + update = True + return + if pin == button_c: + if current_item < len(list_content) - 1: + current_item = min(current_item + (items_per_page) // list_columns, len(list_content) - 1) + update = True + return + if pin == button_up: + if current_item > 0: + current_item -= 1 + update = True + return + if pin == button_down: + if current_item < len(list_content) - 1: + current_item += 1 + update = True + return + + +# Register the button handling function with the buttons +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_up.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_down.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + + +# ------------------------------ +# Main program loop +# ------------------------------ + +try: + with open(list_file, "r") as f: + list_content = f.read().strip().split("\n") + list_title = list_content.pop(0) + list_states = [False] * len(list_content) + for i in range(len(list_content)): + list_content[i] = list_content[i].strip() + if list_content[i].endswith(" X"): + list_states[i] = True + list_content[i] = list_content[i][:-2] + +except OSError: + save_list() + + +while True: + if needs_save: + save_list() + needs_save = False + + if update: + display.pen(15) + display.clear() + + display.pen(12) + display.rectangle(WIDTH - ARROW_WIDTH, 0, ARROW_WIDTH, HEIGHT) + display.rectangle(0, HEIGHT - ARROW_HEIGHT, WIDTH, ARROW_HEIGHT) + + y = LIST_PADDING + 12 + display.pen(0) + display.thickness(3) + display.text(list_title, LIST_PADDING, y, TITLE_TEXT_SIZE) + + y += 12 + display.pen(0) + display.thickness(2) + display.line(LIST_PADDING, y, WIDTH - LIST_PADDING - ARROW_WIDTH, y) + + if len(list_content) > 0: + page_item = 0 + if items_per_page > 0: + page_item = (current_item // items_per_page) * items_per_page + + # Draw the list + display.pen(0) + display.thickness(2) + draw_list(list_content, list_states, page_item, current_item, LIST_PADDING, LIST_START, + LIST_WIDTH, LIST_HEIGHT, ITEM_SPACING, list_columns) + + # Draw the interaction button icons + display.pen(0) + display.thickness(ARROW_THICKNESS) + + # Previous item + if current_item > 0: + draw_up(WIDTH - ARROW_WIDTH, (HEIGHT // 4) - (ARROW_HEIGHT // 2), + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + + # Next item + if current_item < (len(list_content) - 1): + draw_down(WIDTH - ARROW_WIDTH, ((HEIGHT * 3) // 4) - (ARROW_HEIGHT // 2), + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + + # Previous column + if current_item > 0: + draw_left((WIDTH // 7) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT, + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + + # Next column + if current_item < (len(list_content) - 1): + draw_right(((WIDTH * 6) // 7) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT, + ARROW_WIDTH, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + + if list_states[current_item]: + # Tick off item + draw_cross((WIDTH // 2) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT, + ARROW_HEIGHT, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + else: + # Untick item + draw_tick((WIDTH // 2) - (ARROW_WIDTH // 2), HEIGHT - ARROW_HEIGHT, + ARROW_HEIGHT, ARROW_HEIGHT, ARROW_THICKNESS, ARROW_PADDING) + else: + # Say that the list is empty + empty_text = "Nothing Here" + text_length = display.measure_text(empty_text, ITEM_TEXT_SIZE) + display.text(empty_text, ((LIST_PADDING + LIST_WIDTH) - text_length) // 2, (LIST_HEIGHT // 2) + LIST_START - (ITEM_SPACING // 4), ITEM_TEXT_SIZE) + + display.update() + display.update_speed(badger2040.UPDATE_TURBO) + update = False + + time.sleep(0.1) diff --git a/badger2040/os/badger2040/micropython-builtins.cmake b/badger2040/os/badger2040/micropython-builtins.cmake new file mode 100644 index 0000000..1e74802 --- /dev/null +++ b/badger2040/os/badger2040/micropython-builtins.cmake @@ -0,0 +1,54 @@ +function (convert_image TARGET IMAGE) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../modules/${IMAGE}.py + + COMMAND + cd ${CMAKE_CURRENT_LIST_DIR}/assets && python3 ../../../../examples/badger2040/image_converter/convert.py --out_dir ${CMAKE_CURRENT_BINARY_DIR}/../modules --py ${IMAGE}.png + + DEPENDS ${CMAKE_CURRENT_LIST_DIR}/assets/${IMAGE}.png + ) + target_sources(${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/../modules/${IMAGE}.py) +endfunction() + +function (convert_raw TARGET SRC DST) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py + + COMMAND + cd ${CMAKE_CURRENT_LIST_DIR}/assets && python3 ../../../../examples/badger2040/image_converter/data_to_py.py ${CMAKE_CURRENT_LIST_DIR}/assets/${SRC} ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py + + DEPENDS ${CMAKE_CURRENT_LIST_DIR}/assets/${SRC} + ) + target_sources(${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py) +endfunction() + +function (copy_module TARGET SRC DST) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py + + COMMAND + cp ${SRC} ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py + + DEPENDS ${src} + ) + + target_sources(${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/../modules/${DST}.py) +endfunction() + +convert_image(usermod_badger2040 badge_image) +convert_image(usermod_badger2040 badgerpunk) +convert_image(usermod_badger2040 launchericons) + +convert_raw(usermod_badger2040 289-0-wind-in-the-willows-abridged.txt witw) + +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/assets/boot.py boot) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/launcher.py _launcher) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/clock.py _clock) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/fonts.py _fonts) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/ebook.py _ebook) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/image.py _image) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/list.py _list) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/badge.py _badge) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/help.py _help) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/info.py _info) +copy_module(usermod_badger2040 ${CMAKE_CURRENT_LIST_DIR}/qrgen.py _qrgen) diff --git a/badger2040/os/badger2040/pin_interrupt.py b/badger2040/os/badger2040/pin_interrupt.py new file mode 100644 index 0000000..3220dd4 --- /dev/null +++ b/badger2040/os/badger2040/pin_interrupt.py @@ -0,0 +1,65 @@ +import badger2040 +import machine + +display = badger2040.Badger2040() + +button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN) +button_b = machine.Pin(badger2040.BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN) + +display.thickness(10) +display.pen(0) +display.line(0, 5, 295, 5) +display.line(0, 123, 295, 123) + +display.thickness(1) +for x in range(14): + display.line(x * 20, 10, x * 20, 118) + +display.line(0, 0, 295, 127) +display.line(0, 127, 295, 0) + +display.font("sans") +display.thickness(5) +display.text("Hello World", 10, 30, 1.0) +display.pen(7) +display.text("Hello World", 10, 60, 1.0) +display.pen(11) +display.text("Hello World", 10, 90, 1.0) + +display.update() + +dirty = False +pressed = None + + +def button(pin): + global dirty, pressed + if pin == button_a: + pressed = "Button A" + dirty = True + return + if pin == button_b: + pressed = "Button B" + dirty = True + return + + +button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=button) +button_b.irq(trigger=machine.Pin.IRQ_RISING, handler=button) + +# This breaks Thonny, since it's no longer possible to Stop the code +# need to press the reset button on the board... +# It will also crash your USB bus, probably, your whole bus... +# @micropython.asm_thumb +# def lightsleep(): +# wfi() + +while True: + if dirty: + display.pen(15) + display.clear() + display.pen(0) + display.text(pressed, 10, 60, 2.0) + display.update() + dirty = False + # machine.lightsleep() # Currently imposible to wake from this on IRQ diff --git a/badger2040/os/badger2040/qrgen.py b/badger2040/os/badger2040/qrgen.py new file mode 100644 index 0000000..1c980c1 --- /dev/null +++ b/badger2040/os/badger2040/qrgen.py @@ -0,0 +1,70 @@ +import badger2040 +import qrcode +import time + + +# Open the qrcode file +try: + text = open("qrcode.txt", "r") +except OSError: + text = open("qrcode.txt", "w") + text.write("""https://pimoroni.com/badger2040 +Badger 2040 +* 296x128 1-bit e-ink +* six user buttons +* user LED +* 2MB QSPI flash + +Scan this code to learn +more about Badger 2040. +""") + text.flush() + text.seek(0) + + +lines = text.read().strip().split("\n") +code_text = lines.pop(0) +title_text = lines.pop(0) +detail_text = lines + +display = badger2040.Badger2040() +code = qrcode.QRCode() + + +def measure_qr_code(size, code): + w, h = code.get_size() + module_size = int(size / w) + return module_size * w, module_size + + +def draw_qr_code(ox, oy, size, code): + size, module_size = measure_qr_code(size, code) + display.pen(15) + display.rectangle(ox, oy, size, size) + display.pen(0) + for x in range(size): + for y in range(size): + if code.get_module(x, y): + display.rectangle(ox + x * module_size, oy + y * module_size, module_size, module_size) + + +code.set_text(code_text) +size, _ = measure_qr_code(128, code) +left = top = int((badger2040.HEIGHT / 2) - (size / 2)) +draw_qr_code(left, top, 128, code) + +left = 128 + 5 + +display.thickness(2) +display.text(title_text, left, 20, 0.5) +display.thickness(1) + +top = 40 +for line in detail_text: + display.text(line, left, top, 0.4) + top += 10 + +display.update() + +while True: + time.sleep(1.0) diff --git a/badger2040/poetry.lock b/badger2040/poetry.lock new file mode 100644 index 0000000..bbdb9e2 --- /dev/null +++ b/badger2040/poetry.lock @@ -0,0 +1,58 @@ +[[package]] +name = "pillow" +version = "9.1.1" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "8a3b1f7b4c4af4b58e7bcb3333c009c0da1516d59db4332afeca1ce6a360c1ad" + +[metadata.files] +pillow = [ + {file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"}, + {file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"}, + {file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"}, + {file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"}, + {file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"}, + {file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"}, + {file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"}, + {file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"}, + {file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"}, + {file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"}, + {file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"}, + {file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"}, + {file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"}, + {file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"}, + {file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"}, + {file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"}, + {file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"}, + {file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"}, + {file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"}, + {file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"}, + {file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"}, + {file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"}, + {file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"}, +] diff --git a/badger2040/pyproject.toml b/badger2040/pyproject.toml new file mode 100644 index 0000000..cbcd7e3 --- /dev/null +++ b/badger2040/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "badger2040" +version = "0.1.0" +description = "scripts et libs pour badger2040" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.8" +Pillow = "^9.1.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"