This commit is contained in:
Mike 2020-07-26 07:40:36 +01:00
parent e8ef1e0b11
commit f126373371
4 changed files with 330 additions and 9 deletions

View File

@ -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)

View File

@ -5,6 +5,8 @@ name = "pypi"
[packages] [packages]
pyaml = "*" pyaml = "*"
adafruit-circuitpython-servokit = "*"
getch = "*"
[dev-packages] [dev-packages]

101
Pipfile.lock generated
View File

@ -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": {}

View File

@ -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
```