Skip to content

Commit cda3422

Browse files
author
qijun han
committed
docs: update README and index.md for Thread-Everything project; add plugins tutorial
1 parent bc62f02 commit cda3422

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Thread-Everything: 一个简单易用的跨平台多端通信工具
22
==================================================
33
[![Deploy MkDocs site to GitHub Pages (using mkdocs gh-deploy)](https://github.com/sergiudm/detectivePi/actions/workflows/mkdocs.yml/badge.svg)](https://github.com/sergiudm/detectivePi/actions/workflows/mkdocs.yml)
44
[![CI Tests](https://github.com/sergiudm/detectivePi/actions/workflows/test.yml/badge.svg)](https://github.com/sergiudm/detectivePi/actions/workflowstest.yml)
5-
[![PyPI version](https://badge.fury.io/py/detective-pi.svg)](https://pypi.org/project/detective-pi/0.2.0/)
5+
[![PyPI version](https://badge.fury.io/py/detective-pi.svg)](https://pypi.org/project/Thread-Everything/)
66
![GitHub license](https://img.shields.io/github/license/sergiudm/detectivePi)
77
## 介绍
88

site_docs/docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Thread-Everything:An easy-to-use interface to run threads from different machi
22
==================================================
33
[![site deployment](https://github.com/sergiudm/detectivePi/actions/workflows/mkdocs.yml/badge.svg)](https://github.com/sergiudm/detectivePi/actions/workflows/mkdocs.yml)
44
[![CI Tests](https://github.com/sergiudm/detectivePi/actions/workflows/test.yml/badge.svg)](https://github.com/sergiudm/detectivePi/actions/workflowstest.yml)
5-
[![PyPI version](https://badge.fury.io/py/detective-pi.svg)](https://pypi.org/project/detective-pi/0.2.0/)
5+
[![PyPI version](https://badge.fury.io/py/detective-pi.svg)](https://pypi.org/project/Thread-Everything/)
66
![GitHub license](https://img.shields.io/github/license/sergiudm/detectivePi)
77
## Introduction
88

site_docs/docs/plugins-tutorial.md

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
How to write your own plugins
2+
=============================
3+
Thread-Everything is a highly extensible project that allows you to define your own plugins in the `modules` directory. We have implemented some plugins, they lie in:
4+
5+
```
6+
.
7+
├── common.py
8+
├── detect_others.py
9+
├── gesture.py
10+
├── gpio_controller.py
11+
├── __init__.py
12+
├── meditation_assistant.py
13+
├── music_player.py
14+
└── utils.py
15+
```
16+
To integrate your own function, you should:
17+
1. Implement all your features in a Python module.
18+
2. Encaplulate all your functions to a single function.
19+
3. Create a new thread instance in `detective.runner.runner_engine, together with your arguments.
20+
21+
Here is a step-by-step demontration:
22+
23+
## Create a new Python module
24+
In this example, we implement a music player feature in `music_player.py` which lies in `detective/runner`, and the class is defined as:
25+
```python
26+
class MusicPlayer:
27+
def __init__(
28+
self, music_dir, volume_step=5, volume_update_interval=0.5
29+
):
30+
pygame.init()
31+
pygame.mixer.init()
32+
33+
self.music_dir = music_dir
34+
self.playlist = self.load_playlist()
35+
self.current_track_index = 0
36+
self.ch_mode = "sequence" # "sequence", "shuffle", "single"
37+
self.volume = 50 # Initial volume
38+
pygame.mixer.music.set_volume(self.volume / 100.0)
39+
self.is_playing = False
40+
self.last_gesture = None
41+
self.volume_timer = 0
42+
self.volume_step = volume_step
43+
self.volume_update_interval = volume_update_interval
44+
self.load_current_track()
45+
46+
def load_playlist(self):
47+
"""Loads all .mp3 and .wav files from the music directory into a playlist."""
48+
playlist = []
49+
for filename in os.listdir(self.music_dir):
50+
if filename.endswith((".mp3", ".wav")): # Add other audio formats if needed
51+
playlist.append(os.path.join(self.music_dir, filename))
52+
if not playlist:
53+
print(
54+
"Warning: no music file detected, please check ./assets/music directory"
55+
)
56+
return playlist
57+
58+
def load_current_track(self):
59+
"""Loads the current track from the playlist."""
60+
if 0 <= self.current_track_index < len(self.playlist):
61+
try:
62+
pygame.mixer.music.load(self.playlist[self.current_track_index])
63+
print(f"Loaded track: {self.playlist[self.current_track_index]}")
64+
except pygame.error as e:
65+
print(f"Error loading track: {self.playlist[self.current_track_index]}")
66+
print(e)
67+
# Handle the error (e.g., remove the track from the playlist, skip to the next)
68+
self.playlist.pop(self.current_track_index)
69+
self.current_track_index = max(
70+
0, self.current_track_index - 1
71+
) # prevent index out of range
72+
self.load_current_track() # recursively load next track
73+
74+
def play_music(self):
75+
"""Plays the current track."""
76+
if self.playlist:
77+
pygame.mixer.music.play()
78+
self.is_playing = True
79+
80+
def gesture_decode(self, gesture):
81+
current_time = time.time()
82+
83+
# Edge detection for ch_mode, play_next, pause, resume
84+
if gesture != self.last_gesture:
85+
if gesture == "OK":
86+
self.toggle_ch_mode()
87+
print(f"Change mode toggled to: {self.ch_mode}")
88+
elif gesture == "Like":
89+
self.play_next()
90+
print("Play next")
91+
elif gesture == "Return":
92+
if not self.is_playing:
93+
self.resume()
94+
print("Resume")
95+
elif gesture == "Pause":
96+
if self.is_playing:
97+
self.pause()
98+
print("Pause")
99+
100+
# Volume control with timer
101+
if gesture == "Right":
102+
if current_time - self.volume_timer > self.volume_update_interval:
103+
self.volume_down()
104+
self.volume_timer = current_time
105+
elif gesture == "Left":
106+
if current_time - self.volume_timer > self.volume_update_interval:
107+
self.volume_up()
108+
self.volume_timer = current_time
109+
110+
self.last_gesture = gesture
111+
112+
# Check if the current song has ended
113+
if self.is_playing and not pygame.mixer.music.get_busy():
114+
self.handle_song_end()
115+
116+
def toggle_ch_mode(self):
117+
if self.ch_mode == "sequence":
118+
self.ch_mode = "shuffle"
119+
elif self.ch_mode == "shuffle":
120+
self.ch_mode = "single"
121+
else:
122+
self.ch_mode = "sequence"
123+
124+
def volume_up(self):
125+
self.volume = min(100, self.volume + self.volume_step)
126+
pygame.mixer.music.set_volume(self.volume / 100.0)
127+
print(f"Volume up: {self.volume}")
128+
129+
def volume_down(self):
130+
self.volume = max(0, self.volume - self.volume_step)
131+
pygame.mixer.music.set_volume(self.volume / 100.0)
132+
print(f"Volume down: {self.volume}")
133+
134+
def pause(self):
135+
pygame.mixer.music.pause()
136+
self.is_playing = False
137+
138+
def resume(self):
139+
pygame.mixer.music.unpause()
140+
self.is_playing = True
141+
142+
def play_next(self):
143+
if self.ch_mode == "sequence":
144+
self.current_track_index = (self.current_track_index + 1) % len(
145+
self.playlist
146+
)
147+
elif self.ch_mode == "shuffle":
148+
self.current_track_index = random.randint(0, len(self.playlist) - 1)
149+
# "single" mode doesn't change the track
150+
self.load_current_track()
151+
self.play_music()
152+
153+
def handle_song_end(self):
154+
if self.ch_mode == "sequence":
155+
self.current_track_index = (self.current_track_index + 1) % len(
156+
self.playlist
157+
)
158+
elif self.ch_mode == "shuffle":
159+
self.current_track_index = random.randint(0, len(self.playlist) - 1)
160+
# In "single" mode, we just stop
161+
if self.ch_mode != "single":
162+
self.load_current_track()
163+
self.play_music()
164+
165+
def stop(self):
166+
pygame.mixer.music.stop()
167+
self.is_playing = False
168+
```
169+
## Create the function
170+
Then create a single function:
171+
```python
172+
def music_play(music_player, resent_gesture_queue, initial_volume=50):
173+
try:
174+
music_player.volume = initial_volume
175+
pygame.mixer.music.set_volume(music_player.volume / 100.0)
176+
music_player.play_music()
177+
178+
while True:
179+
if not resent_gesture_queue.empty():
180+
gesture = resent_gesture_queue.get()
181+
music_player.gesture_decode(gesture)
182+
time.sleep(0.1)
183+
except Exception as e:
184+
print(f"Error: {e}")
185+
finally:
186+
pygame.quit()
187+
```
188+
You may need to import external packages.
189+
190+
## Create a thread instance
191+
In `detective/runner/runner_engine`, create a `music_thread` instance.
192+
```python
193+
music_thread = threading.Thread(
194+
target=music_play,
195+
args=(
196+
music_player,
197+
resent_gesture_queue,
198+
),
199+
)
200+
```
201+
You also need to assign a name so that the config parser can load the plugin.
202+
```python
203+
def name2thread(name):
204+
return {
205+
"information_server": server_thread,
206+
"working_detect": working_detect_thread,
207+
"music_server": music_thread, # this is the new plugin
208+
"gpio_controller": gpio_controller_thread,
209+
"gesture_detection": gesture_detection_thread,
210+
"meditation_helper": meditation_helper_thread,
211+
}[name]
212+
```
213+
## Load your plugin in the `config.json` file
214+
Finally, configure your plugin in the `config.json`,
215+
```json
216+
{
217+
// ...
218+
"plugin_list": [
219+
// other plugins
220+
"music_server"
221+
]
222+
// ...
223+
}
224+
```
225+
and then you can launch your plugin!
226+

0 commit comments

Comments
 (0)