hackerbot 0.2.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/__init__.py +33 -0
- hackerbot/arm/__init__.py +77 -0
- hackerbot/arm/gripper.py +55 -0
- hackerbot/base/__init__.py +223 -0
- hackerbot/base/maps.py +147 -0
- hackerbot/core.py +120 -0
- hackerbot/examples/keyboard_teleop_examples/AI_ELITE_teleop.py +219 -0
- hackerbot/examples/keyboard_teleop_examples/AI_PRO_teleop.py +180 -0
- hackerbot/examples/keyboard_teleop_examples/arm_teleop.py +230 -0
- hackerbot/examples/keyboard_teleop_examples/base_teleop.py +206 -0
- hackerbot/examples/keyboard_teleop_examples/head_teleop.py +170 -0
- hackerbot/head/__init__.py +61 -0
- hackerbot/head/eyes.py +41 -0
- hackerbot/utils/hackerbot_helper.py +142 -0
- hackerbot/utils/serial_helper.py +156 -0
- hackerbot-0.2.0.dist-info/METADATA +67 -0
- hackerbot-0.2.0.dist-info/RECORD +20 -0
- hackerbot-0.2.0.dist-info/WHEEL +5 -0
- hackerbot-0.2.0.dist-info/licenses/LICENSE +21 -0
- hackerbot-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,206 @@
|
|
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.04.01
|
10
|
+
#
|
11
|
+
# This script is an example of how to use the base teleop behavior.
|
12
|
+
#
|
13
|
+
# Special thanks to the following for their code contributions to this codebase:
|
14
|
+
# Allen Chien - https://github.com/AllenChienXXX
|
15
|
+
################################################################################
|
16
|
+
|
17
|
+
|
18
|
+
from hackerbot import Hackerbot
|
19
|
+
import time
|
20
|
+
import os
|
21
|
+
|
22
|
+
import sys, termios, atexit
|
23
|
+
from select import select
|
24
|
+
|
25
|
+
class KBHit:
|
26
|
+
|
27
|
+
def __init__(self):
|
28
|
+
'''Creates a KBHit object that you can call to do various keyboard things.
|
29
|
+
'''
|
30
|
+
# Save the terminal settings
|
31
|
+
self.fd = sys.stdin.fileno()
|
32
|
+
self.new_term = termios.tcgetattr(self.fd)
|
33
|
+
self.old_term = termios.tcgetattr(self.fd)
|
34
|
+
|
35
|
+
# New terminal setting unbuffered
|
36
|
+
self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
|
37
|
+
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)
|
38
|
+
|
39
|
+
# Support normal-terminal reset at exit
|
40
|
+
atexit.register(self.set_normal_term)
|
41
|
+
|
42
|
+
|
43
|
+
def set_normal_term(self):
|
44
|
+
''' Resets to normal terminal. On Windows this is a no-op.
|
45
|
+
'''
|
46
|
+
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)
|
47
|
+
|
48
|
+
|
49
|
+
def getch(self):
|
50
|
+
''' Returns a keyboard character after kbhit() has been called.
|
51
|
+
'''
|
52
|
+
ch1 = sys.stdin.read(1)
|
53
|
+
if ch1 == '\x1b':
|
54
|
+
# special key pressed
|
55
|
+
ch2 = sys.stdin.read(1)
|
56
|
+
ch3 = sys.stdin.read(1)
|
57
|
+
ch = ch1 + ch2 + ch3
|
58
|
+
else:
|
59
|
+
# not a special key
|
60
|
+
ch = ch1
|
61
|
+
while sys.stdin in select([sys.stdin], [], [], 0)[0]:
|
62
|
+
sys.stdin.read(1)
|
63
|
+
return ch
|
64
|
+
|
65
|
+
|
66
|
+
def kbhit(self):
|
67
|
+
''' Returns True if keyboard character was hit, False otherwise.
|
68
|
+
'''
|
69
|
+
dr,dw,de = select([sys.stdin], [], [], 0)
|
70
|
+
while sys.stdin in select([sys.stdin], [], [], 0)[0]:
|
71
|
+
sys.stdin.read(1)
|
72
|
+
return dr != []
|
73
|
+
|
74
|
+
class BaseTeleop:
|
75
|
+
def __init__(self):
|
76
|
+
self.kb = KBHit()
|
77
|
+
|
78
|
+
self.robot = Hackerbot()
|
79
|
+
self.robot.base.initialize()
|
80
|
+
|
81
|
+
# Modify movement parameters
|
82
|
+
self.step_size = 0.2 # mm
|
83
|
+
self.max_l_step_size = 300.0 # mm/s
|
84
|
+
self.max_r_step_size = 90.0 # degree/s
|
85
|
+
|
86
|
+
self.stop = False
|
87
|
+
self.last_key = None # Track last keypress
|
88
|
+
|
89
|
+
# Print initial instructions to terminal
|
90
|
+
self.print_terminal_instructions()
|
91
|
+
|
92
|
+
def print_terminal_instructions(self):
|
93
|
+
"""Print instructions to the terminal"""
|
94
|
+
os.system('clear' if os.name == 'posix' else 'cls')
|
95
|
+
print("\n" + "="*10 + " Robot Teleop Controls " + "="*10 + "\r")
|
96
|
+
print("\nMoving controls:\r")
|
97
|
+
print(" ↑ / ↓ : forward/backward | ← / → : rotate left/right | space : stop\r")
|
98
|
+
print("o/p : increase/decrease step size by 10%\r")
|
99
|
+
print("\nCTRL-C or '0' to quit\r")
|
100
|
+
print("=" * 43 + "\r")
|
101
|
+
|
102
|
+
def update_display(self):
|
103
|
+
"""Update step size in place without adding new lines"""
|
104
|
+
sys.stdout.write(f"\rCurrent step size: {self.step_size:.2f}m\r")
|
105
|
+
sys.stdout.flush() # Ensure immediate update
|
106
|
+
|
107
|
+
def get_command(self):
|
108
|
+
key = None
|
109
|
+
# Read keyboard input
|
110
|
+
if self.kb.kbhit() is not None:
|
111
|
+
key = self.kb.getch()
|
112
|
+
while sys.stdin in select([sys.stdin], [], [], 0)[0]:
|
113
|
+
sys.stdin.read(1)
|
114
|
+
|
115
|
+
if key == self.last_key:
|
116
|
+
self.last_key = None
|
117
|
+
return None, None
|
118
|
+
|
119
|
+
self.last_key = key # Update last key
|
120
|
+
|
121
|
+
# Check for quit conditions
|
122
|
+
if key == '0': # '0' key to quit
|
123
|
+
self.stop = True
|
124
|
+
return None, None
|
125
|
+
|
126
|
+
if key == 'o':
|
127
|
+
self.step_size += 0.1
|
128
|
+
elif key == 'p':
|
129
|
+
self.step_size -= 0.1
|
130
|
+
if key in ['\x1b[A', '\x1b[B', '\x1b[D', '\x1b[C', ' ']:
|
131
|
+
l_vel, r_vel = self.get_base_command_from_key(key)
|
132
|
+
else:
|
133
|
+
l_vel = None
|
134
|
+
r_vel = None
|
135
|
+
|
136
|
+
return l_vel, r_vel
|
137
|
+
else:
|
138
|
+
self.last_key = None
|
139
|
+
return 0.0, 0.0
|
140
|
+
|
141
|
+
def get_base_command_from_key(self, key):
|
142
|
+
if key == '\x1b[A': # Up arrow - Forward
|
143
|
+
l_vel = self.max_l_step_size * self.step_size
|
144
|
+
r_vel = 0.0
|
145
|
+
elif key == '\x1b[B': # Down arrow - Backward
|
146
|
+
l_vel = -self.max_l_step_size * self.step_size
|
147
|
+
r_vel = 0.0
|
148
|
+
elif key == '\x1b[D': # Left arrow - Rotate left
|
149
|
+
l_vel = 0.0
|
150
|
+
r_vel = self.max_r_step_size * self.step_size
|
151
|
+
elif key == '\x1b[C': # Right arrow - Rotate right
|
152
|
+
l_vel = 0.0
|
153
|
+
r_vel = -self.max_r_step_size * self.step_size
|
154
|
+
elif key == ' ': # Space - Stop
|
155
|
+
l_vel = 0.0
|
156
|
+
r_vel = 0.0
|
157
|
+
else:
|
158
|
+
l_vel = None
|
159
|
+
r_vel = None
|
160
|
+
return l_vel, r_vel
|
161
|
+
|
162
|
+
def run(self):
|
163
|
+
while not self.stop:
|
164
|
+
l_vel, r_vel = self.get_command()
|
165
|
+
if l_vel is not None and r_vel is not None:
|
166
|
+
respone = self.robot.base.drive(l_vel, r_vel, block=False)
|
167
|
+
if respone == False:
|
168
|
+
break
|
169
|
+
l_vel = None
|
170
|
+
r_vel = None
|
171
|
+
# time.sleep(0.01)
|
172
|
+
self.update_display()
|
173
|
+
def cleanup(self):
|
174
|
+
"""Cleanup method to properly shut down the robot and restore terminal settings"""
|
175
|
+
try:
|
176
|
+
# Restore terminal settings
|
177
|
+
self.kb.set_normal_term()
|
178
|
+
# Destroy the robot connection
|
179
|
+
self.robot.base.destroy(auto_dock=True)
|
180
|
+
|
181
|
+
except Exception as e:
|
182
|
+
print(f"\nError during cleanup: {e}")
|
183
|
+
# Try to restore terminal settings even if there's an error
|
184
|
+
try:
|
185
|
+
self.kb.set_normal_term()
|
186
|
+
except:
|
187
|
+
pass
|
188
|
+
|
189
|
+
def __del__(self):
|
190
|
+
"""Destructor to ensure cleanup is called"""
|
191
|
+
self.cleanup()
|
192
|
+
|
193
|
+
|
194
|
+
# Main entry point
|
195
|
+
if __name__ == '__main__':
|
196
|
+
teleop = None
|
197
|
+
try:
|
198
|
+
teleop = BaseTeleop()
|
199
|
+
teleop.run()
|
200
|
+
except KeyboardInterrupt:
|
201
|
+
print("\nShutting down...")
|
202
|
+
except Exception as e:
|
203
|
+
print(f"\nError: {e}")
|
204
|
+
finally:
|
205
|
+
if teleop:
|
206
|
+
teleop.cleanup()
|
@@ -0,0 +1,170 @@
|
|
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.04.01
|
10
|
+
#
|
11
|
+
# This script is an example of how to use the head teleop behavior.
|
12
|
+
#
|
13
|
+
# Special thanks to the following for their code contributions to this codebase:
|
14
|
+
# Allen Chien - https://github.com/AllenChienXXX
|
15
|
+
################################################################################
|
16
|
+
|
17
|
+
|
18
|
+
from hackerbot import Hackerbot
|
19
|
+
import time
|
20
|
+
import os
|
21
|
+
|
22
|
+
import sys
|
23
|
+
from select import select
|
24
|
+
from base_teleop import KBHit
|
25
|
+
|
26
|
+
class HeadTeleop:
|
27
|
+
def __init__(self):
|
28
|
+
self.kb = KBHit()
|
29
|
+
|
30
|
+
self.robot = Hackerbot()
|
31
|
+
self.robot.head.set_idle_mode(False)
|
32
|
+
|
33
|
+
# Modify movement parameters
|
34
|
+
self.joint_speed = 50
|
35
|
+
self.step_size = 0.2 # mm
|
36
|
+
|
37
|
+
self.yaw = 180
|
38
|
+
self.pitch = 180
|
39
|
+
|
40
|
+
self.stop = False
|
41
|
+
self.last_key = None # Track last keypress
|
42
|
+
|
43
|
+
# Print initial instructions to terminal
|
44
|
+
self.print_terminal_instructions()
|
45
|
+
|
46
|
+
def print_terminal_instructions(self):
|
47
|
+
"""Print static instructions to the terminal"""
|
48
|
+
os.system('clear' if os.name == 'posix' else 'cls')
|
49
|
+
print("\n" + "="*10 + " Robot Head Teleop Controls " + "="*10 + "\r")
|
50
|
+
print(" u/i : Yaw rotate L/R")
|
51
|
+
print(" j/k : Pitch rotate B/F")
|
52
|
+
|
53
|
+
print("\nOther Controls:")
|
54
|
+
print(" p/o : increase/decrease step size")
|
55
|
+
print(" -/+ : decrease/increase speed")
|
56
|
+
print("\nCTRL-C or 0 to quit")
|
57
|
+
|
58
|
+
# Reserve space for dynamic updates
|
59
|
+
print("\n" + "=" * 43 + "\r")
|
60
|
+
|
61
|
+
def update_display(self):
|
62
|
+
"""Update step size and speed in place without adding new lines"""
|
63
|
+
sys.stdout.write(f"\rCurrent step size: {self.step_size:.1f} | Current speed: {self.joint_speed}% ")
|
64
|
+
sys.stdout.flush() # Ensure immediate update
|
65
|
+
|
66
|
+
|
67
|
+
def get_head_command(self):
|
68
|
+
key = None
|
69
|
+
# Read keyboard input
|
70
|
+
if self.kb.kbhit() is not None:
|
71
|
+
key = self.kb.getch()
|
72
|
+
|
73
|
+
while sys.stdin in select([sys.stdin], [], [], 0)[0]:
|
74
|
+
sys.stdin.read(1)
|
75
|
+
|
76
|
+
if key == self.last_key:
|
77
|
+
self.last_key = None
|
78
|
+
return None, None
|
79
|
+
|
80
|
+
self.last_key = key
|
81
|
+
|
82
|
+
# Check for quit conditions
|
83
|
+
if key == '0': # ESC or Ctrl-C
|
84
|
+
self.stop = True
|
85
|
+
return None, None
|
86
|
+
|
87
|
+
if key == 'o':
|
88
|
+
self.step_size += 0.1
|
89
|
+
elif key == 'p':
|
90
|
+
self.step_size -= 0.1
|
91
|
+
if key == '-':
|
92
|
+
self.joint_speed -= 10
|
93
|
+
if self.joint_speed < 0:
|
94
|
+
self.joint_speed = 0
|
95
|
+
elif key == '+':
|
96
|
+
self.joint_speed += 10
|
97
|
+
if self.joint_speed > 100:
|
98
|
+
self.joint_speed = 100
|
99
|
+
|
100
|
+
return self.get_head_value_from_key(key)
|
101
|
+
else:
|
102
|
+
self.last_key = None
|
103
|
+
return None, None
|
104
|
+
|
105
|
+
def get_head_value_from_key(self, key):
|
106
|
+
# Joint controls
|
107
|
+
if key == 'u': # Joint 1 left
|
108
|
+
self.yaw += self.step_size*100
|
109
|
+
self.yaw = max(100, min(260, self.yaw))
|
110
|
+
elif key == 'i': # Joint 1 right
|
111
|
+
self.yaw -= self.step_size*100
|
112
|
+
self.yaw = max(100, min(260, self.yaw))
|
113
|
+
elif key == 'j': # Joint 2 left
|
114
|
+
self.pitch += self.step_size*100
|
115
|
+
self.pitch = max(150, min(250, self.pitch))
|
116
|
+
elif key == 'k': # Joint 2 right
|
117
|
+
self.pitch -= self.step_size*100
|
118
|
+
self.pitch = max(150, min(250, self.pitch))
|
119
|
+
else:
|
120
|
+
return None, None
|
121
|
+
|
122
|
+
return self.yaw, self.pitch
|
123
|
+
|
124
|
+
def run(self):
|
125
|
+
while not self.stop:
|
126
|
+
y, p = self.get_head_command()
|
127
|
+
if y is not None and p is not None:
|
128
|
+
response = self.robot.head.look(y, p, self.joint_speed)
|
129
|
+
|
130
|
+
time.sleep(0.2)
|
131
|
+
if response == False:
|
132
|
+
break
|
133
|
+
self.update_display()
|
134
|
+
|
135
|
+
def cleanup(self):
|
136
|
+
"""Cleanup method to properly shut down the robot and restore terminal settings"""
|
137
|
+
try:
|
138
|
+
# Restore terminal settings
|
139
|
+
self.kb.set_normal_term()
|
140
|
+
# Dock the robot
|
141
|
+
self.robot.head.look(180,180,50)
|
142
|
+
# Destroy the robot connection
|
143
|
+
self.robot.destroy()
|
144
|
+
|
145
|
+
except Exception as e:
|
146
|
+
print(f"\nError during cleanup: {e}")
|
147
|
+
# Try to restore terminal settings even if there's an error
|
148
|
+
try:
|
149
|
+
self.kb.set_normal_term()
|
150
|
+
except:
|
151
|
+
pass
|
152
|
+
|
153
|
+
def __del__(self):
|
154
|
+
"""Destructor to ensure cleanup is called"""
|
155
|
+
self.cleanup()
|
156
|
+
|
157
|
+
|
158
|
+
# Main entry point
|
159
|
+
if __name__ == '__main__':
|
160
|
+
teleop = None
|
161
|
+
try:
|
162
|
+
teleop = HeadTeleop()
|
163
|
+
teleop.run()
|
164
|
+
except KeyboardInterrupt:
|
165
|
+
print("\nShutting down...")
|
166
|
+
except Exception as e:
|
167
|
+
print(f"\nError: {e}")
|
168
|
+
finally:
|
169
|
+
if teleop:
|
170
|
+
teleop.cleanup()
|
@@ -0,0 +1,61 @@
|
|
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.04.07
|
10
|
+
#
|
11
|
+
# This module contains the Head component of the hackerbot
|
12
|
+
#
|
13
|
+
# Special thanks to the following for their code contributions to this codebase:
|
14
|
+
# Allen Chien - https://github.com/AllenChienXXX
|
15
|
+
################################################################################
|
16
|
+
|
17
|
+
|
18
|
+
from hackerbot.utils.hackerbot_helper import HackerbotHelper
|
19
|
+
from .eyes import Eyes
|
20
|
+
|
21
|
+
class Head():
|
22
|
+
def __init__(self, controller: HackerbotHelper):
|
23
|
+
self._controller = controller
|
24
|
+
self.idle_mode = True
|
25
|
+
|
26
|
+
self.setup()
|
27
|
+
self.eyes = Eyes(self._controller)
|
28
|
+
|
29
|
+
def setup(self):
|
30
|
+
if not self._controller._dynamixel_controller_attached:
|
31
|
+
self._controller.log_warning("Dynamixel controller not attached, can't control head.")
|
32
|
+
if not self._controller._audio_mouth_eyes_attached:
|
33
|
+
self._controller.log_warning("Audio mouth and eyes not attached, can't control eyes.")
|
34
|
+
|
35
|
+
self.set_idle_mode(True)
|
36
|
+
|
37
|
+
# float: yaw - Unit is in degrees (eg. 180 degrees). Valid values are in the range of 100.0 to 260.0
|
38
|
+
# float: pitch - Unit is in degrees (eg. 180 degrees). Valid values are in the range of 150.0 to 250.0
|
39
|
+
# int: speed - Unitless. Valid values are integers in the range of 6 (slow) to 70 (fast)
|
40
|
+
def look(self, yaw, pitch, speed):
|
41
|
+
try:
|
42
|
+
self.set_idle_mode(False)
|
43
|
+
self._controller.send_raw_command(f"H_LOOK, {yaw}, {pitch}, {speed}")
|
44
|
+
# Not fetching json response since machine mode not implemented
|
45
|
+
return True
|
46
|
+
except Exception as e:
|
47
|
+
self._controller.log_error(f"Error in head:look: {e}")
|
48
|
+
return False
|
49
|
+
|
50
|
+
def set_idle_mode(self, mode):
|
51
|
+
try:
|
52
|
+
if mode:
|
53
|
+
self._controller.send_raw_command("H_IDLE, 1")
|
54
|
+
else:
|
55
|
+
self._controller.send_raw_command("H_IDLE, 0")
|
56
|
+
# Not fetching json response since machine mode not implemented
|
57
|
+
self.idle_mode = mode
|
58
|
+
return True
|
59
|
+
except Exception as e:
|
60
|
+
self._controller.log_error(f"Error in head:set_idle_mode: {e}")
|
61
|
+
return False
|
hackerbot/head/eyes.py
ADDED
@@ -0,0 +1,41 @@
|
|
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.04.07
|
10
|
+
#
|
11
|
+
# This module contains the Eyes component of the hackerbot
|
12
|
+
#
|
13
|
+
# Special thanks to the following for their code contributions to this codebase:
|
14
|
+
# Allen Chien - https://github.com/AllenChienXXX
|
15
|
+
################################################################################
|
16
|
+
|
17
|
+
|
18
|
+
from hackerbot.utils.hackerbot_helper import HackerbotHelper
|
19
|
+
|
20
|
+
class Eyes():
|
21
|
+
def __init__(self, controller: HackerbotHelper):
|
22
|
+
self._controller = controller
|
23
|
+
|
24
|
+
def gaze(self, x, y):
|
25
|
+
"""
|
26
|
+
Move the eyes to the specified position in the view.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
x (float): x position between -1.0 and 1.0
|
30
|
+
y (float): y position between -1.0 and 1.0
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
bool: Whether the command was successful
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
self._controller.send_raw_command(f"H_GAZE,{x},{y}")
|
37
|
+
# Not fetching json response since machine mode not implemented
|
38
|
+
return True
|
39
|
+
except Exception as e:
|
40
|
+
self._controller.log_error(f"Error in eyes:gaze: {e}")
|
41
|
+
return False
|
@@ -0,0 +1,142 @@
|
|
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.04.07
|
10
|
+
#
|
11
|
+
# This module contains the HackerbotHelper class, which is a subclass of SerialHelper.
|
12
|
+
# It contains the fields that will be share among higher level classes.
|
13
|
+
#
|
14
|
+
# Special thanks to the following for their code contributions to this codebase:
|
15
|
+
# Allen Chien - https://github.com/AllenChienXXX
|
16
|
+
################################################################################
|
17
|
+
|
18
|
+
|
19
|
+
from .serial_helper import SerialHelper
|
20
|
+
import time
|
21
|
+
import logging
|
22
|
+
|
23
|
+
class HackerbotHelper(SerialHelper):
|
24
|
+
def __init__(self, port=None, board=None, verbose_mode=False):
|
25
|
+
self._error_msg = ""
|
26
|
+
self._warning_msg = ""
|
27
|
+
self._v_mode = verbose_mode
|
28
|
+
self._main_controller_init = False # Ensure this is set before exception handling
|
29
|
+
|
30
|
+
self._json_mode = False
|
31
|
+
|
32
|
+
self._main_controller_attached = False
|
33
|
+
self._temperature_sensor_attached = False
|
34
|
+
|
35
|
+
self._left_tof_attached = False
|
36
|
+
self._right_tof_attached = False
|
37
|
+
|
38
|
+
self._tofs_enabled = False
|
39
|
+
|
40
|
+
self._base_init = False
|
41
|
+
self._driver_mode = False
|
42
|
+
|
43
|
+
self._audio_mouth_eyes_attached = False
|
44
|
+
self._dynamixel_controller_attached = False
|
45
|
+
|
46
|
+
self._arm_attached = False
|
47
|
+
|
48
|
+
self._port = port
|
49
|
+
self._board = board
|
50
|
+
|
51
|
+
self.setup()
|
52
|
+
|
53
|
+
|
54
|
+
def setup(self):
|
55
|
+
"""
|
56
|
+
Initialize the main controller.
|
57
|
+
|
58
|
+
If self._port and self._board are None, this method will find the first available serial port and use it to initialize the main controller.
|
59
|
+
Otherwise, it will use the provided port and board to initialize.
|
60
|
+
After initialization, it will set the main controller to JSON mode and enable the TOFs.
|
61
|
+
This setup ensures json mode are set and controller is initialized properly.
|
62
|
+
If an exception occurs during initialization, it will raise an exception with the error message.
|
63
|
+
"""
|
64
|
+
try:
|
65
|
+
if self._port is None or self._board is None:
|
66
|
+
super().__init__()
|
67
|
+
self._board, self._port = super().get_board_and_port()
|
68
|
+
else:
|
69
|
+
super().__init__(self._port, self._board)
|
70
|
+
|
71
|
+
self._main_controller_init = True
|
72
|
+
self.set_json_mode(True)
|
73
|
+
# self.set_TOFs(True)
|
74
|
+
except Exception as e:
|
75
|
+
raise Exception(f"Error in setting up hackerbot helper: {e}")
|
76
|
+
|
77
|
+
# Activate JSON mode
|
78
|
+
def set_json_mode(self, mode):
|
79
|
+
try:
|
80
|
+
if mode == True:
|
81
|
+
super().send_raw_command("JSON, 1")
|
82
|
+
time.sleep(0.1) # Short sleep to process json response
|
83
|
+
response = super().get_json_from_command("json")
|
84
|
+
if response is None:
|
85
|
+
raise Exception("Failed to set json mode to: ", mode)
|
86
|
+
else:
|
87
|
+
super().send_raw_command("JSON, 0")
|
88
|
+
self._json_mode = mode
|
89
|
+
except Exception as e:
|
90
|
+
raise Exception(f"Error in set_json_mode: {e}")
|
91
|
+
|
92
|
+
#Set TOFs
|
93
|
+
def set_TOFs(self, mode):
|
94
|
+
try:
|
95
|
+
if not self._left_tof_attached or not self._right_tof_attached:
|
96
|
+
raise Exception("TOFs not attached")
|
97
|
+
if mode == True:
|
98
|
+
super().send_raw_command("TOFS, 1")
|
99
|
+
else:
|
100
|
+
super().send_raw_command("TOFS, 0")
|
101
|
+
time.sleep(0.1) # Short sleep to process json response
|
102
|
+
response = super().get_json_from_command("tofs")
|
103
|
+
if response is None:
|
104
|
+
raise Exception("TOFs activation failed")
|
105
|
+
self._tofs_enabled = mode
|
106
|
+
except Exception as e:
|
107
|
+
raise Exception(f"Error in set_TOFs: {e}")
|
108
|
+
|
109
|
+
def get_current_action(self):
|
110
|
+
return super().get_state()
|
111
|
+
|
112
|
+
def get_error(self):
|
113
|
+
# Serial error should be priority
|
114
|
+
if super().get_ser_error() is not None:
|
115
|
+
return super().get_ser_error()
|
116
|
+
else:
|
117
|
+
return self._error_msg
|
118
|
+
|
119
|
+
def log_error(self, error):
|
120
|
+
if self._v_mode:
|
121
|
+
logging.error(error)
|
122
|
+
self._error_msg = error
|
123
|
+
|
124
|
+
def log_warning(self, warning):
|
125
|
+
if self._v_mode:
|
126
|
+
logging.warning(warning)
|
127
|
+
self._warning_msg = warning
|
128
|
+
|
129
|
+
def check_controller_init(self):
|
130
|
+
if not self._main_controller_init:
|
131
|
+
raise Exception("Main controller not initialized.")
|
132
|
+
if not self._json_mode:
|
133
|
+
raise Exception("JSON mode not enabled.")
|
134
|
+
|
135
|
+
def destroy(self):
|
136
|
+
try:
|
137
|
+
super().disconnect_serial()
|
138
|
+
self._main_controller_init = False
|
139
|
+
return True
|
140
|
+
except Exception as e:
|
141
|
+
self.log_error(f"Error in destroy: {e}")
|
142
|
+
return False
|