├── src
├── lib
│ ├── adafruit_register
│ │ ├── __init__.py
│ │ ├── i2c_bit.mpy
│ │ ├── i2c_bits.mpy
│ │ ├── i2c_struct.mpy
│ │ ├── i2c_bcd_alarm.mpy
│ │ ├── i2c_bcd_datetime.mpy
│ │ └── i2c_struct_array.mpy
│ ├── adafruit_ds3231.mpy
│ ├── adafruit_st7789.mpy
│ ├── adafruit_dotstar.mpy
│ ├── adafruit_hid
│ │ ├── gamepad.mpy
│ │ ├── keycode.mpy
│ │ ├── mouse.mpy
│ │ ├── __init__.mpy
│ │ ├── keyboard.mpy
│ │ ├── consumer_control.mpy
│ │ ├── consumer_control_code.mpy
│ │ ├── keyboard_layout.py
│ │ ├── keyboard_layout_us.py
│ │ └── keyboard_layout_fr.py
│ ├── adafruit_progressbar.mpy
│ ├── adafruit_hashlib
│ │ ├── _md5.mpy
│ │ ├── _sha1.mpy
│ │ ├── __init__.mpy
│ │ ├── _sha224.mpy
│ │ ├── _sha256.mpy
│ │ ├── _sha384.mpy
│ │ └── _sha512.mpy
│ ├── adafruit_display_text
│ │ ├── label.mpy
│ │ ├── __init__.mpy
│ │ └── bitmap_label.mpy
│ ├── adafruit_display_shapes
│ │ ├── circle.mpy
│ │ ├── line.mpy
│ │ ├── rect.mpy
│ │ ├── polygon.mpy
│ │ ├── triangle.mpy
│ │ ├── roundrect.mpy
│ │ └── sparkline.mpy
│ ├── helpers.py
│ ├── totp.py
│ ├── pico_dio.py
│ ├── hmac.py
│ ├── base32.py
│ ├── picoth.py
│ └── rgbkeypad.py
├── code.py
└── params.sample.json
├── tools
├── encrypt.py
└── make_mpy.sh
├── .gitignore
├── README.md
└── LICENSE
/src/lib/adafruit_register/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tools/encrypt.py:
--------------------------------------------------------------------------------
1 | """
2 | Encrypt a params.json file with the given code
3 | """
4 |
5 | # TODO
6 |
--------------------------------------------------------------------------------
/src/lib/adafruit_ds3231.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_ds3231.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_st7789.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_st7789.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_dotstar.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_dotstar.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/gamepad.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/gamepad.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/keycode.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/keycode.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/mouse.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/mouse.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_progressbar.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_progressbar.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/_md5.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/_md5.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/_sha1.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/_sha1.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/__init__.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/__init__.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/keyboard.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/keyboard.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/__init__.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/__init__.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/_sha224.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/_sha224.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/_sha256.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/_sha256.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/_sha384.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/_sha384.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hashlib/_sha512.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hashlib/_sha512.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_register/i2c_bit.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_register/i2c_bit.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_text/label.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_text/label.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_register/i2c_bits.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_register/i2c_bits.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/circle.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/circle.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/line.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/line.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/rect.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/rect.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_text/__init__.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_text/__init__.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/consumer_control.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/consumer_control.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_register/i2c_struct.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_register/i2c_struct.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/polygon.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/polygon.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/triangle.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/triangle.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_register/i2c_bcd_alarm.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_register/i2c_bcd_alarm.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/roundrect.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/roundrect.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_shapes/sparkline.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_shapes/sparkline.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_display_text/bitmap_label.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_display_text/bitmap_label.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/consumer_control_code.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_hid/consumer_control_code.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_register/i2c_bcd_datetime.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_register/i2c_bcd_datetime.mpy
--------------------------------------------------------------------------------
/src/lib/adafruit_register/i2c_struct_array.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AngainorDev/Picoth/HEAD/src/lib/adafruit_register/i2c_struct_array.mpy
--------------------------------------------------------------------------------
/tools/make_mpy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ../../circuitpython/mpy-cross/mpy-cross ../src/lib/base32.py
4 | ../../circuitpython/mpy-cross/mpy-cross ../src/lib/hmac.py
5 | ../../circuitpython/mpy-cross/mpy-cross ../src/lib/totp.py
6 |
--------------------------------------------------------------------------------
/src/code.py:
--------------------------------------------------------------------------------
1 | from picoth import Picoth
2 | import json
3 |
4 |
5 | if __name__ == "__main__":
6 | with open("/params.json") as f:
7 | config = json.load(f)
8 | manager = Picoth(config)
9 | manager.run()
10 |
11 |
--------------------------------------------------------------------------------
/src/lib/helpers.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | # map a numeric touch to the keypad touch index
4 | NUMPAD_TO_TOUCH = [13, 8, 9, 10, 4, 5, 6, 0, 1, 2]
5 |
6 | # map a keypad touch index to a numpad int or string.
7 | TOUCH_TO_NUMPAD = [7, 8, 9, "L", 4, 5, 6, "+", 1, 2, 3, "-", "P", 0, "N", "E"]
8 |
9 |
10 | def hex_to_rgb(hex_str):
11 | return [int(hex_str[x:x + 2], 16) for x in (0, 2, 4)]
12 |
13 |
14 | def ts_to_unix(dt, offset=-3600):
15 | return time.mktime(dt) + offset
16 |
17 |
18 | def state_to_button(state):
19 | # From state int, gives the first button down
20 | # Does not detect multiple buttons
21 | for i in range(16):
22 | if state & (1 << i):
23 | return i
24 | return -1
25 |
26 |
27 | def button_to_numpad(button):
28 | return TOUCH_TO_NUMPAD[button]
29 |
30 |
--------------------------------------------------------------------------------
/src/params.sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "check": "ff00112233",
3 | "layout": "fr",
4 | "time_offset": -3600,
5 | "pages":[
6 | {"type": "TOTP",
7 | "name": " Crypto OTPs",
8 | "keys": [
9 | ["","000000",""],
10 | ["qTrade","FF7829","ftslvgd2zddprptn"],
11 | ["Bittrex","0081EB","stflvgd2zddprptn"],
12 | ["Nash.io","5790FF","ltfsvgd2zddprptn"],
13 | ["Github","F34F29","vtfslgd2zddprptn"],
14 | ["","000000",""],
15 | ["","000000",""],
16 | ["Discord","4285F4","fslvgd2zddprptnt"],
17 | ["","000000",""],
18 | ["","000000",""]
19 | ]
20 | },
21 | {"type": "TOTP",
22 | "name": " TEST OTPs",
23 | "keys": [
24 | ["TEST 0","444444","ftslvgd2zddprptn"],
25 | ["TEST 1","FF0000","tslvgd2zddprptnf"],
26 | ["TEST 2","00FF00","slvgd2zddprptnft"],
27 | ["TEST 3","0000FF","lvgd2zddprptnfts"],
28 | ["TEST 4","00FF00","vgd2zddprptnftsl"],
29 | ["TEST 5","0000FF","gd2zddprptnftslv"],
30 | ["TEST 6","FF0000","d2zddprptnftslvg"],
31 | ["TEST 7","0000FF","2zddprptnftslvgd"],
32 | ["TEST 8","FF0000","zddprptnftslvgd2"],
33 | ["TEST 9","00FF00","ddprptnftslvgd2z"]
34 | ]
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/lib/totp.py:
--------------------------------------------------------------------------------
1 | """
2 | Extended from https://github.com/susam/mintotp/blob/master/mintotp.py
3 | Adapted for Circuit Python, using adafruit libs when available
4 |
5 | Licence: MIT
6 | Copyright (c) 2019 Susam Pal
7 | Copyright (c) 2021 Angainor Dev
8 | """
9 |
10 | import base32
11 | import hmac
12 | import struct
13 | import time
14 | from adafruit_hashlib import sha1 as SHA1
15 |
16 |
17 | class TOTP:
18 |
19 | __slots__ = ("_key", "_binkey", "time_step", "digits", "digest")
20 |
21 | def __init__(self, key, time_step=30, digits=6, digest=SHA1):
22 | self._key = key
23 | self._binkey = base32.b32decode(self._key.upper() + "=" * ((8 - len(self._key)) % 8))
24 | self.time_step = time_step
25 | self.digits = digits
26 | self.digest = digest
27 |
28 | def hotp(self, counter):
29 | # t = time.monotonic_ns()
30 | counter = struct.pack(">Q", counter)
31 | mac = hmac.new(self._binkey, counter, self.digest).digest()
32 | offset = mac[-1] & 0x0F
33 | binary = struct.unpack(">L", mac[offset: offset + 4])[0] & 0x7FFFFFFF
34 | """res = str(binary)[-self.digits:]
35 | while len(res) < self.digits:
36 | res = "0" + res
37 | """
38 | res = f"{binary:06d}"[-self.digits:] # shorter, no speed diff
39 | # print("hotp", (time.monotonic_ns() - t)/ 1e9) # 0.22 sec from .py, same from mpy
40 | return res
41 |
42 | def totp(self, margin=1.5):
43 | left, _ = self.time_left()
44 | while left < margin or left > self.time_step - margin:
45 | time.sleep(1)
46 | left = self.time_left()
47 | # print("left", left)
48 | return self.hotp(time.time() // self.time_step)
49 |
50 | def totpt(self, t):
51 | return self.hotp(t // self.time_step)
52 |
53 | def time_left(self, t=0):
54 | if t == 0:
55 | t = int(time.time())
56 | counter = t // self.time_step
57 | return self.time_step - t + self.time_step * counter, counter
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | .idea
6 | temp
7 | params.json
8 |
9 | # C extensions
10 | *.so
11 |
12 | # Distribution / packaging
13 | .Python
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | pip-wheel-metadata/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | *.py,cover
53 | .hypothesis/
54 | .pytest_cache/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | .python-version
88 |
89 | # pipenv
90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
93 | # install all needed dependencies.
94 | #Pipfile.lock
95 |
96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
97 | __pypackages__/
98 |
99 | # Celery stuff
100 | celerybeat-schedule
101 | celerybeat.pid
102 |
103 | # SageMath parsed files
104 | *.sage.py
105 |
106 | # Environments
107 | .env
108 | .venv
109 | env/
110 | venv/
111 | ENV/
112 | env.bak/
113 | venv.bak/
114 |
115 | # Spyder project settings
116 | .spyderproject
117 | .spyproject
118 |
119 | # Rope project settings
120 | .ropeproject
121 |
122 | # mkdocs documentation
123 | /site
124 |
125 | # mypy
126 | .mypy_cache/
127 | .dmypy.json
128 | dmypy.json
129 |
130 | # Pyre type checker
131 | .pyre/
132 |
--------------------------------------------------------------------------------
/src/lib/pico_dio.py:
--------------------------------------------------------------------------------
1 | import displayio
2 | import board
3 | import busio
4 | import terminalio
5 | from adafruit_display_text import label
6 | # from adafruit_display_shapes import rect
7 | from adafruit_progressbar import ProgressBar
8 | from adafruit_st7789 import ST7789
9 |
10 |
11 | # TODO: Refactor as a class so can be abstracted and use alternates screens or lower level lib when needed.
12 |
13 | def get_display():
14 | displayio.release_displays()
15 |
16 | tft_cs = board.GP21
17 | tft_dc = board.GP16
18 | # tft_res = board.GP23
19 | spi_mosi = board.GP27
20 | spi_clk = board.GP26
21 | # https://gist.github.com/wildestpixel/86ac1063bc456213f92972fcd7c7c2e1
22 | spi = busio.SPI(spi_clk, MOSI=spi_mosi)
23 | while not spi.try_lock():
24 | pass
25 | # Configure SPI was 24MHz by default - Trying 64Mhz, no visible change
26 | spi.configure(baudrate=64000000)
27 | spi.unlock()
28 | display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, baudrate=64000000)
29 | display = ST7789(display_bus, width=135, height=240, rowstart=40, colstart=53)
30 | display.rotation = 270
31 | return display
32 |
33 |
34 | def get_splash():
35 | splash = displayio.Group(max_size=10)
36 | text_group2 = displayio.Group(max_size=10, scale=3, x=40, y=20)
37 | text2 = "Picoth"
38 | text_area2 = label.Label(terminalio.FONT, text=text2, color=0x00FF00)
39 | text_group2.append(text_area2)
40 | splash.append(text_group2)
41 | return splash
42 |
43 |
44 | # OTP screen
45 | def get_otp_group():
46 | otp_group = displayio.Group(max_size=10, scale=1, x=0, y=0)
47 | # OTP Code
48 | text_group1 = displayio.Group(max_size=10, scale=6, x=15, y=100)
49 | text1 = "999999"
50 | text_area1 = label.Label(terminalio.FONT, text=text1, color=0x00FF00)
51 | text_group1.append(text_area1) # Subgroup for text scaling
52 | otp_group.append(text_group1)
53 | # OTP label
54 | text_group2 = displayio.Group(max_size=10, scale=3, x=40, y=20)
55 | text2 = "OTP Label"
56 | text_area2 = label.Label(terminalio.FONT, text=text2, color=0xFF0000)
57 | text_group2.append(text_area2)
58 | otp_group.append(text_group2)
59 | """
60 | # 2 rectangles seemed to fit, but adjusting their width afterward does not refresh the display.
61 | # grey rectangle
62 | rect1 = rect.Rect(0, 70, 240, 4, fill=0x1e1e1e)
63 | otp_group.append(rect1)
64 | # time left
65 | rect2 = rect.Rect(0, 70, 120, 4, fill=0x00fa00)
66 | otp_group.append(rect2)
67 | """
68 | progress = ProgressBar(0, 60, 240, 9, bar_color=0x00fa00, outline_color=0x1e1e1e)
69 | otp_group.append(progress)
70 | return otp_group, text_area1, text_area2, progress
71 |
72 |
73 | def get_page_group():
74 | page_group = displayio.Group(max_size=10, scale=1, x=0, y=0)
75 | # Mode
76 | text_group1 = displayio.Group(max_size=10, scale=4, x=5, y=20)
77 | text_area1 = label.Label(terminalio.FONT, text="xx", color=0x00FF00)
78 | text_group1.append(text_area1) # Subgroup for text scaling
79 | page_group.append(text_group1)
80 | # Page number
81 | text_group2 = displayio.Group(max_size=10, scale=3, x=70, y=70)
82 | text_area2 = label.Label(terminalio.FONT, text="Page X", color=0xFF0000)
83 | text_group2.append(text_area2) # Subgroup for text scaling
84 | page_group.append(text_group2)
85 | # Label
86 | text_group3 = displayio.Group(max_size=10, scale=3, x=10, y=110)
87 | text_area3 = label.Label(terminalio.FONT, text="Label", color=0xFF0000)
88 | text_group3.append(text_area3)
89 | page_group.append(text_group3)
90 | return page_group, text_area1, text_area2, text_area3
91 |
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/keyboard_layout.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | """
6 | `adafruit_hid.keyboard_layout_us.KeyboardLayoutUS`
7 | =======================================================
8 |
9 | * Author(s): Dan Halbert, AngainorDev
10 | """
11 |
12 | from .keycode import Keycode
13 |
14 |
15 | class KeyboardLayout:
16 | """Map ASCII characters to appropriate keypresses on a standard US PC keyboard.
17 |
18 | Non-ASCII characters and most control characters will raise an exception.
19 | """
20 | # We use the top bit of each byte (0x80) to indicate
21 | # that the shift key should be pressed, and an extra 9th bit 0x100 for AltGr
22 | SHIFT_FLAG = 0x80
23 | ALTGR_FLAG = 0x100
24 | ASCII_TO_KEYCODE = ()
25 | NEED_ALTGR = ""
26 |
27 | def __init__(self, keyboard):
28 | """Specify the layout for the given keyboard.
29 |
30 | :param keyboard: a Keyboard object. Write characters to this keyboard when requested.
31 |
32 | Example::
33 |
34 | kbd = Keyboard(usb_hid.devices)
35 | layout = KeyboardLayoutUS(kbd)
36 | """
37 |
38 | self.keyboard = keyboard
39 |
40 | def write(self, string):
41 | """Type the string by pressing and releasing keys on my keyboard.
42 |
43 | :param string: A string of ASCII characters.
44 | :raises ValueError: if any of the characters are not ASCII or have no keycode
45 | (such as some control characters).
46 |
47 | Example::
48 |
49 | # Write abc followed by Enter to the keyboard
50 | layout.write('abc\\n')
51 | """
52 | for char in string:
53 | keycode = self._char_to_keycode(char)
54 | if char in self.NEED_ALTGR:
55 | # Add altgr modifier
56 | self.keyboard.press(Keycode.RIGHT_ALT)
57 | # If this is a shifted char, clear the SHIFT flag and press the SHIFT key.
58 | if keycode & self.SHIFT_FLAG:
59 | keycode &= ~self.SHIFT_FLAG
60 | self.keyboard.press(Keycode.SHIFT)
61 | self.keyboard.press(keycode)
62 | self.keyboard.release_all()
63 |
64 | def keycodes(self, char):
65 | """Return a tuple of keycodes needed to type the given character.
66 |
67 | :param char: A single ASCII character in a string.
68 | :type char: str of length one.
69 | :returns: tuple of Keycode keycodes.
70 | :raises ValueError: if ``char`` is not ASCII or there is no keycode for it.
71 |
72 | Examples::
73 |
74 | # Returns (Keycode.TAB,)
75 | keycodes('\t')
76 | # Returns (Keycode.A,)
77 | keycodes('a')
78 | # Returns (Keycode.SHIFT, Keycode.A)
79 | keycodes('A')
80 | # Raises ValueError because it's a accented e and is not ASCII
81 | keycodes('é')
82 | """
83 | keycode = self._char_to_keycode(char)
84 | if keycode & self.SHIFT_FLAG:
85 | return (Keycode.SHIFT, keycode & ~self.SHIFT_FLAG)
86 | if char in self.NEED_ALTGR:
87 | return (Keycode.RIGHT_ALT, keycode)
88 |
89 | return (keycode,)
90 |
91 | def _above128charval_to_keycode(self, char_val):
92 | raise ValueError("Not an ASCII character.")
93 |
94 | def _char_to_keycode(self, char):
95 | """Return the HID keycode for the given ASCII character, with the SHIFT_FLAG possibly set.
96 |
97 | If the character requires pressing the Shift key, the SHIFT_FLAG bit is set.
98 | You must clear this bit before passing the keycode in a USB report.
99 | """
100 | char_val = ord(char)
101 | if char_val > 128:
102 | return self._above128charval_to_keycode(char)
103 | keycode = self.ASCII_TO_KEYCODE[char_val]
104 | if keycode == 0:
105 | raise ValueError("No keycode available for character.")
106 | return keycode
107 |
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/keyboard_layout_us.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | """
6 | `adafruit_hid.keyboard_layout_us.KeyboardLayoutUS`
7 | =======================================================
8 |
9 | * Author(s): Dan Halbert, AngainorDev
10 | """
11 |
12 | from .keyboard_layout import KeyboardLayout
13 |
14 |
15 | class KeyboardLayoutUS(KeyboardLayout):
16 | """Map ASCII characters to appropriate keypresses on a standard US PC keyboard.
17 |
18 | Non-ASCII characters and most control characters will raise an exception.
19 | """
20 |
21 | # The ASCII_TO_KEYCODE bytes object is used as a table to maps ASCII 0-127
22 | # to the corresponding # keycode on a US 104-key keyboard.
23 | # The user should not normally need to use this table,
24 | # but it is not marked as private.
25 | #
26 | # Because the table only goes to 127, we use the top bit of each byte (ox80) to indicate
27 | # that the shift key should be pressed. So any values 0x{8,9,a,b}* are shifted characters.
28 | #
29 | # The Python compiler will concatenate all these bytes literals into a single bytes object.
30 | # Micropython/CircuitPython will store the resulting bytes constant in flash memory
31 | # if it's in a .mpy file, so it doesn't use up valuable RAM.
32 | #
33 | # \x00 entries have no keyboard key and so won't be sent.
34 | ASCII_TO_KEYCODE = (
35 | b"\x00" # NUL
36 | b"\x00" # SOH
37 | b"\x00" # STX
38 | b"\x00" # ETX
39 | b"\x00" # EOT
40 | b"\x00" # ENQ
41 | b"\x00" # ACK
42 | b"\x00" # BEL \a
43 | b"\x2a" # BS BACKSPACE \b (called DELETE in the usb.org document)
44 | b"\x2b" # TAB \t
45 | b"\x28" # LF \n (called Return or ENTER in the usb.org document)
46 | b"\x00" # VT \v
47 | b"\x00" # FF \f
48 | b"\x00" # CR \r
49 | b"\x00" # SO
50 | b"\x00" # SI
51 | b"\x00" # DLE
52 | b"\x00" # DC1
53 | b"\x00" # DC2
54 | b"\x00" # DC3
55 | b"\x00" # DC4
56 | b"\x00" # NAK
57 | b"\x00" # SYN
58 | b"\x00" # ETB
59 | b"\x00" # CAN
60 | b"\x00" # EM
61 | b"\x00" # SUB
62 | b"\x29" # ESC
63 | b"\x00" # FS
64 | b"\x00" # GS
65 | b"\x00" # RS
66 | b"\x00" # US
67 | b"\x2c" # SPACE
68 | b"\x9e" # ! x1e|SHIFT_FLAG (shift 1)
69 | b"\xb4" # " x34|SHIFT_FLAG (shift ')
70 | b"\xa0" # # x20|SHIFT_FLAG (shift 3)
71 | b"\xa1" # $ x21|SHIFT_FLAG (shift 4)
72 | b"\xa2" # % x22|SHIFT_FLAG (shift 5)
73 | b"\xa4" # & x24|SHIFT_FLAG (shift 7)
74 | b"\x34" # '
75 | b"\xa6" # ( x26|SHIFT_FLAG (shift 9)
76 | b"\xa7" # ) x27|SHIFT_FLAG (shift 0)
77 | b"\xa5" # * x25|SHIFT_FLAG (shift 8)
78 | b"\xae" # + x2e|SHIFT_FLAG (shift =)
79 | b"\x36" # ,
80 | b"\x2d" # -
81 | b"\x37" # .
82 | b"\x38" # /
83 | b"\x27" # 0
84 | b"\x1e" # 1
85 | b"\x1f" # 2
86 | b"\x20" # 3
87 | b"\x21" # 4
88 | b"\x22" # 5
89 | b"\x23" # 6
90 | b"\x24" # 7
91 | b"\x25" # 8
92 | b"\x26" # 9
93 | b"\xb3" # : x33|SHIFT_FLAG (shift ;)
94 | b"\x33" # ;
95 | b"\xb6" # < x36|SHIFT_FLAG (shift ,)
96 | b"\x2e" # =
97 | b"\xb7" # > x37|SHIFT_FLAG (shift .)
98 | b"\xb8" # ? x38|SHIFT_FLAG (shift /)
99 | b"\x9f" # @ x1f|SHIFT_FLAG (shift 2)
100 | b"\x84" # A x04|SHIFT_FLAG (shift a)
101 | b"\x85" # B x05|SHIFT_FLAG (etc.)
102 | b"\x86" # C x06|SHIFT_FLAG
103 | b"\x87" # D x07|SHIFT_FLAG
104 | b"\x88" # E x08|SHIFT_FLAG
105 | b"\x89" # F x09|SHIFT_FLAG
106 | b"\x8a" # G x0a|SHIFT_FLAG
107 | b"\x8b" # H x0b|SHIFT_FLAG
108 | b"\x8c" # I x0c|SHIFT_FLAG
109 | b"\x8d" # J x0d|SHIFT_FLAG
110 | b"\x8e" # K x0e|SHIFT_FLAG
111 | b"\x8f" # L x0f|SHIFT_FLAG
112 | b"\x90" # M x10|SHIFT_FLAG
113 | b"\x91" # N x11|SHIFT_FLAG
114 | b"\x92" # O x12|SHIFT_FLAG
115 | b"\x93" # P x13|SHIFT_FLAG
116 | b"\x94" # Q x14|SHIFT_FLAG
117 | b"\x95" # R x15|SHIFT_FLAG
118 | b"\x96" # S x16|SHIFT_FLAG
119 | b"\x97" # T x17|SHIFT_FLAG
120 | b"\x98" # U x18|SHIFT_FLAG
121 | b"\x99" # V x19|SHIFT_FLAG
122 | b"\x9a" # W x1a|SHIFT_FLAG
123 | b"\x9b" # X x1b|SHIFT_FLAG
124 | b"\x9c" # Y x1c|SHIFT_FLAG
125 | b"\x9d" # Z x1d|SHIFT_FLAG
126 | b"\x2f" # [
127 | b"\x31" # \ backslash
128 | b"\x30" # ]
129 | b"\xa3" # ^ x23|SHIFT_FLAG (shift 6)
130 | b"\xad" # _ x2d|SHIFT_FLAG (shift -)
131 | b"\x35" # `
132 | b"\x04" # a
133 | b"\x05" # b
134 | b"\x06" # c
135 | b"\x07" # d
136 | b"\x08" # e
137 | b"\x09" # f
138 | b"\x0a" # g
139 | b"\x0b" # h
140 | b"\x0c" # i
141 | b"\x0d" # j
142 | b"\x0e" # k
143 | b"\x0f" # l
144 | b"\x10" # m
145 | b"\x11" # n
146 | b"\x12" # o
147 | b"\x13" # p
148 | b"\x14" # q
149 | b"\x15" # r
150 | b"\x16" # s
151 | b"\x17" # t
152 | b"\x18" # u
153 | b"\x19" # v
154 | b"\x1a" # w
155 | b"\x1b" # x
156 | b"\x1c" # y
157 | b"\x1d" # z
158 | b"\xaf" # { x2f|SHIFT_FLAG (shift [)
159 | b"\xb1" # | x31|SHIFT_FLAG (shift \)
160 | b"\xb0" # } x30|SHIFT_FLAG (shift ])
161 | b"\xb5" # ~ x35|SHIFT_FLAG (shift `)
162 | b"\x4c" # DEL DELETE (called Forward Delete in usb.org document)
163 | )
164 |
165 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Picoth
2 | An OTP enabled macro keyboard based upon a Raspberry Pi Pico and RGB keypad base from Pimoroni.
3 |
4 | 
5 |
6 | Project log on Hackaday.io: [Picoth on HackaDay.io](https://hackaday.io/project/177593-picoth-2fa-auth-with-pi-pico)
7 |
8 | **Warning**: In its current state, this device is not safe to use in an adversarial environment: anyone having access to it could steal your config file, hence your OTP keys.
9 | Next versions will include encrypted keys and an option to remove the USB disk drive mount.
10 |
11 | # Overview
12 |
13 | This is a work in progress, feel free to ask and request more info.
14 | [Twitter @Angainor15](https://twitter.com/Angainor15)
15 | [Discord](https://discord.gg/gy9xpuQK8A)
16 |
17 |
18 | ## keypad mapping
19 |
20 | The mapping is a regular keypad instead of the Pimoroni default's one.
21 |
22 | |||||
23 | |---|---|---|---|
24 | | 7 | 8 | 9 | L |
25 | | 4 | 5 | 6 | + |
26 | | 1 | 2 | 3 | - |
27 | | P | 0 | N | E |
28 |
29 | L = Sleep/Lock
30 | E = Enter
31 | P = Previous Page
32 | N = Next Page
33 |
34 | # Hardware
35 |
36 | - 1× Raspberry Pi Pico
37 | https://shop.pimoroni.com/products/raspberry-pi-pico
38 | - 1× Pimoroni's RGB Keypad
39 | https://shop.pimoroni.com/products/pico-rgb-keypad-base
40 | - 1× Pimoroni's Pico display
41 | https://shop.pimoroni.com/products/pico-display-pack
42 | - 1× DS3231 Arduino module
43 | https://s.click.aliexpress.com/e/_AZGRXo
44 |
45 | *Note:* This is not sponsored by Pimoroni, I was not paid to build this project and bought the hardware myself.
46 | I just like what the pirates do.
47 |
48 | # Setup
49 |
50 | See the wiring and photos on [Hackaday](https://hackaday.io/project/177593-picoth-2fa-auth-with-pi-pico/log/189173-definitive-wiring)
51 | and [Twitter post](https://twitter.com/Angainor15/status/1359431057611882498)
52 |
53 |
54 | ## Circuit python
55 |
56 | https://circuitpython.org/board/raspberry_pi_pico/
57 | I was running on 6.2.0 Beta 3, Beta 4 is now available.
58 |
59 | ## Circuit Python libraries
60 |
61 | All needed libs are duplicated in the src/lib for convenience.
62 | Just copy the "src/lib" content on your CIRCUITPY usb drive.
63 |
64 | For reference, here are the stock Adafruit libs that were used (they can all be found in the default [Adafruit lib pack](https://github.com/adafruit/Adafruit_CircuitPython_Bundle))
65 |
66 | - adafruit_display_text
67 | - adafruit_hashlib
68 | - adafruit_hid
69 | - adafruit_register
70 | - adafruit_dotstar
71 | - adafruit_ds3231
72 | - adafruit_progressbar
73 | - adafruit_st7789
74 |
75 | The RGBKeypad library is very heavily inspired from Sandy J Mac Donald's awesome Keybow 2040 library
76 | https://twitter.com/sandyjmacdonald/status/1370459658608074758
77 | https://github.com/sandyjmacdonald/keybow2040-circuitpython
78 |
79 | ## Source code
80 |
81 | Just copy the python files from the "src/" folder on your CIRCUITPY usb drive.
82 |
83 | *Note:* Nothing is "clean" yet. It's a working but proof of concept code, iterated from several attempts and migration from MP to CP and libraries changes.
84 | This will be improved over time.
85 |
86 | ## Time config
87 |
88 | The RTC module needs to be setup once.
89 | This will eventually be done via the GUI, but in the mean time you can do it manually from the Python repl:
90 |
91 | Init a DS3231 instance:
92 | ```
93 | from adafruit_ds3231 import DS3231
94 | import board
95 | import busio
96 | import time
97 | i2c = busio.I2C(board.GP11, board.GP10)
98 | ds = DS3231(i2c)
99 | ```
100 |
101 | From there you can query the current datetime:
102 | `ds.datetime`
103 | the temp
104 | `ds.temperature`
105 |
106 | and more importantly setup the date and time:
107 | `rtc.datetime = time.struct_time((2021, 3, 24, 15, 3, 0, 0, -1, -1))`
108 | params are year, month, day, hour, min, sec, weekday(0-6), yearday(can be -1), isdst(-1 or 0)
109 |
110 | # Config
111 |
112 | Copy params.sample.json to params.json, edit and copy on your device.
113 |
114 | params.json is a json file.
115 | Core params are
116 | ```
117 | {
118 | "check": "ff00112233",
119 | "layout": "fr",
120 | "time_offset": -3600,
121 | "pages":[]
122 | }
123 | ```
124 |
125 | - check is reserved for future use when encrypting the keys
126 | - layout is the keyboard layout (currently supports "us" and "fr")
127 | - time_offset - in seconds - is your timezone: -3600 for GMT+1
128 | - pages is a list of pages (1 page minimum has to be defined)
129 |
130 | Every page is defined as:
131 |
132 | ```
133 | {"type": "TOTP",
134 | "name": "Test OTPs",
135 | "keys": [
136 | ["","000000",""],
137 | ["qTrade","FF7829","ftslvgd2zddprptn"],
138 | ["Bittrex","0081EB","stflvgd2zddprptn"],
139 | ["Nash.io","5790FF","ltfsvgd2zddprptn"],
140 | ["Github","F34F29","vtfslgd2zddprptn"],
141 | ["","000000",""],
142 | ["","000000",""],
143 | ["Discord","4285F4","fslvgd2zddprptnt"],
144 | ["","000000",""],
145 | ["","000000",""]
146 | ]
147 | },
148 |
149 | ```
150 |
151 | - type: only "TOTP" is supported for now
152 | - keys: a list [0..9] of entries. See layout above, fits a numpad, that is first entry, 0, is on the lower line, and 7, 8, 9 (3 last entries in the list) are the 3 top keys.
153 | - Each key is a list as well: ["label", "hexcolor", "totp seed"]
154 | - Use ["","000000",""] for an inactive key
155 |
156 |
157 | # Roadmap
158 |
159 | - Keys encryption, device lock, pincode
160 | - More modes (type in password or text, media pad, pomodoro)
161 | - Sleep/screen saver mode
162 | - Improve screen refresh speed
163 | - Improve GUI
164 | - 3D Printed case
165 | - Alternate screens or no screen?
166 |
167 |
168 | # Licence
169 |
170 | Custom code is released under the GNU AFFERO GENERAL PUBLIC LICENSE.
171 | If this conflicts with some of the MIT licenced source code used, then the MIT licence is to be used instead.
172 | Specific code files can ship with their own licence in their header.
173 |
--------------------------------------------------------------------------------
/src/lib/hmac.py:
--------------------------------------------------------------------------------
1 | """HMAC (Keyed-Hashing for Message Authentication) Python module.
2 |
3 | Implements the HMAC algorithm as described by RFC 2104.
4 | """
5 |
6 | import warnings as _warnings
7 | import binascii
8 | from adafruit_hashlib import sha1 as SHA1
9 | PendingDeprecationWarning = None
10 | RuntimeWarning = None
11 |
12 | trans_5C = bytes((x ^ 0x5C) for x in range(256))
13 | trans_36 = bytes((x ^ 0x36) for x in range(256))
14 |
15 |
16 | def translate(d, t):
17 | return bytes(t[x] for x in d)
18 |
19 |
20 | # The size of the digests returned by HMAC depends on the underlying
21 | # hashing module used. Use digest_size from the instance of HMAC instead.
22 | digest_size = None
23 |
24 |
25 | class HMAC:
26 | """RFC 2104 HMAC class. Also complies with RFC 4231.
27 |
28 | This supports the API for Cryptographic Hash Functions (PEP 247).
29 | """
30 | blocksize = 64 # 512-bit HMAC; can be changed in subclasses.
31 |
32 | def __init__(self, key, msg=None, digestmod=None):
33 | """Create a new HMAC object.
34 |
35 | key: key for the keyed hash object.
36 | msg: Initial input for the hash, if provided.
37 | digestmod: A module supporting PEP 247. *OR*
38 | A hashlib constructor returning a new hash object. *OR*
39 | A hash name suitable for hashlib.new().
40 | Defaults to hashlib.md5.
41 | Implicit default to hashlib.md5 is deprecated and will be
42 | removed in Python 3.6.
43 |
44 | Note: key and msg must be a bytes or bytearray objects.
45 | """
46 |
47 | if not isinstance(key, (bytes, bytearray)):
48 | raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
49 |
50 | if digestmod is None:
51 | _warnings.warn("HMAC() without an explicit digestmod argument "
52 | "is deprecated.", PendingDeprecationWarning, 2)
53 | # digestmod = _hashlib.md5
54 |
55 | if callable(digestmod):
56 | self.digest_cons = digestmod
57 |
58 | self.outer = self.digest_cons()
59 | self.inner = self.digest_cons()
60 | self.digest_size = self.inner.digest_size
61 |
62 | if hasattr(self.inner, 'block_size'):
63 | blocksize = self.inner.block_size
64 | if blocksize < 16:
65 | _warnings.warn('block_size of %d seems too small; using our '
66 | 'default of %d.' % (blocksize, self.blocksize),
67 | RuntimeWarning, 2)
68 | blocksize = self.blocksize
69 | else:
70 | # _warnings.warn('No block_size attribute on given digest object; Assuming %d.'
71 | # % (self.blocksize), RuntimeWarning, 2)
72 | blocksize = self.blocksize
73 |
74 | # self.blocksize is the default blocksize. self.block_size is
75 | # effective block size as well as the public API attribute.
76 | self.block_size = blocksize
77 |
78 | if len(key) > blocksize:
79 | key = self.digest_cons(key).digest()
80 |
81 | key = key + bytes(blocksize - len(key))
82 | self.outer.update(translate(key, trans_5C))
83 | self.inner.update(translate(key, trans_36))
84 | if msg is not None:
85 | self.update(msg)
86 |
87 | @property
88 | def name(self):
89 | return "hmac-" + self.inner.name
90 |
91 | def update(self, msg):
92 | """Update this hashing object with the string msg.
93 | """
94 | self.inner.update(msg)
95 |
96 | def copy_remove(self):
97 | """Return a separate copy of this hashing object.
98 |
99 | An update to this copy won't affect the original object.
100 | """
101 | # Call __new__ directly to avoid the expensive __init__.
102 | other = self.__class__.__new__(self.__class__)
103 | other.digest_cons = self.digest_cons
104 | other.digest_size = self.digest_size
105 | other.inner = self.inner.copy()
106 | other.outer = self.outer.copy()
107 | return other
108 |
109 | def _current(self):
110 | """Return a hash object for the current state.
111 |
112 | To be used only internally with digest() and hexdigest().
113 | """
114 | # h = self.outer.copy()
115 | # CP sha1 has no copy, have to access protected members.
116 | h = SHA1()
117 | h._h = tuple(self.outer._h) # force copy
118 | h._unprocessed = self.outer._unprocessed
119 | h._msg_byte_len = self.outer._msg_byte_len
120 | h.update(self.inner.digest())
121 | return h
122 |
123 | def digest(self):
124 | """Return the hash value of this hashing object.
125 |
126 | This returns a string containing 8-bit data. The object is
127 | not altered in any way by this function; you can continue
128 | updating the object after calling this function.
129 | """
130 | h = self._current()
131 | return h.digest()
132 |
133 | def hexdigest(self):
134 | """Like digest(), but returns a string of hexadecimal digits instead.
135 | """
136 | h = self._current()
137 | return h.hexdigest()
138 |
139 |
140 | def new(key, msg=None, digestmod=None):
141 | """Create a new hashing object and return it.
142 |
143 | key: The starting key for the hash.
144 | msg: if available, will immediately be hashed into the object's starting
145 | state.
146 |
147 | You can now feed arbitrary strings into the object using its update()
148 | method, and can ask for the hash value at any time by calling its digest()
149 | method.
150 | """
151 | return HMAC(key, msg, digestmod)
152 |
153 |
154 | if __name__ == '__main__':
155 | # https://www.di-mgt.com.au/sha_testvectors.html
156 | s = HMAC(binascii.unhexlify("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"), b"Hi There", SHA1)
157 | print(s.digest().hex()) # 0xb617318655057264e28bc0b6fb378c8ef146be00
158 |
--------------------------------------------------------------------------------
/src/lib/base32.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python3
2 |
3 | """RFC 3548: Base16, Base32, Base64 Data Encodings"""
4 |
5 | # Modified 04-Oct-1995 by Jack Jansen to use binascii module
6 | # Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support
7 | # Modified 22-May-2007 by Guido van Rossum to use bytes everywhere
8 |
9 | import struct
10 | import binascii
11 |
12 |
13 | __all__ = ['b32encode', 'b32decode']
14 |
15 |
16 | bytes_types = (bytes, bytearray) # Types acceptable as binary data
17 |
18 |
19 | def _bytes_from_decode_data(s):
20 | if isinstance(s, str):
21 | try:
22 | return s.encode('ascii')
23 | # except UnicodeEncodeError:
24 | except Exception:
25 | raise ValueError('string argument should contain only ASCII characters')
26 | elif isinstance(s, bytes_types):
27 | return s
28 | else:
29 | raise TypeError("argument should be bytes or ASCII string, not %s" % s.__class__.__name__)
30 |
31 |
32 | # Base32 encoding/decoding in Python
33 | _b32alphabet = {
34 | 0: b'A', 9: b'J', 18: b'S', 27: b'3',
35 | 1: b'B', 10: b'K', 19: b'T', 28: b'4',
36 | 2: b'C', 11: b'L', 20: b'U', 29: b'5',
37 | 3: b'D', 12: b'M', 21: b'V', 30: b'6',
38 | 4: b'E', 13: b'N', 22: b'W', 31: b'7',
39 | 5: b'F', 14: b'O', 23: b'X',
40 | 6: b'G', 15: b'P', 24: b'Y',
41 | 7: b'H', 16: b'Q', 25: b'Z',
42 | 8: b'I', 17: b'R', 26: b'2',
43 | }
44 |
45 | _b32tab = [v[0] for k, v in sorted(_b32alphabet.items())]
46 | _b32rev = dict([(v[0], k) for k, v in _b32alphabet.items()])
47 |
48 |
49 | def b32encode(s):
50 | """Encode a byte string using Base32.
51 |
52 | s is the byte string to encode. The encoded byte string is returned.
53 | """
54 | if not isinstance(s, bytes_types):
55 | raise TypeError("expected bytes, not %s" % s.__class__.__name__)
56 | quanta, leftover = divmod(len(s), 5)
57 | # Pad the last quantum with zero bits if necessary
58 | if leftover:
59 | s = s + bytes(5 - leftover) # Don't use += !
60 | quanta += 1
61 | encoded = bytearray()
62 | for i in range(quanta):
63 | # c1 and c2 are 16 bits wide, c3 is 8 bits wide. The intent of this
64 | # code is to process the 40 bits in units of 5 bits. So we take the 1
65 | # leftover bit of c1 and tack it onto c2. Then we take the 2 leftover
66 | # bits of c2 and tack them onto c3. The shifts and masks are intended
67 | # to give us values of exactly 5 bits in width.
68 | c1, c2, c3 = struct.unpack('!HHB', s[i*5:(i+1)*5])
69 | c2 += (c1 & 1) << 16 # 17 bits wide
70 | c3 += (c2 & 3) << 8 # 10 bits wide
71 | encoded += bytes([_b32tab[c1 >> 11], # bits 1 - 5
72 | _b32tab[(c1 >> 6) & 0x1f], # bits 6 - 10
73 | _b32tab[(c1 >> 1) & 0x1f], # bits 11 - 15
74 | _b32tab[c2 >> 12], # bits 16 - 20 (1 - 5)
75 | _b32tab[(c2 >> 7) & 0x1f], # bits 21 - 25 (6 - 10)
76 | _b32tab[(c2 >> 2) & 0x1f], # bits 26 - 30 (11 - 15)
77 | _b32tab[c3 >> 5], # bits 31 - 35 (1 - 5)
78 | _b32tab[c3 & 0x1f], # bits 36 - 40 (1 - 5)
79 | ])
80 | # Adjust for any leftover partial quanta
81 | if leftover == 1:
82 | encoded = encoded[:-6] + b'======'
83 | elif leftover == 2:
84 | encoded = encoded[:-4] + b'===='
85 | elif leftover == 3:
86 | encoded = encoded[:-3] + b'==='
87 | elif leftover == 4:
88 | encoded = encoded[:-1] + b'='
89 | return bytes(encoded)
90 |
91 |
92 | def b32decode(s, casefold=False, map01=None):
93 | """Decode a Base32 encoded byte string.
94 |
95 | s is the byte string to decode. Optional casefold is a flag
96 | specifying whether a lowercase alphabet is acceptable as input.
97 | For security purposes, the default is False.
98 |
99 | RFC 3548 allows for optional mapping of the digit 0 (zero) to the
100 | letter O (oh), and for optional mapping of the digit 1 (one) to
101 | either the letter I (eye) or letter L (el). The optional argument
102 | map01 when not None, specifies which letter the digit 1 should be
103 | mapped to (when map01 is not None, the digit 0 is always mapped to
104 | the letter O). For security purposes the default is None, so that
105 | 0 and 1 are not allowed in the input.
106 |
107 | The decoded byte string is returned. binascii.Error is raised if
108 | the input is incorrectly padded or if there are non-alphabet
109 | characters present in the input.
110 | """
111 | s = _bytes_from_decode_data(s)
112 | quanta, leftover = divmod(len(s), 8)
113 | if leftover:
114 | raise binascii.Error('Incorrect padding')
115 | # Handle section 2.4 zero and one mapping. The flag map01 will be either
116 | # False, or the character to map the digit 1 (one) to. It should be
117 | # either L (el) or I (eye).
118 | if map01 is not None:
119 | map01 = _bytes_from_decode_data(map01)
120 | assert len(map01) == 1, repr(map01)
121 | s = s.translate(bytes.maketrans(b'01', b'O' + map01))
122 | if casefold:
123 | s = s.upper()
124 | # Strip off pad characters from the right. We need to count the pad
125 | # characters because this will tell us how many null bytes to remove from
126 | # the end of the decoded string.
127 | padchars = s.find(b'=')
128 | if padchars > 0:
129 | padchars = len(s) - padchars
130 | s = s[:-padchars]
131 | else:
132 | padchars = 0
133 |
134 | # Now decode the full quanta
135 | parts = []
136 | acc = 0
137 | shift = 35
138 | for c in s:
139 | val = _b32rev.get(c)
140 | if val is None:
141 | raise binascii.Error('Non-base32 digit found')
142 | acc += _b32rev[c] << shift
143 | shift -= 5
144 | if shift < 0:
145 | parts.append(binascii.unhexlify(bytes('%010x' % acc, "ascii")))
146 | acc = 0
147 | shift = 35
148 | # Process the last, partial quanta
149 | last = binascii.unhexlify(bytes('%010x' % acc, "ascii"))
150 | if padchars == 0:
151 | last = b'' # No characters
152 | elif padchars == 1:
153 | last = last[:-1]
154 | elif padchars == 3:
155 | last = last[:-2]
156 | elif padchars == 4:
157 | last = last[:-3]
158 | elif padchars == 6:
159 | last = last[:-4]
160 | else:
161 | raise binascii.Error('Incorrect padding')
162 | parts.append(last)
163 | return b''.join(parts)
164 |
--------------------------------------------------------------------------------
/src/lib/adafruit_hid/keyboard_layout_fr.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | """
6 | `adafruit_hid.keyboard_layout_us.KeyboardLayoutUS`
7 | =======================================================
8 |
9 | * Author(s): Dan Halbert, maditnerd, AngainorDev
10 | """
11 |
12 | from .keyboard_layout import KeyboardLayout
13 |
14 |
15 | class KeyboardLayoutFR(KeyboardLayout):
16 | """Map ASCII characters to appropriate keypresses on a standard FR PC keyboard.
17 | From https://github.com/adafruit/Adafruit_CircuitPython_HID/pull/54
18 | Non-ASCII characters and most control characters will raise an exception.
19 | """
20 |
21 | # The ASCII_TO_KEYCODE bytes object is used as a table to maps ASCII 0-127
22 | # to the corresponding # keycode on a US 104-key keyboard.
23 | # The user should not normally need to use this table,
24 | # but it is not marked as private.
25 | #
26 | # Because the table only goes to 127, we use the top bit of each byte (ox80) to indicate
27 | # that the shift key should be pressed. So any values 0x{8,9,a,b}* are shifted characters.
28 | #
29 | # The Python compiler will concatenate all these bytes literals into a single bytes object.
30 | # Micropython/CircuitPython will store the resulting bytes constant in flash memory
31 | # if it's in a .mpy file, so it doesn't use up valuable RAM.
32 | #
33 | # \x00 entries have no keyboard key and so won't be sent.
34 | ASCII_TO_KEYCODE = (
35 | b"\x00" # NUL
36 | b"\x00" # SOH
37 | b"\x00" # STX
38 | b"\x00" # ETX
39 | b"\x00" # EOT
40 | b"\x00" # ENQ
41 | b"\x00" # ACK
42 | b"\x00" # BEL \a
43 | b"\x2a" # BS BACKSPACE \b (called DELETE in the usb.org document)
44 | b"\x2b" # TAB \t
45 | b"\x28" # LF \n (called Return or ENTER in the usb.org document)
46 | b"\x00" # VT \v
47 | b"\x00" # FF \f
48 | b"\x00" # CR \r
49 | b"\x00" # SO
50 | b"\x00" # SI
51 | b"\x00" # DLE
52 | b"\x00" # DC1
53 | b"\x00" # DC2
54 | b"\x00" # DC3
55 | b"\x00" # DC4
56 | b"\x00" # NAK
57 | b"\x00" # SYN
58 | b"\x00" # ETB
59 | b"\x00" # CAN
60 | b"\x00" # EM
61 | b"\x00" # SUB
62 | b"\x29" # ESC
63 | b"\x00" # FS
64 | b"\x00" # GS
65 | b"\x00" # RS
66 | b"\x00" # US
67 | b"\x2c" # SPACE
68 | b"\x38" # ! x1e|SHIFT_FLAG (shift 1)
69 | b"\x20" # " x34|SHIFT_FLAG (shift ')
70 | b"\xe0" # # x20|SHIFT_FLAG (shift 3)
71 | b"\x30" # $ x21|SHIFT_FLAG (shift 4)
72 | b"\xb4" # % x22|SHIFT_FLAG (shift 5)
73 | b"\x1e" # & x24|SHIFT_FLAG (shift 7)
74 | b"\x21" # '
75 | b"\x22" # ( x26|SHIFT_FLAG (shift 9)
76 | b"\x2d" # ) x27|SHIFT_FLAG (shift 0)
77 | b"\x31" # * x25|SHIFT_FLAG (shift 8)
78 | b"\xae" # + x2e|SHIFT_FLAG (shift =)
79 | b"\x10" # ,
80 | b"\x23" # -
81 | b"\xb6" # .
82 | b"\xb7" # /
83 | b"\xa7" # 0
84 | b"\x9e" # 1
85 | b"\x9f" # 2
86 | b"\xa0" # 3
87 | b"\xa1" # 4
88 | b"\xa2" # 5
89 | b"\xa3" # 6
90 | b"\xa4" # 7
91 | b"\xa5" # 8
92 | b"\xa6" # 9
93 | b"\x37" # : x33|SHIFT_FLAG (shift ;)
94 | b"\x36" # ;
95 | b"\x64" # < x36|SHIFT_FLAG (shift ,)
96 | b"\x2e" # =
97 | b"\x03" # > x37|SHIFT_FLAG (shift .)
98 | b"\x90" # ? x38|SHIFT_FLAG (shift /)
99 | b"\x27" # @ x1f|SHIFT_FLAG (shift 2)
100 | b"\x94" # A x04|SHIFT_FLAG (shift a)
101 | b"\x85" # B x05|SHIFT_FLAG (etc.)
102 | b"\x86" # C x06|SHIFT_FLAG
103 | b"\x87" # D x07|SHIFT_FLAG
104 | b"\x88" # E x08|SHIFT_FLAG
105 | b"\x89" # F x09|SHIFT_FLAG
106 | b"\x8a" # G x0a|SHIFT_FLAG
107 | b"\x8b" # H x0b|SHIFT_FLAG
108 | b"\x8c" # I x0c|SHIFT_FLAG
109 | b"\x8d" # J x0d|SHIFT_FLAG
110 | b"\x8e" # K x0e|SHIFT_FLAG
111 | b"\x8f" # L x0f|SHIFT_FLAG
112 | b"\xb3" # M x10|SHIFT_FLAG
113 | b"\x91" # N x11|SHIFT_FLAG
114 | b"\x92" # O x12|SHIFT_FLAG
115 | b"\x93" # P x13|SHIFT_FLAG
116 | b"\x84" # Q x14|SHIFT_FLAG
117 | b"\x95" # R x15|SHIFT_FLAG
118 | b"\x96" # S x16|SHIFT_FLAG
119 | b"\x97" # T x17|SHIFT_FLAG
120 | b"\x98" # U x18|SHIFT_FLAG
121 | b"\x99" # V x19|SHIFT_FLAG
122 | b"\x9d" # W x1a|SHIFT_FLAG
123 | b"\x9b" # X x1b|SHIFT_FLAG
124 | b"\x9c" # Y x1c|SHIFT_FLAG
125 | b"\x9a" # Z x1d|SHIFT_FLAG
126 | b"\x22" # [
127 | b"\x25" # \ backslash
128 | b"\x2d" # ]
129 | b"\x26" # ^ x23|SHIFT_FLAG (shift 6)
130 | b"\x25" # _ x2d|SHIFT_FLAG (shift -)
131 | b"\x24" # `
132 | b"\x14" # a
133 | b"\x05" # b
134 | b"\x06" # c
135 | b"\x07" # d
136 | b"\x08" # e
137 | b"\x09" # f
138 | b"\x0a" # g
139 | b"\x0b" # h
140 | b"\x0c" # i
141 | b"\x0d" # j
142 | b"\x0e" # k
143 | b"\x0f" # l
144 | b"\x33" # m
145 | b"\x11" # n
146 | b"\x12" # o
147 | b"\x13" # p
148 | b"\x04" # q
149 | b"\x15" # r
150 | b"\x16" # s
151 | b"\x17" # t
152 | b"\x18" # u
153 | b"\x19" # v
154 | b"\x1d" # w
155 | b"\x1b" # x
156 | b"\x1c" # y
157 | b"\x1a" # z
158 | b"\x21" # { x2f|SHIFT_FLAG (shift [)
159 | b"\x23" # | x31|SHIFT_FLAG (shift \)
160 | b"\x2e" # } x30|SHIFT_FLAG (shift ])
161 | b"\x1f" # ~ x35|SHIFT_FLAG (shift `)
162 | b"\x4c" # DEL DELETE (called Forward Delete in usb.org document)
163 | )
164 |
165 | NEED_ALTGR = "~{[|`\\^@]}€"
166 |
167 | def _above128charval_to_keycode(self, char_val):
168 | """Return keycode for above 128 ascii codes.
169 |
170 | Since the values are sparse, this may be more space efficient than bloating the table above
171 | or adding a dict.
172 |
173 | :param char_val: ascii char value
174 | :return: keycode, with modifiers if needed
175 | """
176 | if char_val == 224: # à
177 | keycode = 0x27
178 | elif char_val == 231: # ç
179 | keycode = 0x26
180 | elif char_val == 232: # è
181 | keycode = 0x24
182 | elif char_val == 233: # é
183 | keycode = 0x1f
184 | elif char_val == 249: # ù
185 | keycode = 0x34
186 | elif char_val == 8364: # €
187 | keycode = 0x08 # altgr will be added thanks to NEED_ALTGR
188 | elif char_val == 176: # °
189 | keycode = 0xad
190 | # TODO: add missing ÀÈÉÙ
191 | else:
192 | raise ValueError("Not an ASCII character.")
193 |
194 | return keycode
195 |
--------------------------------------------------------------------------------
/src/lib/picoth.py:
--------------------------------------------------------------------------------
1 | """
2 | Main Picoth manager
3 | """
4 |
5 |
6 | import time
7 | import busio
8 | import board
9 | from adafruit_ds3231 import DS3231
10 |
11 | from totp import TOTP
12 | from rgbkeypad import RgbKeypad
13 |
14 | import usb_hid
15 | from adafruit_hid.keyboard import Keyboard
16 |
17 | from helpers import hex_to_rgb, ts_to_unix, NUMPAD_TO_TOUCH, button_to_numpad
18 | import pico_dio
19 |
20 |
21 | class Picoth(object):
22 |
23 | def __init__(self, config):
24 | """
25 | Init and binds the H/W
26 | """
27 | # Pimoroni's RGB Keypad - Default wiring
28 | self.KEYPAD = RgbKeypad()
29 | self.KEYS = self.KEYPAD.keys
30 | # DS3231 module, i2c1, SCL=GP11 and SDA=GP10
31 | i2c = busio.I2C(board.GP11, board.GP10)
32 | self.DS = DS3231(i2c)
33 | print(self.DS.datetime) # Just to check time at boot when dev
34 |
35 | self.CONFIG = config
36 |
37 | # USB HID
38 | keyboard = Keyboard(usb_hid.devices)
39 | if self.CONFIG.get("layout", "us") == "fr":
40 | # More to come
41 | from adafruit_hid.keyboard_layout_fr import KeyboardLayoutFR
42 | self.LAYOUT = KeyboardLayoutFR(keyboard)
43 | else:
44 | # Default US layout
45 | from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
46 | self.LAYOUT = KeyboardLayoutUS(keyboard)
47 |
48 | # Pico display
49 | self.DISPLAY = pico_dio.get_display()
50 | self.SCREENS = dict()
51 | self.SCREENS["splash"] = pico_dio.get_splash()
52 |
53 | self.DISPLAY.show(self.SCREENS["splash"])
54 |
55 | self.UPDATE_INDEX = 0
56 | self.LOCKED = False
57 | self.LAST_CODE = ""
58 | self.OTP = None
59 | self.MODE = 0
60 | self.PAGE = 0
61 | self.INDEX = None
62 | self.LAST_COUNTER = 0 # time // 30, OTP counter
63 |
64 | self.SCREENS["OTP"] = pico_dio.get_otp_group()
65 | self.SCREENS["PAGE"] = pico_dio.get_page_group()
66 |
67 | self.display_page(self.PAGE)
68 |
69 | for key in self.KEYS:
70 | @self.KEYPAD.on_press(key)
71 | def press_handler(a_key):
72 | self.handle_numpad(button_to_numpad(a_key.number))
73 |
74 | def run(self):
75 | self.KEYS[15].set_led(0, 100, 100) # heartbeat
76 | time_last_fired = [0, 0]
77 | while True:
78 | self.KEYPAD.update()
79 | tm = time.monotonic()
80 | if tm - time_last_fired[1] > 0.1:
81 | self.update()
82 | time_last_fired[1] = tm
83 | if tm - time_last_fired[0] > 1.0:
84 | # print("update", time_last_fired)
85 | time_last_fired[0] = tm
86 | self.KEYS[15].toggle_led()
87 |
88 | def display_page(self, page):
89 | self.OTP = None
90 | self.LAST_COUNTER = 0
91 | self.PAGE = page
92 | self.INDEX = None
93 | self.KEYPAD.clear_all()
94 | for i, item in enumerate(self.CONFIG["pages"][self.PAGE]["keys"]):
95 | r, g, b = hex_to_rgb(item[1])
96 | self.KEYS[NUMPAD_TO_TOUCH[i]].set_led(r, g, b)
97 | # TODO: generalize this key behaviour
98 | self.KEYS[15].set_led(0, 100, 100) # cyan = heartbeat
99 | self.DISPLAY.auto_refresh = False
100 | # TODO: auto center
101 | self.SCREENS["PAGE"][1].text = " Mode {}".format(self.CONFIG["pages"][self.PAGE]["type"])
102 | self.SCREENS["PAGE"][2].text = "Page {}".format(self.PAGE)
103 | self.SCREENS["PAGE"][3].text = self.CONFIG["pages"][self.PAGE]["name"]
104 | self.DISPLAY.show(self.SCREENS["PAGE"][0])
105 | self.DISPLAY.auto_refresh = True
106 |
107 | def handle_numpad(self, numpad):
108 | # numpad int or char
109 | # print("handle numpad", PAGE, numpad)
110 | if self.MODE == 0:
111 | # User mode
112 | if type(numpad) == int:
113 | key = self.CONFIG["pages"][self.PAGE]["keys"][numpad][2]
114 | if key != '':
115 | # dup code
116 | for i, item in enumerate(self.CONFIG["pages"][self.PAGE]["keys"]):
117 | r, g, b = hex_to_rgb(item[1])
118 | self.KEYPAD.set_led(NUMPAD_TO_TOUCH[i], r, g, b)
119 | self.INDEX = numpad
120 | if self.CONFIG["pages"][self.PAGE]["type"] == "TOTP":
121 | self.OTP = TOTP(key)
122 | self.KEYS[15].set_led(0, 100, 0) # green = enter
123 | self.LAST_CODE = ""
124 | self.LAST_COUNTER = 0
125 | self.DISPLAY.auto_refresh = False
126 | # TODO: auto center
127 | self.SCREENS["OTP"][2].text = self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][0]
128 | self.SCREENS["OTP"][2].color = int(self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][1], 16)
129 | self.SCREENS["OTP"][1].text = "------"
130 | self.DISPLAY.show(self.SCREENS["OTP"][0])
131 | self.DISPLAY.auto_refresh = True
132 | elif self.CONFIG["pages"][self.PAGE]["type"] == "KEYS":
133 | self.KEYS[15].set_led(0, 100, 0) # green = enter
134 | self.DISPLAY.auto_refresh = False
135 | # TODO: auto center
136 | self.SCREENS["OTP"][2].text = self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][0]
137 | self.SCREENS["OTP"][2].color = int(self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][1], 16)
138 | self.SCREENS["OTP"][1].text = ""
139 | self.SCREENS["OTP"][3].progress = 1.0
140 | self.DISPLAY.show(self.SCREENS["OTP"][0])
141 | self.DISPLAY.auto_refresh = True
142 | else:
143 | self.OTP = None
144 | elif numpad == "N":
145 | self.PAGE += 1
146 | if self.PAGE >= len(self.CONFIG["pages"]):
147 | self.PAGE = 0
148 | self.display_page(self.PAGE)
149 | elif numpad == "P":
150 | self.PAGE -= 1
151 | if self.PAGE < 0:
152 | self.PAGE = len(self.CONFIG["pages"]) - 1
153 | self.display_page(self.PAGE)
154 | elif numpad == "E":
155 | if self.OTP and self.CONFIG["pages"][self.PAGE]["type"] == "TOTP":
156 | self.LAYOUT.write(self.LAST_CODE)
157 | if self.CONFIG["pages"][self.PAGE]["type"] == "KEYS":
158 | self.LAYOUT.write(self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][2])
159 |
160 | def update(self):
161 | self.UPDATE_INDEX += 1
162 | if self.UPDATE_INDEX > 100:
163 | self.UPDATE_INDEX = 0
164 | if self.LOCKED:
165 | return
166 | if self.MODE == 0:
167 | # User mode
168 | if self.CONFIG["pages"][self.PAGE]["type"] == "TOTP":
169 | self.update_totp()
170 | if self.CONFIG["pages"][self.PAGE]["type"] == "KEYS":
171 | self.update_keys()
172 |
173 | def update_totp(self):
174 | if self.OTP is None:
175 | # print("No OTP")
176 | return
177 | try:
178 | color = self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][1]
179 | r, g, b = hex_to_rgb(color)
180 | if self.UPDATE_INDEX % 3 == 0:
181 | self.KEYPAD.set_led(NUMPAD_TO_TOUCH[self.INDEX], 0, 0, 0)
182 | else:
183 | self.KEYPAD.set_led(NUMPAD_TO_TOUCH[self.INDEX], r, g, b)
184 | # TODO: offset in config
185 | t = ts_to_unix(self.DS.datetime, offset=self.CONFIG["time_offset"])
186 | color = 0x00fa00
187 | left, counter = self.OTP.time_left(t)
188 | if left < 10:
189 | color = 0xfafa00
190 | if left < 5:
191 | color = 0xfa0000
192 | # Todo: only update if width change?
193 | # self.SCREENS["OTP"][3].width = left * 240 // 30
194 | # Have to access the protected member since the lib does only allow color definition at init time
195 | self.SCREENS["OTP"][3]._palette[2] = color
196 | self.SCREENS["OTP"][3].progress = left / 30
197 | if counter != self.LAST_COUNTER and self.OTP:
198 | # Only update code on screen screen (slow) if changed
199 | self.LAST_COUNTER = counter
200 | code = self.OTP.totpt(t) # 0.22sec!
201 | self.LAST_CODE = code
202 | self.SCREENS["OTP"][1].text = code
203 | if self.OTP:
204 | # Debug
205 | if self.UPDATE_INDEX % 10 == 0:
206 | print(self.LAST_CODE)
207 |
208 | except Exception as e:
209 | print(e)
210 | pass
211 |
212 | def update_keys(self):
213 | try:
214 | color = self.CONFIG["pages"][self.PAGE]["keys"][self.INDEX][1]
215 | # TODO1: move hex conversion in KEYPAD
216 | # TODO2: refactor/factorize common behaviour of the blinking active keys
217 | r, g, b = hex_to_rgb(color)
218 | if self.UPDATE_INDEX % 3 == 0:
219 | self.KEYPAD.set_led(NUMPAD_TO_TOUCH[self.INDEX], 0, 0, 0)
220 | else:
221 | self.KEYPAD.set_led(NUMPAD_TO_TOUCH[self.INDEX], r, g, b)
222 | except Exception as e:
223 | print(e)
224 | pass
225 |
--------------------------------------------------------------------------------
/src/lib/rgbkeypad.py:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: 2021 Sandy Macdonald
2 | #
3 | # SPDX-License-Identifier: MIT
4 |
5 | """
6 | `Pimoroni's Pico RGB Keypad CircuitPython library`
7 | ====================================================
8 |
9 | CircuitPython driver for the Pimoroni Pico RGB Keypad.
10 | From Sandy Macdonald's Keybow 2040 library.
11 |
12 | Drop the rgbkeypad.py file into your `lib` folder on your `CIRCUITPY` drive.
13 |
14 | * Author: Sandy Macdonald
15 | * Author: Angainor Dev
16 |
17 | Notes
18 | --------------------
19 |
20 | **Hardware:**
21 |
22 | * Pimoroni Pico RGB Keypad
23 | _
24 |
25 | **Software and Dependencies:**
26 |
27 | * Adafruit CircuitPython firmware for Raspberry Pi Pico:
28 | _
29 |
30 | * Adafruit Dotstar circuit python library
31 |
32 | """
33 |
34 | import time
35 | import board
36 | import busio
37 | from random import randint
38 | from digitalio import DigitalInOut, Direction, Pull
39 | from adafruit_bus_device.i2c_device import I2CDevice
40 | import adafruit_dotstar
41 |
42 |
43 | NUM_KEYS = 16
44 |
45 | # These are the 16 switches on keypad, with their value.
46 | _PINS = [2**i for i in range(NUM_KEYS)]
47 |
48 |
49 | class RgbKeypad(object):
50 | """
51 | Represents a keypad and hence a set of Key instances with
52 | associated LEDs and key behaviours.
53 |
54 |
55 | """
56 | def __init__(self):
57 | self.pins = _PINS
58 | self.cs = DigitalInOut(board.GP17)
59 | self.cs.direction = Direction.OUTPUT
60 | self.cs.value = 0
61 |
62 | self.pixels = adafruit_dotstar.DotStar(board.GP18, board.GP19, 16, brightness=0.1, auto_write=True)
63 |
64 | i2c = busio.I2C(board.GP5, board.GP4)
65 | self.expander = I2CDevice(i2c, 0x20)
66 |
67 | self.keys = []
68 | self.time_of_last_press = time.monotonic()
69 | self.time_since_last_press = None
70 | self.led_sleep_enabled = False
71 | self.led_sleep_time = 60
72 | self.sleeping = False
73 | self.was_asleep = False
74 | self.last_led_states = None
75 | # self.rotation = 0
76 | self.full_state = [0]
77 | for i in range(len(self.pins)):
78 | _key = Key(i, self.pins[i], self.pixels, self.full_state)
79 | self.keys.append(_key)
80 |
81 | def update(self):
82 | # Call this in each iteration of your while loop to update
83 | # to update everything's state, e.g. `keybow.update()`
84 |
85 | with self.expander:
86 | self.expander.write(bytes([0x0]))
87 | result = bytearray(2)
88 | self.expander.readinto(result)
89 | self.full_state[0] = result[0] | result[1] << 8
90 | for _key in self.keys:
91 | _key.update()
92 |
93 | # Used to work out the sleep behaviour, by keeping track
94 | # of the time of the last key press.
95 | if self.any_pressed():
96 | self.time_of_last_press = time.monotonic()
97 | self.sleeping = False
98 |
99 | self.time_since_last_press = time.monotonic() - self.time_of_last_press
100 |
101 | # If LED sleep is enabled, but not engaged, check if enough time
102 | # has elapsed to engage sleep. If engaged, record the state of the
103 | # LEDs, so it can be restored on wake.
104 | if self.led_sleep_enabled and not self.sleeping:
105 | if time.monotonic() - self.time_of_last_press > self.led_sleep_time:
106 | self.sleeping = True
107 | self.last_led_states = [k.rgb if k.lit else [0, 0, 0] for k in self.keys]
108 | self.set_all(0, 0, 0)
109 | self.was_asleep = True
110 |
111 | # If it was sleeping, but is no longer, then restore LED states.
112 | if not self.sleeping and self.was_asleep:
113 | for k in range(len(self.keys)):
114 | self.keys[k].set_led(*self.last_led_states[k])
115 | self.was_asleep = False
116 |
117 | def set_led(self, number, r, g, b):
118 | # Set an individual key's LED to an RGB value by its number.
119 |
120 | self.keys[number].set_led(r, g, b)
121 |
122 | def set_all(self, r, g, b):
123 | # Set all of Keybow's LEDs to an RGB value.
124 |
125 | if not self.sleeping:
126 | for _key in self.keys:
127 | _key.set_led(r, g, b)
128 | else:
129 | for _key in self.keys:
130 | _key.led_off()
131 |
132 | def random_colors(self, x_range=3, y_range=3):
133 | for x in range(x_range):
134 | for y in range(y_range):
135 | i = x * 4 + y
136 | self.keys[i].set_led(randint(0, 255), randint(0, 255), randint(0, 255))
137 |
138 | def clear_all(self):
139 | for _key in self.keys:
140 | _key.led_off()
141 |
142 | def get_states(self):
143 | # Returns a Boolean list of Keybow's key states
144 | # (0=not pressed, 1=pressed).
145 |
146 | _states = [_key.state for _key in self.keys]
147 | return _states
148 |
149 | def get_pressed(self):
150 | # Returns a list of key numbers currently pressed.
151 |
152 | _pressed = [_key.number for _key in self.keys if _key.state]
153 | return _pressed
154 |
155 | def any_pressed(self):
156 | # Returns True if any key is pressed, False if none are pressed.
157 |
158 | if any(self.get_states()):
159 | return True
160 | else:
161 | return False
162 |
163 | def none_pressed(self):
164 | # Returns True if none of the keys are pressed, False is any key
165 | # is pressed.
166 |
167 | if not any(self.get_states()):
168 | return True
169 | else:
170 | return False
171 |
172 | @staticmethod
173 | def on_press(_key, handler=None):
174 | # Attaches a press function to a key, via a decorator. This is stored as
175 | # `key.press_function` in the key's attributes, and run if necessary
176 | # as part of the key's update function (and hence Keybow's update
177 | # function). It can be attached as follows:
178 |
179 | # @keybow.on_press(key)
180 | # def press_handler(key, pressed):
181 | # if pressed:
182 | # do something
183 | # else:
184 | # do something else
185 |
186 | if _key is None:
187 | return
188 |
189 | def attach_handler(a_handler):
190 | _key.press_function = a_handler
191 |
192 | if handler is not None:
193 | attach_handler(handler)
194 | else:
195 | return attach_handler
196 |
197 | @staticmethod
198 | def on_release(_key, handler=None):
199 | # Attaches a release function to a key, via a decorator. This is stored
200 | # as `key.release_function` in the key's attributes, and run if
201 | # necessary as part of the key's update function (and hence Keybow's
202 | # update function). It can be attached as follows:
203 |
204 | # @keybow.on_release(key)
205 | # def release_handler(key):
206 | # do something
207 |
208 | if _key is None:
209 | return
210 |
211 | def attach_handler(a_handler):
212 | _key.release_function = a_handler
213 |
214 | if handler is not None:
215 | attach_handler(handler)
216 | else:
217 | return attach_handler
218 |
219 | @staticmethod
220 | def on_hold(_key, handler=None):
221 | # Attaches a hold unction to a key, via a decorator. This is stored as
222 | # `key.hold_function` in the key's attributes, and run if necessary
223 | # as part of the key's update function (and hence Keybow's update
224 | # function). It can be attached as follows:
225 |
226 | # @keybow.on_hold(key)
227 | # def hold_handler(key):
228 | # do something
229 |
230 | if _key is None:
231 | return
232 |
233 | def attach_handler(a_handler):
234 | _key.hold_function = a_handler
235 |
236 | if handler is not None:
237 | attach_handler(handler)
238 | else:
239 | return attach_handler
240 |
241 |
242 | class Key:
243 | """
244 | Represents a key on Keypad, with associated value and
245 | LED behaviours.
246 |
247 | :param number: the key number (0-15) to associate with the key
248 | :param mask: the value when pressed (2**key number)
249 | :param pixels: the dotstar instance for the LEDs
250 | :param full_state: a list of the keypad full keys state (int)
251 | """
252 | def __init__(self, number, mask, pixels, full_state):
253 | self.mask = mask
254 | self.number = number
255 | self.full_state = full_state
256 |
257 | self.state = 0
258 | self.pressed = 0
259 | self.last_state = None
260 | self.time_of_last_press = time.monotonic()
261 | self.time_since_last_press = None
262 | self.time_held_for = 0
263 | self.held = False
264 | self.hold_time = 0.75
265 | self.modifier = False
266 | self.rgb = [0, 0, 0]
267 | self.lit = False
268 | self.xy = self.get_xy()
269 | self.x, self.y = self.xy
270 | self.pixels = pixels
271 | self.led_off()
272 | self.press_function = None
273 | self.release_function = None
274 | self.hold_function = None
275 | self.press_func_fired = False
276 | self.hold_func_fired = False
277 | self.debounce = 0.125
278 | self.key_locked = False
279 |
280 | def get_state(self):
281 | # Returns the state of the key (0=not pressed, 1=pressed).
282 | res = 0 if self.full_state[0] & self.mask else 1
283 | return res
284 |
285 | def update(self):
286 | # Updates the state of the key and updates all of its
287 | # attributes.
288 |
289 | self.time_since_last_press = time.monotonic() - self.time_of_last_press
290 |
291 | # Keys get locked during the debounce time.
292 | if self.time_since_last_press < self.debounce:
293 | self.key_locked = True
294 | else:
295 | self.key_locked = False
296 |
297 | self.state = self.get_state()
298 | self.pressed = self.state
299 | update_time = time.monotonic()
300 |
301 | # If there's a `press_function` attached, then call it,
302 | # returning the key object and the pressed state.
303 | if self.press_function is not None and self.pressed and not self.press_func_fired and not self.key_locked:
304 | self.press_function(self)
305 | self.press_func_fired = True
306 | # time.sleep(0.05) # A little debounce
307 |
308 | # If the key has been pressed and releases, then call
309 | # the `release_function`, if one is attached.
310 | if not self.pressed and self.last_state:
311 | if self.release_function is not None:
312 | self.release_function(self)
313 | self.last_state = False
314 | self.press_func_fired = False
315 |
316 | if not self.pressed:
317 | self.time_held_for = 0
318 | self.last_state = False
319 |
320 | # If the key has just been pressed, then record the
321 | # `time_of_last_press`, and update last_state.
322 | elif self.pressed and not self.last_state:
323 | self.time_of_last_press = update_time
324 | self.last_state = True
325 |
326 | # If the key is pressed and held, then update the
327 | # `time_held_for` variable.
328 | elif self.pressed and self.last_state:
329 | self.time_held_for = update_time - self.time_of_last_press
330 | self.last_state = True
331 |
332 | # If the `hold_time` theshold is crossed, then call the
333 | # `hold_function` if one is attached. The `hold_func_fired`
334 | # ensures that the function is only called once.
335 | if self.time_held_for > self.hold_time:
336 | self.held = True
337 | if self.hold_function is not None and not self.hold_func_fired:
338 | self.hold_function(self)
339 | self.hold_func_fired = True
340 | else:
341 | self.held = False
342 | self.hold_func_fired = False
343 |
344 | def get_xy(self):
345 | # Returns the x/y coordinate of a key from 0,0 to 3,3.
346 |
347 | return number_to_xy(self.number)
348 |
349 | def get_number(self):
350 | # Returns the key number, from 0 to 15.
351 |
352 | return xy_to_number(self.x, self.y)
353 |
354 | def is_modifier(self):
355 | # Designates a modifier key, so you can hold the modifier
356 | # and tap another key to trigger additional behaviours.
357 |
358 | if self.modifier:
359 | return True
360 | else:
361 | return False
362 |
363 | def set_led(self, r, g, b):
364 | # Set this key's LED to an RGB value.
365 |
366 | if [r, g, b] == [0, 0, 0]:
367 | self.lit = False
368 | else:
369 | self.lit = True
370 | self.rgb = [r, g, b]
371 |
372 | self.pixels[self.number] = (r, g, b)
373 |
374 | def led_on(self):
375 | # Turn the LED on, using its current RGB value.
376 |
377 | r, g, b = self.rgb
378 | self.set_led(r, g, b)
379 |
380 | def led_off(self):
381 | # Turn the LED off.
382 |
383 | self.set_led(0, 0, 0)
384 |
385 | def led_state(self, state):
386 | # Set the LED's state (0=off, 1=on)
387 |
388 | state = int(state)
389 |
390 | if state == 0:
391 | self.led_off()
392 | elif state == 1:
393 | self.led_on()
394 | else:
395 | return
396 |
397 | def toggle_led(self, rgb=None):
398 | # Toggle the LED's state, retaining its RGB value for when it's toggled
399 | # back on. Can also be passed an RGB tuple to set the colour as part of
400 | # the toggle.
401 |
402 | if rgb is not None:
403 | self.rgb = rgb
404 | if self.lit:
405 | self.led_off()
406 | else:
407 | self.led_on()
408 |
409 | def __str__(self):
410 | # When printed, show the key's state (0 or 1).
411 | return self.state
412 |
413 |
414 | def xy_to_number(x, y):
415 | # Convert an x/y coordinate to key number.
416 | return x + (y * 4)
417 |
418 |
419 | def number_to_xy(number):
420 | # Convert a number to an x/y coordinate.
421 | x = number % 4
422 | y = number // 4
423 | return x, y
424 |
425 |
426 | def hsv_to_rgb(h, s, v):
427 | # Convert an HSV (0.0-1.0) colour to RGB (0-255)
428 | rgb = [v, v, v] # s = 0, default value
429 |
430 | i = int(h * 6.0)
431 |
432 | f = (h * 6.) - i
433 | p, q, t = v * (1. - s), v * (1. - s * f), v * (1. - s * (1. - f))
434 | i %= 6
435 |
436 | if i == 0:
437 | rgb = [v, t, p]
438 | if i == 1:
439 | rgb = [q, v, p]
440 | if i == 2:
441 | rgb = [p, v, t]
442 | if i == 3:
443 | rgb = [p, q, v]
444 | if i == 4:
445 | rgb = [t, p, v]
446 | if i == 5:
447 | rgb = [v, p, q]
448 |
449 | rgb = tuple(int(c * 255) for c in rgb)
450 |
451 | return rgb
452 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------