Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.

Commit 45acccb

Browse files
Refactor ArduinoSerial and RobustSerial modules to remove MOTOR handling, improve connection logic, and enhance logging. Update Sensor class to support ESP32 protocol with improved retry logic and motion monitoring. Modify AudioLLM to utilize the ollama library for LLM interactions. Enhance Personality module for better state management and behavior handling, including improved logging and error handling.
1 parent 3c2b2b5 commit 45acccb

25 files changed

+2504
-34247
lines changed

modules/actuators/piservo.py

Lines changed: 167 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,198 @@
22
from modules.config import Config
33
from time import sleep
44
from pubsub import pub
5+
import serial
6+
from modules.neopixel.neopx import get_shared_esp32_serial
7+
import threading
8+
import sys
9+
import os
10+
11+
# Add path to SerialShareManager
12+
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
13+
from modules.SerialShareManager import get_serial
14+
15+
# No longer need a global ESP32 connection - using the shared one
516

617
class PiServo:
718

819
def __init__(self, **kwargs):
920
"""
1021
PiServo class
11-
:param kwargs: pin, range, start
22+
:param kwargs: pin, range, start, protocol, id, name
1223
:param pin: GPIO pin number
1324
:param range: [min, max] angle range
1425
:param start: initial angle
26+
:param protocol: 'GPIO' (default) or 'ESP32'
27+
:param id: Servo ID for identification
28+
:param name: Servo name for logging and subscription
1529
1630
Install: pip install gpiozero
1731
18-
Subscribes to 'piservo:move' to move servo
32+
Subscribes to 'piservo:move' to move any servo
33+
- Argument: angle (int) - angle to move servo
34+
35+
Subscribes to 'piservo:move:{name}' to move specific servo
1936
- Argument: angle (int) - angle to move servo
2037
2138
Example:
22-
pub.sendMessage('piservo:move', angle=90)
39+
pub.sendMessage('piservo:move', angle=90)
40+
pub.sendMessage('piservo:move:ear', angle=45)
2341
"""
24-
2542
self.pin = kwargs.get('pin')
2643
self.range = kwargs.get('range')
2744
self.start = kwargs.get('start', 0)
45+
self.id = kwargs.get('id', 0)
46+
self.name = kwargs.get('name', f'servo_{self.id}')
2847
self.servo = None
48+
self.serial = None # Will be set if ESP32 protocol is used
49+
self.verbose_logging = kwargs.get('verbose_logging', False) # Add logging control
50+
51+
# Protokol desteği ekleme
52+
accepted_protocols = ['GPIO', 'ESP32']
53+
self.protocol = kwargs.get('protocol', 'GPIO')
54+
if self.protocol not in accepted_protocols:
55+
raise ValueError("Invalid protocol specified. Choose one of: " + ", ".join(accepted_protocols))
56+
57+
# ESP32 protocol handling with improved retry logic
58+
if self.protocol == 'ESP32':
59+
# Subscribe to both events - the original and the new global event
60+
pub.subscribe(self.on_serial_ready, 'esp32:serial_ready')
61+
pub.subscribe(self.on_serial_ready, 'esp32:serial_global_ready')
62+
63+
# Try to get the shared serial connection now - it might already be available
64+
self.serial = get_serial(f'servo_{self.name}')
65+
if not self.serial:
66+
# Try the old way too as fallback
67+
self.serial = get_shared_esp32_serial()
68+
69+
# Start retry timer with increased initial delay
70+
retry_timer = threading.Timer(5.0, self.retry_get_serial)
71+
retry_timer.daemon = True
72+
retry_timer.start()
73+
74+
# Main movement subscription
2975
pub.subscribe(self.move, 'piservo:move')
30-
# print(self.range)
31-
# self.move(0)
32-
# sleep(2)
33-
# self.move(self.range[0])
34-
# sleep(2)
35-
# self.move(self.range[1])
36-
# sleep(2)
37-
self.move(self.start)
3876

