work
This commit is contained in:
parent
e8ef1e0b11
commit
f126373371
@ -1,13 +1,31 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from adafruit_servokit import ServoKit
|
||||||
|
|
||||||
|
|
||||||
|
import getch
|
||||||
import logging
|
import logging
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
import pprint
|
import pprint
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import tty
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
class MikesServoMapper:
|
class MikesServoMapper:
|
||||||
|
|
||||||
|
__BASE_I2C_ADDRESS = 0x40
|
||||||
|
__CHANNELS_COUNT = 16
|
||||||
|
|
||||||
|
__DEFAULT_SERVO_DEGREES = 180
|
||||||
|
__DEFAULT_JIGGLE_DURATION = 2
|
||||||
|
__DEFAULT_JIGGLE_SLICES = 50
|
||||||
|
|
||||||
|
__ESCAPE_KEY = chr(27)
|
||||||
|
|
||||||
def __init__(self, config_file: str, names):
|
def __init__(self, config_file: str, names):
|
||||||
|
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
@ -21,15 +39,17 @@ class MikesServoMapper:
|
|||||||
self.__config = None
|
self.__config = None
|
||||||
self.load_config(config_file)
|
self.load_config(config_file)
|
||||||
self.pull_config_names()
|
self.pull_config_names()
|
||||||
|
|
||||||
self.__logger.info("Names: %s" % (pprint.pformat(self.__names)))
|
self.__logger.info("Names: %s" % (pprint.pformat(self.__names)))
|
||||||
|
|
||||||
|
self.__mappings = {}
|
||||||
|
|
||||||
def init_logging(self):
|
def init_logging(self):
|
||||||
|
|
||||||
self.__logger = logging.Logger("Mikes Servo Mapper")
|
self.__logger = logging.Logger("Mikes Servo Mapper")
|
||||||
self.__logger_formatter = logging.Formatter(fmt="Hi poop")
|
self.__logger_formatter = logging.Formatter(fmt="[%(asctime)s][%(name)s] %(message)s")
|
||||||
|
|
||||||
stream_handler = logging.StreamHandler(sys.stdout)
|
stream_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
stream_handler.setFormatter(self.__logger_formatter)
|
||||||
|
|
||||||
self.__logger.addHandler(stream_handler)
|
self.__logger.addHandler(stream_handler)
|
||||||
|
|
||||||
@ -70,3 +90,170 @@ class MikesServoMapper:
|
|||||||
self.__names.sort()
|
self.__names.sort()
|
||||||
self.__logger.info("Names after pulling from config: %s" % (self.__names,))
|
self.__logger.info("Names after pulling from config: %s" % (self.__names,))
|
||||||
|
|
||||||
|
def set_name_mapping(self, name, channel):
|
||||||
|
|
||||||
|
self.__mappings[name] = channel
|
||||||
|
|
||||||
|
def get_name_mapping(self, name):
|
||||||
|
|
||||||
|
if name in self.__mappings:
|
||||||
|
return self.__mappings[name]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def determine_i2c_address(self):
|
||||||
|
|
||||||
|
return self.__BASE_I2C_ADDRESS
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
self.__logger.info("Running!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
self.__logger.info("")
|
||||||
|
self.__logger.info("Please choose a mode: ")
|
||||||
|
self.__logger.info("1. Create mappings")
|
||||||
|
self.__logger.info("2. Test current mappings")
|
||||||
|
self.__logger.info("3. Write mappings to file")
|
||||||
|
self.__logger.info("Q. Quit")
|
||||||
|
user_choice = input("====> ")
|
||||||
|
|
||||||
|
if user_choice == "q" or user_choice == "Q":
|
||||||
|
self.__logger.info("Quitting!")
|
||||||
|
break
|
||||||
|
|
||||||
|
if user_choice == "1":
|
||||||
|
self.do_mappings()
|
||||||
|
elif user_choice == "2":
|
||||||
|
self.test_mappings()
|
||||||
|
elif user_choice == "3":
|
||||||
|
self.write_mappings()
|
||||||
|
else:
|
||||||
|
self.__logger.warning("Invalid choice: %s" % user_choice)
|
||||||
|
|
||||||
|
def do_mappings(self):
|
||||||
|
|
||||||
|
self.__logger.info("Begin mapping mode !")
|
||||||
|
|
||||||
|
i2c_address = self.determine_i2c_address()
|
||||||
|
servo_kit = ServoKit(
|
||||||
|
address=i2c_address,
|
||||||
|
channels=self.__CHANNELS_COUNT
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
while True:
|
||||||
|
|
||||||
|
# Print all current mappings
|
||||||
|
self.__logger.info("")
|
||||||
|
self.__logger.info("Current Mappings:")
|
||||||
|
menu_number_to_name = {}
|
||||||
|
for name_index in range(len(self.__names)):
|
||||||
|
|
||||||
|
name = self.__names[name_index]
|
||||||
|
name_number = name_index + 1
|
||||||
|
menu_number_to_name[str(name_number)] = name
|
||||||
|
|
||||||
|
self.__logger.info(
|
||||||
|
"%s. %s ==> %s"
|
||||||
|
% (name_number, name, self.get_name_mapping(name=name))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.__logger.info("")
|
||||||
|
self.__logger.info("Please enter a number to change the corresponding mapping, or Q to quit.")
|
||||||
|
user_input = input("==========> ")
|
||||||
|
if user_input == "Q" or user_input == "q":
|
||||||
|
self.__logger.info("Quitting mapping mode")
|
||||||
|
break
|
||||||
|
elif user_input in menu_number_to_name:
|
||||||
|
name = menu_number_to_name[user_input]
|
||||||
|
channel = self.run_one_mapping(
|
||||||
|
servo_kit=servo_kit,
|
||||||
|
name=name,
|
||||||
|
default_channel=self.get_name_mapping(name)
|
||||||
|
)
|
||||||
|
self.set_name_mapping(name=name, channel=channel)
|
||||||
|
else:
|
||||||
|
self.__logger.warning("Invalid input: %s" % user_input)
|
||||||
|
|
||||||
|
def run_one_mapping(self, servo_kit, name, default_channel=None):
|
||||||
|
|
||||||
|
selected_channel = default_channel
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
self.__logger.info("")
|
||||||
|
self.__logger.info("Mapping channel for: %s" % (name,))
|
||||||
|
self.__logger.info(
|
||||||
|
"Press a key between 0-9 and A-F to try a channel."
|
||||||
|
" Press the space bar when you've found the correct channel, or escape to abort."
|
||||||
|
)
|
||||||
|
self.__logger.info("Currently selected channel: %s" % selected_channel)
|
||||||
|
|
||||||
|
key = getch.getch().lower()
|
||||||
|
if key == self.__ESCAPE_KEY:
|
||||||
|
self.__logger.info("Aborting")
|
||||||
|
selected_channel = None
|
||||||
|
break
|
||||||
|
elif key == " ":
|
||||||
|
self.__logger.info("Selected channel: %s" % selected_channel)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
|
||||||
|
try:
|
||||||
|
channel = int(key, 16)
|
||||||
|
selected_channel = channel
|
||||||
|
self.jiggle_channel(servo_kit=servo_kit, channel=channel)
|
||||||
|
except ValueError:
|
||||||
|
self.__logger.warning("Invalid input!: %s" % (key,))
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
return selected_channel
|
||||||
|
|
||||||
|
def test_mappings(self):
|
||||||
|
|
||||||
|
self.__logger.info("Testing mappings!")
|
||||||
|
|
||||||
|
for name in self.__mappings.keys():
|
||||||
|
|
||||||
|
channel = self.get_name_mapping(name=name)
|
||||||
|
self.__logger.info("Jiggling mapping: %s ==> %s" % (name, channel))
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
self.__logger.info("Done testing mappings")
|
||||||
|
|
||||||
|
def jiggle_channel(self, servo_kit, channel):
|
||||||
|
|
||||||
|
duration = self.__DEFAULT_JIGGLE_DURATION
|
||||||
|
|
||||||
|
degrees_per_slice = self.__DEFAULT_SERVO_DEGREES / self.__DEFAULT_JIGGLE_SLICES
|
||||||
|
seconds_per_slice = duration / self.__DEFAULT_JIGGLE_SLICES
|
||||||
|
|
||||||
|
self.__logger.info(
|
||||||
|
"Jiggling servo on channel #%s using %s slices over %s seconds"
|
||||||
|
% (channel, self.__DEFAULT_JIGGLE_SLICES, duration)
|
||||||
|
)
|
||||||
|
|
||||||
|
servo = servo_kit.servo[channel]
|
||||||
|
|
||||||
|
# Jiggle
|
||||||
|
for slice_index in range(self.__DEFAULT_JIGGLE_SLICES):
|
||||||
|
|
||||||
|
angle = 0 + (degrees_per_slice * slice_index)
|
||||||
|
servo.angle = angle
|
||||||
|
time.sleep(seconds_per_slice)
|
||||||
|
|
||||||
|
# Center
|
||||||
|
servo.angle = 90
|
||||||
|
|
||||||
|
def write_mappings(self):
|
||||||
|
|
||||||
|
output_file_path = os.path.join(
|
||||||
|
"output",
|
||||||
|
"servo-mappings.yml"
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(output_file_path, 'w') as f:
|
||||||
|
yaml.dump(self.__mappings, f, default_flow_style=False)
|
||||||
|
2
Pipfile
2
Pipfile
@ -5,6 +5,8 @@ name = "pypi"
|
|||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
pyaml = "*"
|
pyaml = "*"
|
||||||
|
adafruit-circuitpython-servokit = "*"
|
||||||
|
getch = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
101
Pipfile.lock
generated
101
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "1f84f63565324f6ff4b4071af61c776885d170eed424917d7adb74b2b97517e3"
|
"sha256": "82ef81fca28479231fa9cc4c64186b3ca5b9a84ed6de11c07c74fa837224d85a"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -16,6 +16,63 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
"adafruit-blinka": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7f6b2361e066033cfbace159bf85c26f50ef18a3c889b068529e29891caf1a10"
|
||||||
|
],
|
||||||
|
"version": "==5.2.3"
|
||||||
|
},
|
||||||
|
"adafruit-circuitpython-busdevice": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7709c75c22e37f3af1b161be26c7b9925bafd68a0d7c5526637a3f838fcb6627"
|
||||||
|
],
|
||||||
|
"version": "==4.3.2"
|
||||||
|
},
|
||||||
|
"adafruit-circuitpython-motor": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9324290afa07faec1c7d9ca35f084150f0bf8169dc12599bda8fc4c615d0806e"
|
||||||
|
],
|
||||||
|
"version": "==3.1.2"
|
||||||
|
},
|
||||||
|
"adafruit-circuitpython-pca9685": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0f8e8c4cdb4ef1828ac9e8bc870017f2f5ef1cccc906172e06d81062859bc34e"
|
||||||
|
],
|
||||||
|
"version": "==3.3.2"
|
||||||
|
},
|
||||||
|
"adafruit-circuitpython-register": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5e7232fd7ffb3f0aac2dc0de631097c629e836a8845aa0e028e8dc1c364ea064"
|
||||||
|
],
|
||||||
|
"version": "==1.9.0"
|
||||||
|
},
|
||||||
|
"adafruit-circuitpython-servokit": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:396c93f07b62ff5599691261ead0f0731d1a22dae8e394afbd372a0842fc886c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"adafruit-platformdetect": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01f07606319f4e463889ee129fa1c62b4538d754cb1f89cf530b1aef91253729"
|
||||||
|
],
|
||||||
|
"version": "==2.14.1"
|
||||||
|
},
|
||||||
|
"adafruit-pureio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ebab172823f7249e02a644844a64e6dc2e4b3ded38ba42099068fd3e96623cfb"
|
||||||
|
],
|
||||||
|
"version": "==1.1.5"
|
||||||
|
},
|
||||||
|
"getch": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a6c22717c10051ce65b8fb7bddb171af705b1175e694a73be956990f6089d8b1",
|
||||||
|
"sha256:be451438f7a2b389f96753aea39b6ed2540a390f1b9a12badcbc110cf9a5ce7f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.0"
|
||||||
|
},
|
||||||
"pyaml": {
|
"pyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71",
|
"sha256:29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71",
|
||||||
@ -24,6 +81,26 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==20.4.0"
|
"version": "==20.4.0"
|
||||||
},
|
},
|
||||||
|
"pyftdi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02926258d5dfd28452a3d4d7c2f6d5bab722133b2885bde8b9e28bd2ccc095ca",
|
||||||
|
"sha256:6cacb8fe28491ee6d00b8d45e18f73ea539b31bcd785f5d8c80a60322297c007"
|
||||||
|
],
|
||||||
|
"version": "==0.51.2"
|
||||||
|
},
|
||||||
|
"pyserial": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627",
|
||||||
|
"sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"
|
||||||
|
],
|
||||||
|
"version": "==3.4"
|
||||||
|
},
|
||||||
|
"pyusb": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4e9b72cc4a4205ca64fbf1f3fff39a335512166c151ad103e55c8223ac147362"
|
||||||
|
],
|
||||||
|
"version": "==1.0.2"
|
||||||
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
|
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
|
||||||
@ -39,6 +116,28 @@
|
|||||||
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
|
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
|
||||||
],
|
],
|
||||||
"version": "==5.3.1"
|
"version": "==5.3.1"
|
||||||
|
},
|
||||||
|
"rpi-ws281x": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:461153b0ae29f28ebf7fbca781a92759a5f622d5478d3e61b0475fb0a7da6fb3",
|
||||||
|
"sha256:6cfb8c0c429dee312b2173ea3968c3da2a3625ce57bbf580ee9f49c3edaf4504",
|
||||||
|
"sha256:7175e708d6085bc02a9d0b8227797d697e34fd00787030ae5f119fe2f4f90889"
|
||||||
|
],
|
||||||
|
"version": "==4.2.4"
|
||||||
|
},
|
||||||
|
"rpi.gpio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7424bc6c205466764f30f666c18187a0824077daf20b295c42f08aea2cb87d3f"
|
||||||
|
],
|
||||||
|
"version": "==0.7.0"
|
||||||
|
},
|
||||||
|
"sysv-ipc": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2b2b8491764845d06a9d5731ebe67f12fbe0c35967ac7f2bbe55ff24cb302405",
|
||||||
|
"sha256:8eff10dd17789ddf21b422ce46ae0f6420088902a88e4296cb805cf2fde8b4dc",
|
||||||
|
"sha256:a67fc5b7a3dd5fc9c50776de0c65a44eb776d29bb88364dedb1fb1149e53a1f1"
|
||||||
|
],
|
||||||
|
"version": "==1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
43
README.md
43
README.md
@ -7,13 +7,22 @@ Written and tested using the Adafruit I2C servo driver board: [PCA9685](https://
|
|||||||
|
|
||||||
## Requiremments
|
## Requiremments
|
||||||
|
|
||||||
* ```pipenv```
|
### Python Requirements
|
||||||
|
|
||||||
## Installation
|
Python's requirements are handled by pipenv, which you can install like so:
|
||||||
|
|
||||||
cd to this repo's directory and install pip dependencies with:
|
```bash
|
||||||
|
sudo apt install pipenv
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```bash
|
||||||
|
sudo dnf install pipenv
|
||||||
|
```
|
||||||
|
|
||||||
```pipenv install```
|
Once installed, you can have pipenv install all python requirements like so:
|
||||||
|
|
||||||
|
1. cd to this repo's directory
|
||||||
|
2. Execute the command: ```pipenv install```
|
||||||
|
|
||||||
## Execution
|
## Execution
|
||||||
|
|
||||||
@ -23,6 +32,30 @@ cd to this repo's directory and execute using:
|
|||||||
|
|
||||||
## Command Line Arguments
|
## Command Line Arguments
|
||||||
|
|
||||||
TODO
|
### ***--name*** (Specify one or more mapping names)
|
||||||
|
|
||||||
|
You can specify desired mapping names by adding the ***--name*** argument, as many times as you wish:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pipenv run python3 main.py --name Leg --name Arm
|
||||||
|
```
|
||||||
|
|
||||||
|
### ***--config*** (Specify an input config file)
|
||||||
|
|
||||||
|
You can specify a yaml configuration file to load with this argument, like so:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pipenv run python3 main.py --config /path/to/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
So far the config file is only good for storing desired names to be mapped. Here's an example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
|
||||||
|
names:
|
||||||
|
- Manny
|
||||||
|
- Moe
|
||||||
|
- Jack
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user