hackerbot 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hackerbot/base/__init__.py +68 -1
- hackerbot/head/__init__.py +0 -26
- hackerbot/utils/tts_helper.py +101 -0
- hackerbot-0.6.0.dist-info/METADATA +129 -0
- {hackerbot-0.5.0.dist-info → hackerbot-0.6.0.dist-info}/RECORD +8 -7
- {hackerbot-0.5.0.dist-info → hackerbot-0.6.0.dist-info}/WHEEL +1 -1
- hackerbot-0.5.0.dist-info/METADATA +0 -69
- {hackerbot-0.5.0.dist-info → hackerbot-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {hackerbot-0.5.0.dist-info → hackerbot-0.6.0.dist-info}/top_level.txt +0 -0
hackerbot/base/__init__.py
CHANGED
@@ -16,8 +16,12 @@
|
|
16
16
|
|
17
17
|
|
18
18
|
from hackerbot.utils.hackerbot_helper import HackerbotHelper
|
19
|
+
from hackerbot.utils.tts_helper import TTSHelper
|
19
20
|
from .maps import Maps
|
20
21
|
import time
|
22
|
+
import sounddevice as sd
|
23
|
+
from piper.voice import PiperVoice
|
24
|
+
import numpy as np
|
21
25
|
|
22
26
|
class Base():
|
23
27
|
def __init__(self, controller: HackerbotHelper):
|
@@ -220,4 +224,67 @@ class Base():
|
|
220
224
|
if auto_dock:
|
221
225
|
time.sleep(3.0)
|
222
226
|
self.dock(block=False)
|
223
|
-
self._controller.destroy()
|
227
|
+
self._controller.destroy()
|
228
|
+
|
229
|
+
def speak(self, model_src, text, speaker_id=None):
|
230
|
+
"""
|
231
|
+
Synthesize and play speech audio based on the given text and voice model.
|
232
|
+
|
233
|
+
This function attempts to load a voice model, initialize an audio stream,
|
234
|
+
synthesize the given text into audio, and play it through the audio output
|
235
|
+
stream. If any step fails, an error is logged, and the process is aborted.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
model_src: The source of the voice model to load for speech synthesis.
|
239
|
+
text: The text content to be synthesized into speech.
|
240
|
+
speaker_id (optional): The ID of the speaker to use, if applicable.
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
None
|
244
|
+
"""
|
245
|
+
try:
|
246
|
+
try:
|
247
|
+
tts_helper = TTSHelper()
|
248
|
+
model_path = tts_helper.get_or_download_model(model_src)
|
249
|
+
except Exception as e:
|
250
|
+
self._controller.log_error(f"Failed to get or download model: {e}")
|
251
|
+
return
|
252
|
+
|
253
|
+
try:
|
254
|
+
voice = PiperVoice.load(model_path)
|
255
|
+
except Exception as e:
|
256
|
+
self._controller.log_error(f"Failed to load voice model: {e}")
|
257
|
+
return
|
258
|
+
|
259
|
+
try:
|
260
|
+
stream = sd.OutputStream(
|
261
|
+
samplerate=voice.config.sample_rate,
|
262
|
+
channels=1,
|
263
|
+
dtype='int16',
|
264
|
+
blocksize=0 # Let sounddevice choose blocksize automatically
|
265
|
+
)
|
266
|
+
except Exception as e:
|
267
|
+
self._controller.log_error(f"Failed to initialize audio stream: {e}")
|
268
|
+
return
|
269
|
+
|
270
|
+
try:
|
271
|
+
with stream:
|
272
|
+
for audio_bytes in voice.synthesize_stream_raw(text, speaker_id=speaker_id):
|
273
|
+
try:
|
274
|
+
int_data = np.frombuffer(audio_bytes, dtype=np.int16)
|
275
|
+
stream.write(int_data)
|
276
|
+
except Exception as e:
|
277
|
+
self._controller.log_error(f"Error writing audio data to stream: {e}")
|
278
|
+
break
|
279
|
+
|
280
|
+
try:
|
281
|
+
stream.stop()
|
282
|
+
except Exception as e:
|
283
|
+
self._controller.log_error(f"Failed to stop audio stream cleanly: {e}")
|
284
|
+
|
285
|
+
print("Finished speaking.")
|
286
|
+
|
287
|
+
except Exception as e:
|
288
|
+
self._controller.log_error(f"Error during audio streaming: {e}")
|
289
|
+
except Exception as e:
|
290
|
+
self._controller.log_error(f"Error in base:speak: {e}")
|
hackerbot/head/__init__.py
CHANGED
@@ -17,9 +17,6 @@
|
|
17
17
|
|
18
18
|
from hackerbot.utils.hackerbot_helper import HackerbotHelper
|
19
19
|
from .eyes import Eyes
|
20
|
-
import sounddevice as sd
|
21
|
-
from piper.voice import PiperVoice
|
22
|
-
import numpy as np
|
23
20
|
|
24
21
|
class Head():
|
25
22
|
def __init__(self, controller: HackerbotHelper):
|
@@ -62,26 +59,3 @@ class Head():
|
|
62
59
|
except Exception as e:
|
63
60
|
self._controller.log_error(f"Error in head:set_idle_mode: {e}")
|
64
61
|
return False
|
65
|
-
|
66
|
-
def speak(self, model_src, text, speaker_id = None):
|
67
|
-
model = model_src
|
68
|
-
voice = PiperVoice.load(model)
|
69
|
-
|
70
|
-
# Setup a sounddevice OutputStream with appropriate parameters
|
71
|
-
# The sample rate and channels should match the properties of the PCM data
|
72
|
-
stream = sd.OutputStream(
|
73
|
-
samplerate=voice.config.sample_rate,
|
74
|
-
channels=1,
|
75
|
-
dtype='int16',
|
76
|
-
blocksize=0 # Let sounddevice choose blocksize automatically
|
77
|
-
)
|
78
|
-
|
79
|
-
with stream: # This automatically handles start/stop/close
|
80
|
-
for audio_bytes in voice.synthesize_stream_raw(text, speaker_id=speaker_id):
|
81
|
-
int_data = np.frombuffer(audio_bytes, dtype=np.int16)
|
82
|
-
stream.write(int_data)
|
83
|
-
|
84
|
-
# At this point, all data has been written,
|
85
|
-
# but we need to wait for any remaining buffered audio to play out.
|
86
|
-
stream.stop() # Ensure stream stops cleanly
|
87
|
-
print("Finished speaking.") # <-- You now know it's done
|
@@ -0,0 +1,101 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Copyright (c) 2025 Hackerbot Industries LLC
|
3
|
+
#
|
4
|
+
# This source code is licensed under the MIT license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
#
|
7
|
+
# Created By: Allen Chien
|
8
|
+
# Created: April 2025
|
9
|
+
# Updated: 2025.05.13
|
10
|
+
#
|
11
|
+
# This module contains the TTSHelper class that gets or downloads a Piper voice
|
12
|
+
# model from HuggingFace
|
13
|
+
#
|
14
|
+
# Special thanks to the following for their code contributions to this codebase:
|
15
|
+
# Allen Chien - https://github.com/AllenChienXXX
|
16
|
+
# Saad Elbeleidy - https://github.com/sbeleidy
|
17
|
+
#
|
18
|
+
# Credits:
|
19
|
+
# This project makes use of functionality inspired by or adapted from:
|
20
|
+
# Dimits - https://github.com/Reqeique/Dimits
|
21
|
+
################################################################################
|
22
|
+
|
23
|
+
|
24
|
+
import os
|
25
|
+
from huggingface_hub import hf_hub_url
|
26
|
+
import requests
|
27
|
+
|
28
|
+
class TTSHelper:
|
29
|
+
"""
|
30
|
+
Downloads a Piper voice model (.onnx + .json) from HuggingFace
|
31
|
+
and stores it in a local directory for future use.
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, cache_dir: str = os.path.expanduser("~/piper_models")):
|
35
|
+
"""
|
36
|
+
Args:
|
37
|
+
cache_dir (str): Directory where downloaded models will be stored. Defaults to "~/piper_models".
|
38
|
+
"""
|
39
|
+
self.cache_dir = cache_dir
|
40
|
+
os.makedirs(self.cache_dir, exist_ok=True)
|
41
|
+
|
42
|
+
def get_or_download_model(self, model_src: str) -> str:
|
43
|
+
"""
|
44
|
+
If model_src is a valid file path, returns it.
|
45
|
+
Otherwise treats it as a HuggingFace voice model name and downloads it.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
model_src (str): Either local .onnx path or voice name (e.g., "en_US-amy-low").
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
str: Path to ONNX model file.
|
52
|
+
"""
|
53
|
+
if os.path.isfile(model_src):
|
54
|
+
return model_src
|
55
|
+
return self._download_model_from_huggingface(model_src)
|
56
|
+
|
57
|
+
def _download_model_from_huggingface(self, voice: str) -> str:
|
58
|
+
"""
|
59
|
+
Downloads a Piper voice model (.onnx and .json) from HuggingFace.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
voice (str): Voice model name, e.g., "en_US-amy-low".
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
str: Path to downloaded .onnx model.
|
66
|
+
|
67
|
+
Raises:
|
68
|
+
RuntimeError: If the download fails.
|
69
|
+
"""
|
70
|
+
try:
|
71
|
+
locale, person, pitch = voice.split('-')
|
72
|
+
lang, country = locale.split('_')
|
73
|
+
except ValueError:
|
74
|
+
raise RuntimeError(f"Invalid voice model name: {voice}")
|
75
|
+
|
76
|
+
base_path = f"{lang}/{locale}/{person}/{pitch}"
|
77
|
+
filename_onnx = f"{voice}.onnx"
|
78
|
+
filename_json = f"{voice}.onnx.json"
|
79
|
+
|
80
|
+
url_onnx = hf_hub_url(repo_id="rhasspy/piper-voices", filename=f"{base_path}/{filename_onnx}")
|
81
|
+
url_json = hf_hub_url(repo_id="rhasspy/piper-voices", filename=f"{base_path}/{filename_json}")
|
82
|
+
|
83
|
+
path_onnx = os.path.join(self.cache_dir, filename_onnx)
|
84
|
+
path_json = os.path.join(self.cache_dir, filename_json)
|
85
|
+
|
86
|
+
if not os.path.exists(path_onnx):
|
87
|
+
self._download_file(url_onnx, path_onnx)
|
88
|
+
if not os.path.exists(path_json):
|
89
|
+
self._download_file(url_json, path_json)
|
90
|
+
|
91
|
+
return path_onnx
|
92
|
+
|
93
|
+
def _download_file(self, url: str, dest_path: str):
|
94
|
+
try:
|
95
|
+
response = requests.get(url, stream=True)
|
96
|
+
response.raise_for_status()
|
97
|
+
with open(dest_path, 'wb') as f:
|
98
|
+
for chunk in response.iter_content(chunk_size=8192):
|
99
|
+
f.write(chunk)
|
100
|
+
except Exception as e:
|
101
|
+
raise RuntimeError(f"Failed to download {url}: {e}")
|
@@ -0,0 +1,129 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: hackerbot
|
3
|
+
Version: 0.6.0
|
4
|
+
Summary: This module contains the setup for the hackerbot python package.
|
5
|
+
Author-email: Allen Chien <allen@hackerbot.co>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/hackerbotindustries/hackerbot-python-package
|
8
|
+
Requires-Python: >=3.11
|
9
|
+
Description-Content-Type: text/markdown
|
10
|
+
License-File: LICENSE
|
11
|
+
Requires-Dist: pyserial
|
12
|
+
Requires-Dist: pytest
|
13
|
+
Requires-Dist: piper-tts
|
14
|
+
Requires-Dist: sounddevice
|
15
|
+
Requires-Dist: pyaudio
|
16
|
+
Requires-Dist: huggingface_hub
|
17
|
+
Requires-Dist: requests
|
18
|
+
Dynamic: license-file
|
19
|
+
|
20
|
+

|
21
|
+
# Hackerbot Python Package
|
22
|
+
|
23
|
+
The `hackerbot` Python package provides modules for controlling and managing the Hackerbot robotics system.
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
You can now install the package directly from PyPI:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
pip install hackerbot
|
31
|
+
```
|
32
|
+
|
33
|
+
This will automatically install all required dependencies and make the `hackerbot` package available in your Python environment.
|
34
|
+
|
35
|
+
### (Optional) Installing from Source
|
36
|
+
|
37
|
+
If you prefer to install from source for development purposes:
|
38
|
+
|
39
|
+
1. **Clone the Repository**
|
40
|
+
|
41
|
+
```bash
|
42
|
+
git clone https://github.com/hackerbotindustries/hackerbot-python-package.git
|
43
|
+
cd hackerbot-python-package/
|
44
|
+
```
|
45
|
+
|
46
|
+
2. **Install Locally**
|
47
|
+
|
48
|
+
```bash
|
49
|
+
pip install .
|
50
|
+
```
|
51
|
+
|
52
|
+
## Quick Start
|
53
|
+
|
54
|
+
SSH into the Raspberry Pi or open a VNC viewer, then install the official Python package:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
pip install hackerbot
|
58
|
+
```
|
59
|
+
|
60
|
+
Or upgrade the existing package:
|
61
|
+
|
62
|
+
```bash
|
63
|
+
pip install --upgrade hackerbot
|
64
|
+
```
|
65
|
+
|
66
|
+
Then, run `python3` to open up the Python interactive shell and copy and paste the following:
|
67
|
+
|
68
|
+
```python
|
69
|
+
from hackerbot import Hackerbot
|
70
|
+
|
71
|
+
bot = Hackerbot()
|
72
|
+
|
73
|
+
bot.base.drive(0, 65)
|
74
|
+
bot.base.drive(200, 0)
|
75
|
+
```
|
76
|
+
|
77
|
+
You should see your Hackerbot leave the charger and move in the opposite direction.
|
78
|
+
|
79
|
+
```python
|
80
|
+
bot.head.look(180, 250, 70)
|
81
|
+
```
|
82
|
+
|
83
|
+
Now your robot should move its head and look up at you!
|
84
|
+
|
85
|
+
```python
|
86
|
+
bot.arm.move_joints(0, 0, 0, 0, 0, 0, 10)
|
87
|
+
```
|
88
|
+
|
89
|
+
You should see your elephant arm moving to a straight-up position.
|
90
|
+
|
91
|
+
```python
|
92
|
+
bot.base.destroy(auto_dock=True)
|
93
|
+
```
|
94
|
+
|
95
|
+
Safely clean up, and your Hackerbot will return to the charger. Once `destroy` is called, you need to create a new Hackerbot instance to perform new actions.
|
96
|
+
|
97
|
+
## Usage
|
98
|
+
|
99
|
+
After installation, you can import and use the package in your Python scripts:
|
100
|
+
|
101
|
+
```python
|
102
|
+
import hackerbot
|
103
|
+
```
|
104
|
+
|
105
|
+
## Testing
|
106
|
+
|
107
|
+
To run the unit tests:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
cd tests/unit_tests
|
111
|
+
pytest
|
112
|
+
```
|
113
|
+
|
114
|
+
## Troubleshooting
|
115
|
+
|
116
|
+
If you run into issues during installation or usage, try the following:
|
117
|
+
|
118
|
+
* Use a virtual environment:
|
119
|
+
|
120
|
+
```bash
|
121
|
+
python3 -m venv venv
|
122
|
+
source venv/bin/activate
|
123
|
+
```
|
124
|
+
|
125
|
+
* Upgrade `pip`:
|
126
|
+
|
127
|
+
```bash
|
128
|
+
pip install --upgrade pip
|
129
|
+
```
|
@@ -2,14 +2,15 @@ hackerbot/__init__.py,sha256=fjzBB_VsqCuKJ3yeLdqLSvAQIz9bAMI-KXrUC6ZrscY,1248
|
|
2
2
|
hackerbot/core.py,sha256=oZKQEUaMn19MnaWNTDFqTZ7lziBEZeSk5CdLXLSLvr0,5521
|
3
3
|
hackerbot/arm/__init__.py,sha256=JdCmTDQ2qk3W8-Alc4mjaQYxHnmH26r0nSy0JFg6J4A,3400
|
4
4
|
hackerbot/arm/gripper.py,sha256=xZux0PZL4vvrJTTKHL7dDsRBfJr4zbmgMaA0uBR1u4s,1986
|
5
|
-
hackerbot/base/__init__.py,sha256=
|
5
|
+
hackerbot/base/__init__.py,sha256=7OtK29R8G9JYkg_hYWJbZ4DhX0PtldYCneyrZE31qCQ,11387
|
6
6
|
hackerbot/base/maps.py,sha256=I_4NdvOMnkHX6-mW43QpDPJ-eVc6npgcBXq99tU7D1g,5916
|
7
|
-
hackerbot/head/__init__.py,sha256=
|
7
|
+
hackerbot/head/__init__.py,sha256=uBJ4gTst_QuCdveeNnuYZLIwsS8ANK9hxKMsLJVpI74,2467
|
8
8
|
hackerbot/head/eyes.py,sha256=xqeKMxL12iaa8KQzDlbgbNy3LzcmWm8aXkebztYJ4P8,1370
|
9
9
|
hackerbot/utils/hackerbot_helper.py,sha256=egQPVBBUo52ywsO6jGAGVqhyGLpMPz-b2fgLBb39WSM,4998
|
10
10
|
hackerbot/utils/serial_helper.py,sha256=qo-mpbE_sb23_IAIfDuRT8sCDtW8d8A8qyav-b9e5Kw,6567
|
11
|
-
hackerbot
|
12
|
-
hackerbot-0.
|
13
|
-
hackerbot-0.
|
14
|
-
hackerbot-0.
|
15
|
-
hackerbot-0.
|
11
|
+
hackerbot/utils/tts_helper.py,sha256=X-3G2hhq94i9z3MdLzD-pFSwnIW707pI7Leu1h8EO60,3575
|
12
|
+
hackerbot-0.6.0.dist-info/licenses/LICENSE,sha256=SCcXH0bf35ISRu_Ks8xEKySHXiqclANLWhBUbVU8VvA,1081
|
13
|
+
hackerbot-0.6.0.dist-info/METADATA,sha256=V95jUbd4OUR80K9xnCg2uIjeCmMpAC2NmgSW4PHx8GM,2724
|
14
|
+
hackerbot-0.6.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
15
|
+
hackerbot-0.6.0.dist-info/top_level.txt,sha256=2n_FStAr1SiI3BV67x7KJHYIOmEwxjUD59zedw2hLkU,10
|
16
|
+
hackerbot-0.6.0.dist-info/RECORD,,
|
@@ -1,69 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: hackerbot
|
3
|
-
Version: 0.5.0
|
4
|
-
Summary: This module contains the setup for the hackerbot python package.
|
5
|
-
Author-email: Allen Chien <allen@hackerbot.co>
|
6
|
-
License-Expression: MIT
|
7
|
-
Project-URL: Homepage, https://github.com/hackerbotindustries/hackerbot-python-package
|
8
|
-
Requires-Python: >=3.11
|
9
|
-
Description-Content-Type: text/markdown
|
10
|
-
License-File: LICENSE
|
11
|
-
Requires-Dist: pyserial
|
12
|
-
Requires-Dist: pytest
|
13
|
-
Requires-Dist: piper-tts
|
14
|
-
Requires-Dist: sounddevice
|
15
|
-
Requires-Dist: pyaudio
|
16
|
-
Dynamic: license-file
|
17
|
-
|
18
|
-
# Hackerbot Python Package
|
19
|
-
|
20
|
-
Hackerbot python package (`hackerbot-python-package`) is a project that includes modules for controlling and managing the Hackerbot system.
|
21
|
-
|
22
|
-
## Installation
|
23
|
-
|
24
|
-
Follow these steps to clone the repository and set up the required dependencies.
|
25
|
-
|
26
|
-
### 1. Clone the Repository
|
27
|
-
Use SSH to clone the repository:
|
28
|
-
```bash
|
29
|
-
https://github.com/hackerbotindustries/hackerbot-python-package.git
|
30
|
-
```
|
31
|
-
This will create a directory named `hackerbot-python-package` and download all necessary files.
|
32
|
-
|
33
|
-
### 2. Navigate to the Modules Directory
|
34
|
-
Move into the `hackerbot_modules` directory:
|
35
|
-
```bash
|
36
|
-
cd hackerbot-python-package/
|
37
|
-
```
|
38
|
-
|
39
|
-
### 3. Install Dependencies
|
40
|
-
Install the `hackerbot` package using `pip`:
|
41
|
-
```bash
|
42
|
-
pip install .
|
43
|
-
```
|
44
|
-
This will install the package locally for your Python environment.
|
45
|
-
|
46
|
-
## Usage
|
47
|
-
Once installed, you can import `hackerbot` in your Python scripts:
|
48
|
-
```python
|
49
|
-
import hackerbot
|
50
|
-
```
|
51
|
-
|
52
|
-
### 4. Testing
|
53
|
-
To run the unit tests run:
|
54
|
-
```bash
|
55
|
-
cd tests/unit_tests
|
56
|
-
pytest
|
57
|
-
```
|
58
|
-
|
59
|
-
## Troubleshooting
|
60
|
-
If you run into issues with the installation, try the following:
|
61
|
-
- Ensure you're using a virtual environment:
|
62
|
-
```bash
|
63
|
-
python3 -m venv venv
|
64
|
-
source venv/bin/activate
|
65
|
-
```
|
66
|
-
- Upgrade `pip` before installation:
|
67
|
-
```bash
|
68
|
-
pip install --upgrade pip
|
69
|
-
```
|
File without changes
|
File without changes
|