77+
# Servo-specific subscription
78+
specific_topic = f'piservo:move:{self.name}'
79+
pub.subscribe(self.move, specific_topic)
80+
81+
# Initialize to starting position with increased delay
82+
timer = threading.Timer(2.0, lambda: self.move(self.start))
83+
timer.daemon = True
84+
timer.start()
85+
86+
def retry_get_serial(self):
87+
"""Retry getting the serial connection with progressive delay"""
88+
if self.serial is None:
89+
# First try new method
90+
self.serial = get_serial(f'servo_{self.name}')
91+
if not self.serial:
92+
# Fall back to old method
93+
self.serial = get_shared_esp32_serial()
94+
95+
if self.serial:
96+
if self.verbose_logging:
97+
pub.sendMessage('log', msg=f'[SERVO] {self.name} connected to shared ESP32 serial on retry')
98+
self.move(self.start)
99+
else:
100+
# Progressive backoff - increase delay each time
101+
delay = min(10.0, 2.0 + len(self.name) % 3) # Vary delay slightly per instance
102+
retry_timer = threading.Timer(delay, self.retry_get_serial)
103+
retry_timer.daemon = True
104+
retry_timer.start()
39105

106+
def on_serial_ready(self):
107+
"""Called when the shared serial connection becomes available"""
108+
# Get the serial connection and log
109+
self.serial = get_shared_esp32_serial()
110+
if self.serial:
111+
if self.verbose_logging:
112+
pub.sendMessage('log', msg=f'[SERVO] {self.name} connected to shared ESP32 serial')
113+
# Move to start position once serial is available
114+
self.move(self.start)
115+
40116
def move(self, angle):
117+
"""
118+
Move servo to specified angle
119+
:param angle: Angle to move servo to
120+
"""
121+
# Açı değerini izin verilen aralıkta tut
122+
if self.range:
123+
if angle < self.range[0]:
124+
angle = self.range[0]
125+
if self.verbose_logging:
126+
pub.sendMessage('log', msg=f'[SERVO] {self.name}: Angle limited to minimum {self.range[0]}')
127+
elif angle > self.range[1]:
128+
angle = self.range[1]
129+
if self.verbose_logging:
130+
pub.sendMessage('log', msg=f'[SERVO] {self.name}: Angle limited to maximum {self.range[1]}')
131+
132+
# ESP32 protokolü kullanıyorsa
133+
if self.protocol == 'ESP32':
134+
self.send_servo_command(angle)
135+
return
136+
137+
# GPIO protokolü için orijinal kod
41138
if self.servo is None:
42139
self.servo = AngularServo(self.pin, min_angle=self.range[0], max_angle=self.range[1], initial_angle=self.start)
43140
self.servo.angle = angle # Changes the angle (to move the servo)
44141
sleep(1) # @TODO: Remove this sleep
45-
self.servo.detach() # Detaches the servo (to stop jitter)
142+
self.servo.detach() # Detaches the servo (to stop jitter)
143+
144+
def send_servo_command(self, angle):
145+
"""
146+
Send servo command to ESP32
147+
:param angle: Angle to move servo to
148+
"""
149+
if self.protocol != 'ESP32':
150+
return
151+
152+
if self.serial is None:
153+
if self.verbose_logging:
154+
pub.sendMessage('log', msg=f'[SERVO] {self.name}: Cannot send command - no serial connection')
155+
# Try to reconnect to serial
156+
self.serial = get_shared_esp32_serial()
157+
if self.serial is None:
158+
return
159+
160+
# Constrain angle to valid range
161+
if self.range:
162+
if angle < self.range[0]:
163+
angle = self.range[0]
164+
elif angle > self.range[1]:
165+
angle = self.range[1]
166+
167+
# Create and send command
168+
cmd = f"SERVO {self.pin} {angle}\n"
169+
# Only log actual movement commands when verbose is enabled
170+
if self.verbose_logging:
171+
pub.sendMessage('log', msg=f'[SERVO] {self.name}: Sending command: {cmd.strip()}')
172+
173+
try:
174+
self.serial.write(cmd.encode())
175+
sleep(0.1) # Brief pause for command processing
176+
except Exception as e:
177+
pub.sendMessage('log', msg=f'[SERVO] ERROR: Failed to send command to ESP32: {e}')
178+
self.serial = None # Reset the serial connection to trigger a reconnect
179+
180+
def detach(self):
181+
"""
182+
Detach the servo to prevent jitter
183+
"""
184+
if self.protocol == 'ESP32' and self.serial is not None:
185+
cmd = f"SERVO_DETACH {self.pin}\n"
186+
if self.verbose_logging:
187+
pub.sendMessage('log', msg=f'[SERVO] {self.name}: Detaching servo on pin {self.pin}')
188+
try:
189+
self.serial.write(cmd.encode())
190+
except Exception as e:
191+
pub.sendMessage('log', msg=f'[SERVO] ERROR: Failed to send detach command: {e}')
192+
elif self.servo:
193+
self.servo.detach()
194+
195+
def __del__(self):
196+
"""
197+
Clean up resources when object is destroyed
198+
"""
199+
self.detach()

