Compare commits
10 Commits
4f1ea7d416
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d598def3fb | |||
| 57dda76a4e | |||
| 1cc9348d24 | |||
| 6724f8a4f3 | |||
| cb4dac9d6b | |||
| 44aaa083cc | |||
| 26ce2d1734 | |||
| d95d351bfd | |||
| 3a72054866 | |||
| ce61de7a6f |
@@ -116,3 +116,4 @@ dmypy.json
|
|||||||
|
|
||||||
|
|
||||||
.idea*
|
.idea*
|
||||||
|
config.json
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for software.
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your freedom to share and change the works.
|
||||||
|
By contrast, the GPL is intended to guarantee your freedom to share and change all versions.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender them.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run modified versions of the software inside them.
|
||||||
|
This is fundamentally incompatible with the aim of protecting users' freedom to change software.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
The GPL assures that patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of works.
|
||||||
|
"The Program" refers to any copyrightable work licensed under this License.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
The "source code" for a work means the preferred form for making modifications.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
All rights granted under this License are granted for the term of copyright on the Program,
|
||||||
|
and are irrevocable provided the stated conditions are met.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
No covered work shall be deemed part of an effective technological measure.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
You may convey verbatim copies of the Program's source code as you receive it.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
You may convey a work based on the Program under section 4, provided that you also meet all conditions:
|
||||||
|
a) The work must carry prominent notices stating that you modified it.
|
||||||
|
b) You must license the entire work under this License.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
You may convey a covered work in object code form under sections 4 and 5, provided that you also convey
|
||||||
|
the machine-readable Corresponding Source under the terms of this License.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
"Additional permissions" are terms that supplement the terms of this License by making exceptions.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
You may not propagate or modify a covered work except as expressly provided under this License.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
You are not required to accept this License in order to receive or run a copy of the Program.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
Each time you convey a covered work, the recipient automatically receives a license from the original licensors.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this License of the Program.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
If conditions are imposed on you that contradict the conditions of this License, they do not excuse you from the conditions.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
You have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER BE LIABLE TO YOU FOR DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest possible use to the public,
|
||||||
|
the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program:
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> Lucas Oskorep
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
========================================================================
|
||||||
|
|
||||||
|
Copyright (C) YOUR NAME 2024
|
||||||
|
|
||||||
|
Licensed under the GNU General Public License Version 3 (GPL-3.0-only).
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GPL v3 as published by the Free Software Foundation,
|
||||||
|
either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
See <https://www.gnu.org/licenses/> for full license text.
|
||||||
@@ -1,28 +1,90 @@
|
|||||||
```
|
```
|
||||||
_______________________
|
_______________________
|
||||||
| | | / \ | Macropad OS
|
| |MACROPAD OS!| / \ |
|
||||||
| |_________| \____/ |
|
| |____________| \____/ |
|
||||||
| |
|
|
||||||
| _____ _____ _____ |
|
| _____ _____ _____ |
|
||||||
||key1 | |key2 | |key3 ||
|
||key1 | |key2 | |key3 ||
|
||||||
||_____| |_____| |_____||
|
||_____| |_____| |_____||
|
||||||
| _____ _____ _____ |
|
| _____ _____ _____ | __ ___ __ ____ _____
|
||||||
||key4 | |key5 | |key6 ||
|
||key4 | |key5 | |key6 || / \/ /___ _______________ ____ ____ _____/ / / __ \/ ___/
|
||||||
||_____| |_____| |_____||
|
||_____| |_____| |_____|| / /|_/ / __ `/ ___/ ___/ __ \/ __ \/ __ `/ __ / / / / /\__ \
|
||||||
| _____ _____ _____ |
|
| _____ _____ _____ | / / / / /_/ / /__/ / / /_/ / /_/ / /_/ / /_/ / / /_/ /___/ /
|
||||||
||key7 | |key8 | |key9 ||
|
||key7 | |key8 | |key9 || /_/ /_/\__,_/\___/_/ \____/ .___/\__,_/\__,_/ \____//____/
|
||||||
||_____| |_____| |_____||
|
||_____| |_____| |_____|| /_/
|
||||||
| _____ _____ _____ |
|
| _____ _____ _____ |
|
||||||
||key11| |key13| |key12||
|
||key11| |key13| |key12||
|
||||||
||_____| |_____| |_____||
|
||_____| |_____| |_____||
|
||||||
|_______________________|
|
|_______________________|
|
||||||
```
|
```
|
||||||
|
|
||||||
What is Macropad OS?
|
# Macropad OS!
|
||||||
|
|
||||||
|
MacropadOS is a simple OS build to run on the [Adafruit Macropad](https://www.adafruit.com/product/5128). It's a very
|
||||||
|
basic GUI running in CircuitPython which allows users to navigate through apps, change settings, and more coming soon!
|
||||||
|
|
||||||
|
The OS itself is one part of the project though, this also includes a framework for building quick and simple apps in
|
||||||
|
CircuitPython using a framework very similar to Android apps with a very similar app-lifecycle (OnStart, OnResume, etc).
|
||||||
|
|
||||||
|
This repo contains everything you need to get started, from building the macropad itself to installing the software and
|
||||||
|
creating your own apps!
|
||||||
|
|
||||||
|
|
||||||
How to Build:
|
## Building the Macropad :
|
||||||
|
Pictures and a more complete build guide coming soon<sup>tm</sup>
|
||||||
|
|
||||||
|
See the 3d_printing_files folder for all the required .3mf model files!
|
||||||
|
If printing at home is not an option for you, you can reach out to https://craftcloud3d.com or similar services to
|
||||||
|
print the files for you!
|
||||||
|
|
||||||
|
|
||||||
How to Install:
|
#### Required Materials:
|
||||||
* Connect your complete
|
* 3d Printed
|
||||||
|
* 1x Main body
|
||||||
|
* 1x Key stabilizer
|
||||||
|
* 2x Peg Feet (can be TPU)
|
||||||
|
* 1x Support Bar (can be TPU)
|
||||||
|
* 12x Cherry MX Compatible KeyCaps
|
||||||
|
* Purchased
|
||||||
|
* 12x Cherry MX Compatible Keys
|
||||||
|
|
||||||
|
#### Optional (also 3d printed, but with TPU or another flex material):
|
||||||
|
* 1x Top Dampener
|
||||||
|
* 1x Bottom Dampener
|
||||||
|
|
||||||
|
#### Assembly:
|
||||||
|
1. Print all the required parts and any optional parts above.
|
||||||
|
2. (Optional)Take the dampeners if you are using them and place them into the main body with the pegs facing out of the body at the top and bottom.
|
||||||
|
3. Take the Adafruit Macropad and place it into the main body. It will snap into the second set of slots on the side of the body.
|
||||||
|
4. Snap the key stabilizer into the top slots on the body above the macropad itself. Feet of the stabilizer should touch the macropad itself.
|
||||||
|
5. Install the 12 keys into each slot
|
||||||
|
6. Add the Keycaps and press them onto each key
|
||||||
|
7. Flip macropad over and press in peg feet
|
||||||
|
8. Do the same for the top Support bar
|
||||||
|
9. You now have a fully assembled Macropad!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Installing Macropad OS:
|
||||||
|
Requirements:
|
||||||
|
* Macropad with the CircuitPython installed on it. See [docs](https://CircuitPython.org/board/adafruit_macropad_rp2040/)
|
||||||
|
* Python 3.5+
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
```bash
|
||||||
|
pip install circup # See the circup repository(https://github.com/adafruit/circup) for any issues installing circup
|
||||||
|
circup install -r requirements.txt
|
||||||
|
# Open copy_to_device.cmd and make the first line the letter of your circutpython drive
|
||||||
|
copy_to_device.cmd
|
||||||
|
# Alternatively just copy all of the files not in the 3d printing folder and copy them to the CircuitPython device
|
||||||
|
```
|
||||||
|
|
||||||
|
Congrats! MacropadOS is now installed and after all of the files are transferred to the device it will boot.
|
||||||
|
|
||||||
|
## Creating JSON Apps (Easy):
|
||||||
|
|
||||||
|
Documentation coming soon...
|
||||||
|
|
||||||
|
## Creating Python Apps (Advanced):
|
||||||
|
|
||||||
|
Documentation coming soon...
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
from macropad_os import AppRouter, SerialComms, Config
|
import os
|
||||||
|
|
||||||
|
from macropad_os import MacropadOS, SerialComms, Config, App
|
||||||
|
|
||||||
from adafruit_macropad import MacroPad
|
from adafruit_macropad import MacroPad
|
||||||
|
|
||||||
from macropad_apps.python import NumpadApp
|
|
||||||
|
|
||||||
macropad = MacroPad()
|
macropad = MacroPad()
|
||||||
|
|
||||||
default_config = Config("default_config.json").load()
|
default_config = Config("default_config.json").load()
|
||||||
config = Config("config.json").load(default_config)
|
config = Config("config.json").load(default_config)
|
||||||
|
|
||||||
ar = AppRouter(macropad, config, [
|
PYTHON_APP_FOLDER = "./macropad_apps/python"
|
||||||
NumpadApp(macropad, config),
|
JSON_APP_FOLDER = "./macropad_apps/json"
|
||||||
# Arrow Keys
|
|
||||||
# Script Runner
|
|
||||||
])
|
|
||||||
|
|
||||||
sc = SerialComms(config)
|
|
||||||
|
|
||||||
|
ar = MacropadOS(
|
||||||
|
macropad,
|
||||||
|
config,
|
||||||
|
python_apps=PYTHON_APP_FOLDER,
|
||||||
|
json_apps=JSON_APP_FOLDER
|
||||||
|
)
|
||||||
|
# sc = SerialComms(config)
|
||||||
# _thread.start_new_thread(sc.run, (sc))
|
# _thread.start_new_thread(sc.run, (sc))
|
||||||
|
|
||||||
ar.start()
|
ar.start()
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
{
|
|
||||||
"profileName" : "",
|
|
||||||
"sortOrder" : 99,
|
|
||||||
|
|
||||||
"key1" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key2" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key3" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key4" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key5" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key6" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key7" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key8" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key9" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key10" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key11" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
},
|
|
||||||
"key12" : {
|
|
||||||
"text" : [""],
|
|
||||||
"value" : [""]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,54 +1,53 @@
|
|||||||
{
|
{
|
||||||
"profileName" : "Gamepad",
|
"profileName" : "Gamepad Json",
|
||||||
"sortOrder" : 1,
|
"sortOrder" : 1,
|
||||||
|
|
||||||
"key1" : {
|
"key1" : {
|
||||||
"text" : ["ESC"],
|
"text" : "ESC",
|
||||||
"value" : ["ESCAPE"]
|
"values" : ["ESCAPE"]
|
||||||
},
|
},
|
||||||
"key2" : {
|
"key2" : {
|
||||||
"text" : ["Tab"],
|
"text" : "Tab",
|
||||||
"value" : ["TAB"]
|
"values" : ["TAB"]
|
||||||
},
|
},
|
||||||
"key3" : {
|
"key3" : {
|
||||||
"text" : ["R"],
|
"text" : "R",
|
||||||
"value" : ["R"]
|
"values" : ["R"]
|
||||||
},
|
},
|
||||||
"key4" : {
|
"key4" : {
|
||||||
"text" : ["Q"],
|
"text" : "Q",
|
||||||
"value" : ["Q"]
|
"values" : ["Q"]
|
||||||
},
|
},
|
||||||
"key5" : {
|
"key5" : {
|
||||||
"text" : ["W"],
|
"text" : "W",
|
||||||
"value" : ["W"]
|
"values" : ["W"]
|
||||||
},
|
},
|
||||||
"key6" : {
|
"key6" : {
|
||||||
"text" : ["E"],
|
"text" : "E",
|
||||||
"value" : ["E"]
|
"values" : ["E"]
|
||||||
},
|
},
|
||||||
"key7" : {
|
"key7" : {
|
||||||
"text" : ["A"],
|
"text" : "A",
|
||||||
"value" : ["A"]
|
"values" : ["A"]
|
||||||
},
|
},
|
||||||
"key8" : {
|
"key8" : {
|
||||||
"text" : ["S"],
|
"text" : "S",
|
||||||
"value" : ["S"]
|
"values" : ["S"]
|
||||||
},
|
},
|
||||||
"key9" : {
|
"key9" : {
|
||||||
"text" : ["D"],
|
"text" : "D",
|
||||||
"value" : ["D"]
|
"values" : ["D"]
|
||||||
},
|
},
|
||||||
"key10" : {
|
"key10" : {
|
||||||
"text" : ["Shift"],
|
"text" : "Shift",
|
||||||
"value" : [""]
|
"values" : ["SHIFT"]
|
||||||
},
|
},
|
||||||
"key11" : {
|
"key11" : {
|
||||||
"text" : ["C"],
|
"text" : "C",
|
||||||
"value" : ["C"]
|
"values" : ["C"]
|
||||||
},
|
},
|
||||||
"key12" : {
|
"key12" : {
|
||||||
"text" : ["Space"],
|
"text" : "Space",
|
||||||
"value" : ["SPACEBAR"]
|
"values" : ["SPACEBAR"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,52 @@
|
|||||||
{
|
{
|
||||||
"profileName" : "NumPad",
|
"profileName" : "Numpad Json",
|
||||||
"sortOrder" : 0,
|
"sortOrder" : 0,
|
||||||
|
|
||||||
"key1" : {
|
"key1" : {
|
||||||
"text" : ["7"],
|
"text" : "7",
|
||||||
"value" : ["SEVEN"]
|
"values" : ["SEVEN"]
|
||||||
},
|
},
|
||||||
"key2" : {
|
"key2" : {
|
||||||
"text" : ["8"],
|
"text" : "8",
|
||||||
"value" : ["EIGHT"]
|
"values" : ["EIGHT"]
|
||||||
},
|
},
|
||||||
"key3" : {
|
"key3" : {
|
||||||
"text" : ["9"],
|
"text" : "9",
|
||||||
"value" : ["NINE"]
|
"values" : ["NINE"]
|
||||||
},
|
},
|
||||||
"key4" : {
|
"key4" : {
|
||||||
"text" : ["4"],
|
"text" : "4",
|
||||||
"value" : ["FOUR"]
|
"values" : ["FOUR"]
|
||||||
},
|
},
|
||||||
"key5" : {
|
"key5" : {
|
||||||
"text" : ["5"],
|
"text" : "5",
|
||||||
"value" : ["FIVE"]
|
"values" : ["FIVE"]
|
||||||
},
|
},
|
||||||
"key6" : {
|
"key6" : {
|
||||||
"text" : ["6"],
|
"text" : "6",
|
||||||
"value" : ["SIX"]
|
"values" : ["SIX"]
|
||||||
},
|
},
|
||||||
"key7" : {
|
"key7" : {
|
||||||
"text" : ["1"],
|
"text" : "1",
|
||||||
"value" : ["ONE"]
|
"values" : ["ONE"]
|
||||||
},
|
},
|
||||||
"key8" : {
|
"key8" : {
|
||||||
"text" : ["2"],
|
"text" : "2",
|
||||||
"value" : ["TWO"]
|
"values" : ["TWO"]
|
||||||
},
|
},
|
||||||
"key9" : {
|
"key9" : {
|
||||||
"text" : ["3"],
|
"text" : "3",
|
||||||
"value" : ["THREE"]
|
"values" : ["THREE"]
|
||||||
},
|
},
|
||||||
"key10" : {
|
"key10" : {
|
||||||
"text" : ["0"],
|
"text" : "0",
|
||||||
"value" : ["ZERO"]
|
"values" : ["ZERO"]
|
||||||
},
|
},
|
||||||
"key11" : {
|
"key11" : {
|
||||||
"text" : ["."],
|
"text" : ".",
|
||||||
"value" : ["PERIOD"]
|
"values" : ["PERIOD"]
|
||||||
},
|
},
|
||||||
"key12" : {
|
"key12" : {
|
||||||
"text" : ["ENT"],
|
"text" : "ENT",
|
||||||
"value" : ["RETURN"]
|
"values" : ["RETURN"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"profileName" : "",
|
||||||
|
"sortOrder" : 99,
|
||||||
|
|
||||||
|
"key1" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key2" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key3" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key4" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key5" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key6" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key7" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key8" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key9" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key10" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key11" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
},
|
||||||
|
"key12" : {
|
||||||
|
"text" : "",
|
||||||
|
"values" : [""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .numpad import NumpadApp
|
|
||||||
@@ -1,47 +1,67 @@
|
|||||||
|
from time import monotonic_ns
|
||||||
|
|
||||||
import terminalio
|
import terminalio
|
||||||
from adafruit_display_text.bitmap_label import Label
|
from adafruit_display_text.bitmap_label import Label
|
||||||
|
|
||||||
from adafruit_display_text.scrolling_label import ScrollingLabel
|
|
||||||
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
||||||
|
from adafruit_hid.keycode import Keycode
|
||||||
from rainbowio import colorwheel
|
from rainbowio import colorwheel
|
||||||
|
|
||||||
from macropad_os import App
|
from macropad_os import App
|
||||||
from macropad_os.app_utils import rgb_from_int
|
from macropad_os.app_utils import rgb_from_int, MacroSet, Macro
|
||||||
|
|
||||||
|
COLOR_UPDATE_RATE = 33000000 # .033 seconds
|
||||||
labels = [
|
|
||||||
"7", "8", "9",
|
|
||||||
"4", "5", "6",
|
|
||||||
"1", "2", "3",
|
|
||||||
"0", ".", "SHIFT"
|
|
||||||
]
|
|
||||||
|
|
||||||
modified_labels = [
|
|
||||||
"<", ">", "&",
|
|
||||||
"(", ")", "%",
|
|
||||||
"/", "+", "-",
|
|
||||||
"*", "=", "SHIFT"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class NumpadApp(App):
|
class NumpadApp(App):
|
||||||
|
|
||||||
def __init__(self, macropad, config):
|
def __init__(self, macropad, config):
|
||||||
super().__init__(macropad, config)
|
super().__init__(macropad, config)
|
||||||
self.name = "Numpad"
|
self.name = "NumpadApp"
|
||||||
self.wheel_offset = 0
|
self.wheel_offset = 0
|
||||||
self.lit_keys = [False] * 12
|
|
||||||
self.labels = []
|
self.labels = []
|
||||||
self.title = "Numpad"
|
self.title = "Numpad"
|
||||||
self.modifier_pressed = False
|
self.modifier_pressed = False
|
||||||
|
self.last_color_update = 0
|
||||||
|
self.macros = MacroSet(
|
||||||
|
[
|
||||||
|
Macro("7", Keycode.SEVEN), Macro("8", Keycode.EIGHT), Macro("9", Keycode.NINE),
|
||||||
|
Macro("4", Keycode.FOUR), Macro("5", Keycode.FIVE), Macro("6", Keycode.SIX),
|
||||||
|
Macro("1", Keycode.ONE), Macro("2", Keycode.TWO), Macro("3", Keycode.THREE),
|
||||||
|
Macro("0", Keycode.ZERO), Macro(".", Keycode.PERIOD),
|
||||||
|
Macro("Mod", self.swap_modifier, released=self.swap_modifier)
|
||||||
|
],
|
||||||
|
encoder_up=Macro("+", Keycode.KEYPAD_PLUS),
|
||||||
|
encoder_down=Macro("-", Keycode.KEYPAD_MINUS),
|
||||||
|
)
|
||||||
|
self.mod_macros = MacroSet([
|
||||||
|
Macro("<", Keycode.SHIFT, Keycode.COMMA), Macro(">", Keycode.SHIFT, Keycode.PERIOD), Macro("BACKSP", Keycode.BACKSPACE),
|
||||||
|
|
||||||
|
Macro("(", Keycode.SHIFT, Keycode.NINE), Macro(")", Keycode.SHIFT, Keycode.ZERO),Macro("%", Keycode.SHIFT, Keycode.FIVE),
|
||||||
|
|
||||||
|
Macro("/", Keycode.FORWARD_SLASH), Macro("+", Keycode.KEYPAD_PLUS), Macro("-", Keycode.MINUS),
|
||||||
|
|
||||||
|
Macro("*", Keycode.SHIFT, Keycode.EIGHT), Macro("=", Keycode.EQUALS), Macro("Mod", self.swap_modifier, released=self.swap_modifier)
|
||||||
|
],
|
||||||
|
encoder_up=Macro("+", Keycode.KEYPAD_PLUS),
|
||||||
|
encoder_down=Macro("-", Keycode.KEYPAD_MINUS),
|
||||||
|
)
|
||||||
|
self.active_macros = self.macros
|
||||||
|
|
||||||
|
def swap_modifier(self):
|
||||||
|
self.modifier_pressed = not self.modifier_pressed
|
||||||
|
if self.modifier_pressed:
|
||||||
|
self.active_macros = self.mod_macros
|
||||||
|
else:
|
||||||
|
self.active_macros = self.macros
|
||||||
|
for i in range(12):
|
||||||
|
self.labels[i].text = self.active_macros.get_macro_from_key(i).name
|
||||||
|
|
||||||
def on_start(self):
|
def on_start(self):
|
||||||
print("on start from the app!")
|
print("on start from the app!")
|
||||||
self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1))
|
self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1))
|
||||||
self.set_title(self.title)
|
self.set_title(self.title)
|
||||||
self.lit_keys = [True] * 12
|
|
||||||
for i in range(12):
|
for i in range(12):
|
||||||
self.labels.append(Label(terminalio.FONT, text=labels[i]))
|
self.labels.append(Label(terminalio.FONT, text=self.active_macros.get_macro_from_key(i).name))
|
||||||
for index in range(12):
|
for index in range(12):
|
||||||
x = index % 3
|
x = index % 3
|
||||||
y = index // 3
|
y = index // 3
|
||||||
@@ -65,21 +85,20 @@ class NumpadApp(App):
|
|||||||
def update_key_colors(self):
|
def update_key_colors(self):
|
||||||
self.wheel_offset += 1
|
self.wheel_offset += 1
|
||||||
colors = []
|
colors = []
|
||||||
|
|
||||||
|
last_update_ago = monotonic_ns() - self.last_color_update
|
||||||
|
if last_update_ago > COLOR_UPDATE_RATE:
|
||||||
|
self.last_color_update = monotonic_ns()
|
||||||
for pixel in range(12):
|
for pixel in range(12):
|
||||||
if self.lit_keys[pixel]:
|
|
||||||
(r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset))
|
(r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset))
|
||||||
colors.append((r, g, b))
|
colors.append((r, g, b))
|
||||||
else:
|
|
||||||
colors.append((0, 0, 0))
|
|
||||||
self.set_colors(colors)
|
self.set_colors(colors)
|
||||||
|
|
||||||
def process_keys_pressed_callback(self, keyevent):
|
def process_keys_pressed_callback(self, key_event):
|
||||||
print("PROCESS KEYS CALLBACK FROM DEBUG")
|
self.press_macro(self.active_macros.get_macro_from_key(key_event))
|
||||||
print(keyevent)
|
|
||||||
|
|
||||||
def process_keys_released_callback(self, keyevent):
|
def process_keys_released_callback(self, key_event):
|
||||||
print("PROCESS KEYS RELEASED CALLBACK FROM DEBUG")
|
self.release_macro(self.active_macros.get_macro_from_key(key_event))
|
||||||
print(keyevent)
|
|
||||||
def process_enbcoder_changed(self, keyevent):
|
def process_enbcoder_changed(self, key_event):
|
||||||
print("PROCESS Encoder Changed Callback FROM DEBUG")
|
print(key_event)
|
||||||
print(keyevent)
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ from .serial_communications import SerialComms
|
|||||||
from .configuration import Config, ConfigItem
|
from .configuration import Config, ConfigItem
|
||||||
from .app_state import AppState, InvalidStateUpdateError
|
from .app_state import AppState, InvalidStateUpdateError
|
||||||
from .abstract_app import App
|
from .abstract_app import App
|
||||||
from .app_router import AppRouter
|
from .macropad_os import MacropadOS
|
||||||
+75
-11
@@ -1,10 +1,11 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
import displayio
|
import displayio
|
||||||
import terminalio
|
import terminalio
|
||||||
from adafruit_display_text import label
|
|
||||||
|
|
||||||
|
from time import monotonic_ns, sleep
|
||||||
|
|
||||||
|
from adafruit_display_text import label
|
||||||
from macropad_os import AppState, InvalidStateUpdateError, Config
|
from macropad_os import AppState, InvalidStateUpdateError, Config
|
||||||
|
from macropad_os.app_utils import Macro
|
||||||
|
|
||||||
DISPLAY = displayio.Group()
|
DISPLAY = displayio.Group()
|
||||||
|
|
||||||
@@ -13,8 +14,12 @@ def convert_to_keynum(x, y):
|
|||||||
return 3 * x + y
|
return 3 * x + y
|
||||||
|
|
||||||
|
|
||||||
class App(object):
|
# TODO: Limit sounds to a similar rate as well for better performance
|
||||||
|
|
||||||
|
MAX_LIGHTING_UPDATE_RATE = 33000000 # .033 seconds
|
||||||
|
|
||||||
|
|
||||||
|
class App(object):
|
||||||
def __init__(self, macropad, config: Config):
|
def __init__(self, macropad, config: Config):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -42,10 +47,12 @@ class App(object):
|
|||||||
self._state = AppState.STOPPED
|
self._state = AppState.STOPPED
|
||||||
self._name = "app"
|
self._name = "app"
|
||||||
self._key_tones = {}
|
self._key_tones = {}
|
||||||
|
self._last_lighting_update = 0
|
||||||
|
|
||||||
self._current_brightness = config.brightness()
|
self._current_brightness = config.brightness()
|
||||||
|
|
||||||
self.macropad = macropad
|
self.macropad = macropad
|
||||||
|
self.keyboard = macropad.keyboard
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
@@ -86,7 +93,7 @@ class App(object):
|
|||||||
self._state = AppState.PAUSED
|
self._state = AppState.PAUSED
|
||||||
|
|
||||||
def _on_pause(self) -> None:
|
def _on_pause(self) -> None:
|
||||||
self.macropad.keyboard.release_all()
|
self.keyboard.release_all()
|
||||||
self.macropad.consumer_control.release()
|
self.macropad.consumer_control.release()
|
||||||
self.macropad.mouse.release_all()
|
self.macropad.mouse.release_all()
|
||||||
self.macropad.stop_tone()
|
self.macropad.stop_tone()
|
||||||
@@ -116,7 +123,6 @@ class App(object):
|
|||||||
if key_event.key_number < 12:
|
if key_event.key_number < 12:
|
||||||
if key_event.pressed:
|
if key_event.pressed:
|
||||||
self.macropad.stop_tone()
|
self.macropad.stop_tone()
|
||||||
print(self.config.get_items())
|
|
||||||
self._play_tone_for_key(key_event.key_number)
|
self._play_tone_for_key(key_event.key_number)
|
||||||
if self._key_pressed_callbacks:
|
if self._key_pressed_callbacks:
|
||||||
for callback in self._key_pressed_callbacks:
|
for callback in self._key_pressed_callbacks:
|
||||||
@@ -124,7 +130,7 @@ class App(object):
|
|||||||
else:
|
else:
|
||||||
self._stop_tone_for_key(key_event.key_number)
|
self._stop_tone_for_key(key_event.key_number)
|
||||||
if self._key_released_callbacks:
|
if self._key_released_callbacks:
|
||||||
for callback in self._key_pressed_callbacks:
|
for callback in self._key_released_callbacks:
|
||||||
callback(key_event.key_number)
|
callback(key_event.key_number)
|
||||||
|
|
||||||
def _play_tone_for_key(self, key_number):
|
def _play_tone_for_key(self, key_number):
|
||||||
@@ -137,6 +143,7 @@ class App(object):
|
|||||||
|
|
||||||
def _stop_tone_for_key(self, key_number):
|
def _stop_tone_for_key(self, key_number):
|
||||||
self.macropad.stop_tone()
|
self.macropad.stop_tone()
|
||||||
|
if key_number in self._pressed_keys:
|
||||||
self._pressed_keys.remove(key_number)
|
self._pressed_keys.remove(key_number)
|
||||||
if self._pressed_keys and self.config.key_tone_enabled():
|
if self._pressed_keys and self.config.key_tone_enabled():
|
||||||
if key_number in self._key_tones:
|
if key_number in self._key_tones:
|
||||||
@@ -169,13 +176,14 @@ class App(object):
|
|||||||
raise NotImplementedError("on_stop not implemented.")
|
raise NotImplementedError("on_stop not implemented.")
|
||||||
|
|
||||||
def _update_lighting(self) -> None:
|
def _update_lighting(self) -> None:
|
||||||
|
last_update_ago = monotonic_ns() - self._last_lighting_update
|
||||||
|
if last_update_ago > MAX_LIGHTING_UPDATE_RATE:
|
||||||
|
self._last_lighting_update = monotonic_ns()
|
||||||
new_brightness = self.config.brightness()
|
new_brightness = self.config.brightness()
|
||||||
if self._current_brightness != new_brightness:
|
if self._current_brightness != new_brightness:
|
||||||
print("SETTING BRIGHTNESS!!!!")
|
# Setting the brightness here
|
||||||
print(self._key_lights)
|
|
||||||
self._key_lights = [tuple(rgb_val * new_brightness / self._current_brightness for rgb_val in color) for
|
self._key_lights = [tuple(rgb_val * new_brightness / self._current_brightness for rgb_val in color) for
|
||||||
color in self._key_lights]
|
color in self._key_lights]
|
||||||
print(self._key_lights)
|
|
||||||
self._current_brightness = self.config.brightness()
|
self._current_brightness = self.config.brightness()
|
||||||
for index, color in enumerate(self._key_lights):
|
for index, color in enumerate(self._key_lights):
|
||||||
self.macropad.pixels[index] = color
|
self.macropad.pixels[index] = color
|
||||||
@@ -199,7 +207,6 @@ class App(object):
|
|||||||
self._key_lights[key_value] = color
|
self._key_lights[key_value] = color
|
||||||
|
|
||||||
def set_colors(self, colors) -> None:
|
def set_colors(self, colors) -> None:
|
||||||
print(time.time())
|
|
||||||
if len(colors) != 12:
|
if len(colors) != 12:
|
||||||
raise ValueError("Colors must be passed in as a 12 len array")
|
raise ValueError("Colors must be passed in as a 12 len array")
|
||||||
for color in colors:
|
for color in colors:
|
||||||
@@ -255,6 +262,63 @@ class App(object):
|
|||||||
self._labels = labels
|
self._labels = labels
|
||||||
# if self._layout
|
# if self._layout
|
||||||
|
|
||||||
|
def press_macro(self, macro:Macro) -> None:
|
||||||
|
for item in macro.codes:
|
||||||
|
if callable(item):
|
||||||
|
item()
|
||||||
|
elif isinstance(item, int):
|
||||||
|
if item >= 0:
|
||||||
|
self.macropad.keyboard.press(item)
|
||||||
|
else:
|
||||||
|
self.macropad.keyboard.release(-item)
|
||||||
|
elif isinstance(item, float):
|
||||||
|
sleep(item)
|
||||||
|
elif isinstance(item, str):
|
||||||
|
self.macropad.keyboard_layout.write(item)
|
||||||
|
elif isinstance(item, list):
|
||||||
|
for code in item:
|
||||||
|
if isinstance(code, int):
|
||||||
|
self.macropad.consumer_control.release()
|
||||||
|
self.macropad.consumer_control.press(code)
|
||||||
|
if isinstance(code, float):
|
||||||
|
sleep(code)
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
if 'buttons' in item:
|
||||||
|
if item['buttons'] >= 0:
|
||||||
|
self.macropad.mouse.press(item['buttons'])
|
||||||
|
else:
|
||||||
|
self.macropad.mouse.release(-item['buttons'])
|
||||||
|
self.macropad.mouse.move(item['x'] if 'x' in item else 0,
|
||||||
|
item['y'] if 'y' in item else 0,
|
||||||
|
item['wheel'] if 'wheel' in item else 0)
|
||||||
|
if 'tone' in item:
|
||||||
|
if item['tone'] > 0:
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
self.macropad.start_tone(item['tone'])
|
||||||
|
else:
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
elif 'play' in item:
|
||||||
|
self.macropad.play_file(item['play'])
|
||||||
|
|
||||||
|
def release_macro(self, macro:Macro) -> None:
|
||||||
|
for item in macro.codes:
|
||||||
|
if isinstance(item, int):
|
||||||
|
if item >= 0:
|
||||||
|
self.macropad.keyboard.release(item)
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
if 'buttons' in item:
|
||||||
|
if item['buttons'] >= 0:
|
||||||
|
self.macropad.mouse.release(item['buttons'])
|
||||||
|
elif 'tone' in item:
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
self.macropad.consumer_control.release()
|
||||||
|
release_action = macro.released
|
||||||
|
if release_action:
|
||||||
|
if callable(release_action):
|
||||||
|
release_action()
|
||||||
|
else:
|
||||||
|
self.keyboard.send(release_action)
|
||||||
|
|
||||||
def register_on_key_pressed(self, function) -> None:
|
def register_on_key_pressed(self, function) -> None:
|
||||||
self._key_pressed_callbacks.append(function)
|
self._key_pressed_callbacks.append(function)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
from .static_lighting import *
|
from .static_lighting import *
|
||||||
from .sound_patterns import *
|
|
||||||
from .helpers import *
|
from .helpers import *
|
||||||
|
from .macro_utils import *
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class Macro(object):
|
||||||
|
def __init__(self, name, *codes, released = None):
|
||||||
|
self.name = name
|
||||||
|
self.codes = codes
|
||||||
|
self.released = released
|
||||||
|
|
||||||
|
|
||||||
|
class MacroSet(object):
|
||||||
|
def __init__(self, key_macros: [Macro], encoder_up: Macro, encoder_down: Macro):
|
||||||
|
if len(key_macros) != 12:
|
||||||
|
raise ValueError("12 keys must be passed to a keyset")
|
||||||
|
self._key_macros = key_macros
|
||||||
|
self.encoder_up = encoder_up
|
||||||
|
self.encoder_down = encoder_down
|
||||||
|
|
||||||
|
def get_macro_from_key(self, key_index) -> Macro:
|
||||||
|
if key_index < 0 or key_index > 11:
|
||||||
|
raise ValueError("Invalid key index")
|
||||||
|
return self._key_macros[key_index]
|
||||||
|
|
||||||
@@ -1,23 +1,29 @@
|
|||||||
import time
|
import time, os
|
||||||
|
|
||||||
|
from . import App
|
||||||
from .app_state import AppState
|
from .app_state import AppState
|
||||||
|
|
||||||
from macropad_os.system_apps import OptionsApp, DebugApp
|
from macropad_os.system_apps import OptionsApp, DebugApp, JsonApp
|
||||||
|
|
||||||
|
|
||||||
class AppRouter(object):
|
class MacropadOS(object):
|
||||||
def __init__(self, macropad, config, apps):
|
def __init__(self, macropad, config, python_apps: str, json_apps: str):
|
||||||
print("app router")
|
print("app router")
|
||||||
self.macropad = macropad
|
self.macropad = macropad
|
||||||
self.app_index = 0
|
self.app_index = 0
|
||||||
self.apps = apps
|
|
||||||
self.options = OptionsApp(macropad, config)
|
self.options = OptionsApp(macropad, config)
|
||||||
self.current_app = apps[self.app_index]
|
|
||||||
self.config = config
|
self.config = config
|
||||||
self.encoder_state = False
|
self.encoder_state = False
|
||||||
self.options_time = 500000000 # .5 seconds in nanoseconds
|
self.options_time = 500000000 # .5 seconds in nanoseconds
|
||||||
self.click_time = 0
|
self.click_time = 0
|
||||||
self.debug_app = DebugApp(macropad, config, "DEBUG APP")
|
self.debug_app = DebugApp(macropad, config, "DEBUG APP")
|
||||||
self.debug_app_active = self.config.debug_app_enabled()
|
self.debug_app_active = self.config.debug_app_enabled()
|
||||||
|
self.python_apps_location = python_apps
|
||||||
|
self.json_apps_location = json_apps
|
||||||
|
self.apps: [App] = []
|
||||||
|
self.load_python_apps()
|
||||||
|
self.load_json_apps()
|
||||||
|
self.current_app = self.apps[self.app_index]
|
||||||
if self.debug_app_active:
|
if self.debug_app_active:
|
||||||
self.apps.append(self.debug_app)
|
self.apps.append(self.debug_app)
|
||||||
|
|
||||||
@@ -38,7 +44,41 @@ class AppRouter(object):
|
|||||||
print("Starting new app")
|
print("Starting new app")
|
||||||
self.current_app.resume()
|
self.current_app.resume()
|
||||||
|
|
||||||
|
def load_python_apps(self):
|
||||||
|
app_classes = []
|
||||||
|
for filename in sorted(os.listdir(self.python_apps_location)):
|
||||||
|
if filename.endswith('.py') and not filename.startswith('._'):
|
||||||
|
try:
|
||||||
|
print(filename)
|
||||||
|
module = __import__(self.python_apps_location + '/' + filename[:-3])
|
||||||
|
classes = [getattr(module, a) for a in dir(module)
|
||||||
|
if isinstance(getattr(module, a), type)]
|
||||||
|
for cls in classes:
|
||||||
|
if issubclass(cls, App) and cls.__name__ != "App":
|
||||||
|
app_classes.append(cls)
|
||||||
|
print(app_classes)
|
||||||
|
except (SyntaxError, ImportError, AttributeError, KeyError, NameError, IndexError, TypeError) as err:
|
||||||
|
print("ERROR in", filename)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exception(err, err, err.__traceback__)
|
||||||
|
self.apps.extend(a(self.macropad, self.config) for a in app_classes)
|
||||||
|
|
||||||
|
def load_json_apps(self):
|
||||||
|
json_apps = []
|
||||||
|
for filename in sorted(os.listdir(self.json_apps_location)):
|
||||||
|
if filename.endswith('.json'):
|
||||||
|
json_apps.append(
|
||||||
|
JsonApp(
|
||||||
|
self.macropad,
|
||||||
|
self.config,
|
||||||
|
self.json_apps_location + "/" + filename
|
||||||
|
)
|
||||||
|
)
|
||||||
|
json_apps.sort(key=lambda x: x.sort_order)
|
||||||
|
self.apps.extend(json_apps)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
print(self.current_app)
|
||||||
self.current_app.start()
|
self.current_app.start()
|
||||||
self.current_app.resume()
|
self.current_app.resume()
|
||||||
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
from .debug_app import DebugApp
|
from .debug_app import DebugApp
|
||||||
from .options_app import OptionsApp
|
from .options_app import OptionsApp
|
||||||
|
from .json_app import JsonApp
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import terminalio
|
||||||
|
import json
|
||||||
|
|
||||||
|
from time import monotonic_ns
|
||||||
|
|
||||||
|
from adafruit_display_text.bitmap_label import Label
|
||||||
|
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
||||||
|
from adafruit_hid.keycode import Keycode
|
||||||
|
from macropad_os import App
|
||||||
|
from macropad_os.app_utils import rgb_from_int, Macro, MacroSet
|
||||||
|
from rainbowio import colorwheel
|
||||||
|
|
||||||
|
COLOR_UPDATE_RATE = 33000000 # .033 seconds
|
||||||
|
|
||||||
|
JSON_FILE_LOCATION = "./macropad_apps/json/"
|
||||||
|
|
||||||
|
PROFILE_NAME = "profileName"
|
||||||
|
SORT_ORDER = "sortOrder"
|
||||||
|
KEY_KEYS = ["key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9", "key10", "key11", "key12"]
|
||||||
|
REQUIRED_KEYS = KEY_KEYS.copy()
|
||||||
|
REQUIRED_KEYS.extend(x for x in [PROFILE_NAME, SORT_ORDER])
|
||||||
|
|
||||||
|
|
||||||
|
class JsonApp(App):
|
||||||
|
def __init__(self, macropad, config, json_file):
|
||||||
|
super().__init__(macropad, config)
|
||||||
|
self.json_file = json_file
|
||||||
|
self.wheel_offset = 0
|
||||||
|
self.labels = []
|
||||||
|
self.title = ""
|
||||||
|
self.last_color_update = 0
|
||||||
|
self.active_macro = 0
|
||||||
|
self.name = "JSON_APP"
|
||||||
|
self.json_dict = JsonApp.read_json_file(self.json_file)
|
||||||
|
self.macros = JsonApp.get_macroset_from_dict(self.json_dict)
|
||||||
|
self.title = self.json_dict[PROFILE_NAME]
|
||||||
|
self.sort_order = self.json_dict[SORT_ORDER]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_macro_from_dict(key_dict) -> Macro:
|
||||||
|
print(f"Creating macro for {key_dict}")
|
||||||
|
name = key_dict["text"]
|
||||||
|
values = key_dict["values"]
|
||||||
|
codes = []
|
||||||
|
for value in values:
|
||||||
|
if isinstance(value, str):
|
||||||
|
value_mod = 1
|
||||||
|
if value[0] is '-':
|
||||||
|
value_mod = -1
|
||||||
|
value = value[1:]
|
||||||
|
if hasattr(Keycode, value):
|
||||||
|
keycode = getattr(Keycode, value)
|
||||||
|
codes.append(keycode * value_mod)
|
||||||
|
else:
|
||||||
|
codes.append(value)
|
||||||
|
if isinstance(value, int) or isinstance(value, float) or isinstance(value, dict) or isinstance(value, list):
|
||||||
|
# TODO: Add validation for lists and dictionaries
|
||||||
|
codes.append(value)
|
||||||
|
print(name, codes)
|
||||||
|
return Macro(name, *codes)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_macroset_from_dict(json_dict: {}) -> MacroSet:
|
||||||
|
key_macros = []
|
||||||
|
for key in KEY_KEYS:
|
||||||
|
key_macros.append(JsonApp.create_macro_from_dict(json_dict[key]))
|
||||||
|
return MacroSet(key_macros=key_macros, encoder_up=Macro("Test"), encoder_down=Macro("Test"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_json_file(json_file) -> {}:
|
||||||
|
try:
|
||||||
|
f = open(json_file, "r")
|
||||||
|
json_dict = json.load(f)
|
||||||
|
print(json_dict)
|
||||||
|
for key in REQUIRED_KEYS:
|
||||||
|
if key not in json_dict:
|
||||||
|
raise ValueError(f"JSON FOR FILE - {json_file} IS INVALID - KEY MISSING {key}")
|
||||||
|
return json_dict
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
print("on start from the app!")
|
||||||
|
self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1))
|
||||||
|
self.set_title(self.title)
|
||||||
|
for i in range(12):
|
||||||
|
self.labels.append(
|
||||||
|
Label(terminalio.FONT, text=self.macros.get_macro_from_key(i).name))
|
||||||
|
for index in range(12):
|
||||||
|
x = index % 3
|
||||||
|
y = index // 3
|
||||||
|
self._layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1))
|
||||||
|
self.register_on_key_pressed(self.process_keys_pressed_callback)
|
||||||
|
self.register_on_key_released(self.process_keys_released_callback)
|
||||||
|
self.register_on_encoder_changed(self.process_enbcoder_changed)
|
||||||
|
|
||||||
|
def on_resume(self):
|
||||||
|
print("resume from the debug app!")
|
||||||
|
|
||||||
|
def on_pause(self):
|
||||||
|
print("Pausing")
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
print("Stopping")
|
||||||
|
|
||||||
|
def on_loop(self):
|
||||||
|
self.update_key_colors()
|
||||||
|
|
||||||
|
def update_key_colors(self):
|
||||||
|
self.wheel_offset += 1
|
||||||
|
colors = []
|
||||||
|
last_update_ago = monotonic_ns() - self.last_color_update
|
||||||
|
if last_update_ago > COLOR_UPDATE_RATE:
|
||||||
|
self.last_color_update = monotonic_ns()
|
||||||
|
for pixel in range(12):
|
||||||
|
(r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset))
|
||||||
|
colors.append((r, g, b))
|
||||||
|
self.set_colors(colors)
|
||||||
|
|
||||||
|
def process_keys_pressed_callback(self, key_event):
|
||||||
|
print(f"KEY PRESSED - {key_event}")
|
||||||
|
print(self.macros)
|
||||||
|
macro = self.macros.get_macro_from_key(key_event)
|
||||||
|
print(macro)
|
||||||
|
print(macro.name)
|
||||||
|
print(macro.codes)
|
||||||
|
|
||||||
|
self.press_macro(self.macros.get_macro_from_key(key_event))
|
||||||
|
|
||||||
|
def process_keys_released_callback(self, key_event):
|
||||||
|
self.release_macro(self.macros.get_macro_from_key(key_event))
|
||||||
|
|
||||||
|
def process_enbcoder_changed(self, key_event):
|
||||||
|
print(key_event)
|
||||||
@@ -204,7 +204,7 @@ class OptionsApp(App):
|
|||||||
|
|
||||||
def on_wheel_change(self, event):
|
def on_wheel_change(self, event):
|
||||||
if event > 0:
|
if event > 0:
|
||||||
self._up()
|
|
||||||
else:
|
|
||||||
self._down()
|
self._down()
|
||||||
|
else:
|
||||||
|
self._up()
|
||||||
self.update_settings_menu()
|
self.update_settings_menu()
|
||||||
|
|||||||
Reference in New Issue
Block a user