modules/actuators/servo.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def __init__(self, **kwargs):
5656

5757
pub.subscribe(self.move, 'servo:' + self.identifier + ':mvabs')
5858
pub.subscribe(self.move_relative, 'servo:' + self.identifier + ':mv')
59+
# Genel animasyon komutlarını dinle
60+
pub.subscribe(self.handle_servo_animation, 'servo:move')
5961

6062
def __del__(self):
6163
pass #self.reset()
@@ -186,3 +188,25 @@ def translate(self, value):
186188

187189
# Convert the 0-1 range into a value in the right range.
188190
return self.range[0] + (valueScaled * rightSpan)
191+
192+
def handle_servo_animation(self, data):
193+
"""
194+
DeskGUI veya CommandReceiver'dan gelen genel animasyon komutlarını işler.
195+
:param data: {'animation': animasyon_adı, 'repeat': tekrar_sayısı}
196+
"""
197+
animation = data.get('animation', '').upper()
198+
repeat = data.get('repeat', 1)
199+
# Sadece bu servo ilgili animasyonda rol oynuyorsa hareket etsin
200+
# Örnek: pan servosu HEAD_LEFT animasyonunda hareket eder
201+
if animation == 'HEAD_LEFT' and self.identifier == 'pan':
202+
for _ in range(repeat):
203+
self.move(0) # Sola bak (örnek değer)
204+
sleep(0.5)
205+
self.move(50) # Ortaya dön
206+
elif animation == 'HEAD_RIGHT' and self.identifier == 'pan':
207+
for _ in range(repeat):
208+
self.move(100) # Sağa bak (örnek değer)
209+
sleep(0.5)
210+
self.move(50) # Ortaya dön
211+
# Diğer animasyonlar için benzer şekilde ekleyebilirsiniz
212+
# İlgili servo ve animasyon eşleşmiyorsa hiçbir şey yapmaz

modules/animate.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,17 @@ def animate(self, action):
3737
cmd = list(step.keys())[0]
3838
args = list(step.values())
3939
if 'servo:' in cmd:
40-
pub.sendMessage(cmd, percentage=args[0])
40+
# Hatalı veya eksik veri varsa atla ve logla
41+
try:
42+
val = args[0]
43+
if val is None or (isinstance(val, str) and not val.strip()):
44+
pub.sendMessage('log:error', msg=f"[Animate] Invalid servo value in animation '{action}': {cmd} -> {val}")
45+
continue
46+
val = float(val)
47+
except Exception as e:
48+
pub.sendMessage('log:error', msg=f"[Animate] Exception parsing servo value in animation '{action}': {cmd} -> {args[0]} | {e}")
49+
continue
50+
pub.sendMessage(cmd, percentage=val)
4151
elif 'sleep' == cmd:
4252
sleep(args[0])
4353
elif 'animate' == cmd:

modules/archived/opencv/faces.py

Lines changed: 0 additions & 105 deletions
This file was deleted.

0 commit comments

Comments
 (0)