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

Commit 395cc3c

Browse files
Merge pull request #10 from SentryCoderDev/feature/Jetson-Integration
First stage of Jetson integration
2 parents 8c0e6cc + 292200c commit 395cc3c

File tree

10 files changed

+196
-127
lines changed

10 files changed

+196
-127
lines changed

config/jetservo.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
jetservo:
2+
conf:
3+
servo1: { id: 1, pin: 17, range: [0, 180], start: 0 }
4+
servo2: { id: 2, pin: 18, range: [0, 180], start: 0 }
5+
docs:
6+
description: "The jetservo module is used to control servos on the Jetson Nano."
7+
events:
8+
sub:
9+
- jetservo:move:
10+
description: "Move both servos to the same absolute position."
11+
args:
12+
- angle: integer
13+
- jetservo:inversermove:
14+
description: "Move servos in inverse directions."
15+
args:
16+
- angle: integer
17+
- jetservo:left_move:
18+
description: "Move the left servo to an absolute position."
19+
args:
20+
- angle: integer
21+
- jetservo:right_move:
22+
description: "Move the right servo to an absolute position."
23+
args:
24+
- angle: integer

config/piservo.yml

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

main.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
# Import modules
2020
from modules.config import Config
2121
from modules.actuators.servo import Servo
22-
from modules.actuators.piservo import PiServo
22+
from modules.actuators.jetservo import JetsonServo
2323
from modules.animate import Animate
2424
# from modules.power import Power
2525
# from modules.keyboard import Keyboard
2626
# from modules.gamepad import Gamepad
27-
from modules.sensor import Sensor
27+
+from modules.jetsensor import JetsonMotionSensor
2828
# try:
2929
# from modules.hotword import HotWord
3030
# except ModuleNotFoundError as e:
@@ -40,7 +40,7 @@
4040
# from modules.battery import Battery
4141
from modules.braillespeak import Braillespeak
4242
from modules.buzzer import Buzzer
43-
from modules.pitemperature import PiTemperature
43+
+from modules.jettemperature import JetsonTemperature
4444
from modules.osc_module import StartOSCServer
4545

4646
from modules.translator import Translator
@@ -88,11 +88,11 @@ def main():
8888
s = servo_conf[key]
8989
servos[key] = Servo(s['pin'], key, s['range'], s['id'], start_pos=s['start'])
9090

91-
piservos = dict()
92-
piservo_conf = Config.get('piservo','conf')
93-
for key in piservo_conf:
94-
s = piservo_conf[key]
95-
piservos[key] = PiServo(s['pin'], s['range'], start_pos=s['start'])
91+
jetservos = dict()
92+
jetservo_conf = Config.get('jetservo','conf')
93+
for key in jetservo_conf:
94+
s = jetservo_conf[key]
95+
jetservos[key] = JetsonServo(s['pin'], s['range'], start_pos=s['start'])
9696

9797
# pub.sendMessage('log', msg="[Main] Starting pan test")
9898
# pub.sendMessage('servo:pan:mvabs', percentage=0)
@@ -118,7 +118,7 @@ def main():
118118
# tts = TTS(translator=translator)
119119

120120
if Config.get('motion','pin') != '':
121-
motion = Sensor(Config.get('motion','pin'))
121+
motion = JetsonMotionSensor(Config.get('motion','pin'))
122122

123123
pub.sendMessage('tts', msg='I am awake.')
124124
pub.sendMessage('speak', msg='hi')
@@ -163,7 +163,7 @@ def main():
163163
# keyboard = Keyboard()
164164

165165
# gamepad = Gamepad()
166-
temp = PiTemperature()
166+
temp = JetsonTemperature()
167167

168168
# Voice
169169
# if Config.get('hotword', 'model') != '':

modules/actuators/jetservo.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Jetson.GPIO as GPIO
2+
from time import sleep
3+
from pubsub import pub
4+
5+
class JetsonServo:
6+
def __init__(self, left_pin, right_pin, **kwargs):
7+
self.left_pin = left_pin
8+
self.right_pin = right_pin
9+
self.start = kwargs.get('start_pos', 0)
10+
self.setup_gpio()
11+
self.left_servo = GPIO.PWM(left_pin, 50) # Left servo pin, 50Hz
12+
self.right_servo = GPIO.PWM(right_pin, 50) # Right servo pin, 50Hz
13+
self.left_servo.start(self.start)
14+
self.right_servo.start(self.start)
15+
16+
pub.subscribe(self.move, 'jetservo:move') # All jetservo movement
17+
pub.subscribe(self.invert_move, 'jetservo:inversermove') # Inverse jetservo movement
18+
pub.subscribe(self.move_left, 'jetservo:left_move') # Left jetservo
19+
pub.subscribe(self.move_right, 'jetservo:right_move') # Right jetservo
20+
21+
def setup_gpio(self):
22+
GPIO.setmode(GPIO.BCM) # Set GPIO numbering mode to BCM
23+
GPIO.setup(self.left_pin, GPIO.OUT) # Set left pin as output
24+
GPIO.setup(self.right_pin, GPIO.OUT) # Set right pin as output
25+
26+
def move(self, angle):
27+
left_duty = self.angle_to_duty(angle)
28+
right_duty = self.angle_to_duty(angle)
29+
self.left_servo.ChangeDutyCycle(left_duty)
30+
self.right_servo.ChangeDutyCycle(right_duty)
31+
sleep(1) # @TODO: Remove this sleep
32+
33+
def invert_move(self, angle):
34+
left_duty = self.angle_to_duty(angle)
35+
right_duty = self.angle_to_duty(180 - angle) # Inverse movement for right servo
36+
self.left_servo.ChangeDutyCycle(left_duty)
37+
self.right_servo.ChangeDutyCycle(right_duty)
38+
sleep(1) # @TODO: Remove this sleep
39+
40+
def move_left(self, angle):
41+
duty = self.angle_to_duty(angle)
42+
GPIO.output(self.left_pin, GPIO.HIGH) # Set left servo pin to HIGH
43+
self.left_servo.ChangeDutyCycle(duty)
44+
sleep(1) # Wait for the servo to complete movement
45+
GPIO.output(self.left_pin, GPIO.LOW) # Set left servo pin to LOW
46+
47+
def move_right(self, angle):
48+
duty = self.angle_to_duty(angle)
49+
GPIO.output(self.right_pin, GPIO.HIGH) # Set right servo pin to HIGH
50+
self.right_servo.ChangeDutyCycle(duty)
51+
sleep(1) # Wait for the servo to complete movement
52+
GPIO.output(self.right_pin, GPIO.LOW) # Set right servo pin to LOW
53+
54+
def angle_to_duty(self, angle):
55+
duty = angle / 18 + 2 # Map angle (0 to 180) to duty cycle (2 to 12)
56+
return duty
57+
58+
def cleanup(self):
59+
self.left_servo.stop()
60+
self.right_servo.stop()
61+
GPIO.cleanup() # Clean up GPIO pins

modules/actuators/piservo.py

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

modules/jetsensor.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Jetson.GPIO as GPIO
2+
from time import sleep
3+
from pubsub import pub
4+
5+
class JetsonMotionSensor:
6+
def __init__(self, pin, **kwargs):
7+
self.pin = pin
8+
self.value = None
9+
GPIO.setup(self.pin, GPIO.IN) # Set pin as input
10+
11+
def loop(self):
12+
if self.read():
13+
pub.sendMessage('motion')
14+
sleep(0.1) # Adjust delay as needed
15+
16+
def read(self):
17+
self.value = GPIO.input(self.pin)
18+
return self.value
19+

modules/jettemperature.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import os
2+
from pubsub import pub
3+
4+
class JetsonTemperature:
5+
WARNING_TEMP = 80
6+
THROTTLED_TEMP = 85
7+
AVG_TEMP = 70
8+
9+
def __init__(self, sensor_file='/sys/devices/virtual/thermal/thermal_zone0/temp'):
10+
self.sensor_file = sensor_file
11+
pub.subscribe(self.monitor, 'loop:10')
12+
13+
def read(self):
14+
with open(self.sensor_file, 'r') as f:
15+
temp_str = f.readline().strip()
16+
return float(temp_str) / 1000 # Convert millidegrees Celsius to degrees Celsius
17+
18+
def monitor(self):
19+
val = self.read()
20+
pub.sendMessage('log', msg='[TEMP] ' + str(val))
21+
if val >= JetsonTemperature.THROTTLED_TEMP:
22+
pub.sendMessage('led:full', color='red') # WARNING
23+
# else:
24+
# pub.sendMessage('led', identifiers='top5', color=self.map_range(round(val))) # right
25+
26+
27+
def map_range(self, value):
28+
# Cap range for LED
29+
if value > JetsonTemperature.WARNING_TEMP:
30+
value = JetsonTemperature.WARNING_TEMP
31+
if value < JetsonTemperature.AVG_TEMP:
32+
value = JetsonTemperature.AVG_TEMP
33+
34+
# translate range (STARTUP_TEMP to WARNING_TEMP) to (100 to 0) (green is cool, red is hot)
35+
OldRange = (JetsonTemperature.AVG_TEMP - JetsonTemperature.WARNING_TEMP)
36+
NewRange = (100 - 0)
37+
val = (((value - JetsonTemperature.WARNING_TEMP) * NewRange) / OldRange) + 0
38+
return val
39+

modules/personality.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@
2828
from pubsub import pub
2929
pub.sendMessage('behaviour', type=Personality.INPUT_TYPE_FUN)
3030
"""
31+
from jetsensor import JetsonMotionSensor
3132

3233
class Personality:
3334

34-
3535
def __init__(self, **kwargs):
3636
self.mode = kwargs.get('mode', Config.MODE_LIVE)
3737
self.state = Config.STATE_SLEEPING
3838
self.eye = 'blue'
39+
self.boredom_level = 0
3940

4041
pub.subscribe(self.loop, 'loop:1')
4142
pub.subscribe(self.process_sentiment, 'sentiment')
42-
43+
pub.subscribe(self.on_motion_detected, 'motion')
4344
behaviours = { 'boredom': Boredom(self),
4445
'dream': Dream(self),
4546
'faces': Faces(self),
@@ -51,24 +52,53 @@ def __init__(self, **kwargs):
5152
'sentiment': Sentiment(self)}
5253

5354
self.behaviours = SimpleNamespace(**behaviours)
55+
self.motion_sensor = JetsonMotionSensor(pin=17)
5456

5557
def loop(self):
5658
# pub.sendMessage('speech', msg="Hello, I am happy") # for testing sentiment responses
5759
if not self.is_asleep() and not self.behaviours.faces.face_detected and not self.behaviours.motion.is_motion() and not self.behaviours.objects.is_detected:
60+
self.boredom_level += 1
5861
self.set_eye('red')
62+
else:
63+
self.boredom_level = 0
64+
65+
66+
if self.boredom_level > 10:
67+
self.random_servo_movement()
68+
69+
70+
self.motion_sensor.loop()
5971

72+
6073
if self.state == Config.STATE_ALERT and self.lt(self.behaviours.faces.last_face, self.past(2*60)) and self.lt(self.behaviours.objects.last_detection, self.past(2*60)):
6174
# reset to idle position after 2 minutes inactivity
6275
pub.sendMessage('animate', action="wake")
6376
self.set_state(Config.STATE_IDLE)
6477

6578
def process_sentiment(self, score):
66-
pub.sendMessage('log', msg="[Personality] Sentiment: " + str(score))
67-
if score > 0:
68-
pub.sendMessage('piservo:move', angle=0)
69-
else:
70-
pub.sendMessage('piservo:move', angle=40)
71-
79+
80+
if self.boredom_level <= 10:
81+
pub.sendMessage('log', msg="[Personality] Sentiment: " + str(score))
82+
if score > 0:
83+
pub.sendMessage('jetservo:left_move', angle=0)
84+
pub.sendMessage('jetservo:right_move', angle=0)
85+
else:
86+
pub.sendMessage('jetservo:left_move', angle=40)
87+
pub.sendMessage('jetservo:right_move', angle=40)
88+
89+
def random_servo_movement(self):
90+
pub.sendMessage('log', msg="[Personality] Random Servo Movement due to boredom")
91+
left_angle = randint(-30, 30)
92+
right_angle = randint(-30, 30)
93+
pub.sendMessage('jetservo:left_move', angle=left_angle)
94+
pub.sendMessage('jetservo:right_move', angle=right_angle)
95+
96+
def on_motion_detected(self):
97+
98+
pub.sendMessage('log', msg="[Personality] Motion detected, servos moving up like ears")
99+
pub.sendMessage('jetservo:left_move', angle=-90)
100+
pub.sendMessage('jetservo:right_move', angle=-90)
101+
72102
def set_eye(self, color):
73103
if self.eye == color:
74104
return
@@ -79,36 +109,35 @@ def set_eye(self, color):
79109
def set_state(self, state):
80110
if self.state == state:
81111
return
82-
83112
pub.sendMessage('log', msg="[Personality] State: " + str(state))
84113
if state == Config.STATE_SLEEPING:
85114
pub.sendMessage("sleep")
86115
pub.sendMessage("animate", action="sleep")
87116
pub.sendMessage("animate", action="sit")
88117
pub.sendMessage("led:off")
89118
pub.sendMessage("led", identifiers=['status1'], color="off")
90-
pub.sendMessage('piservo:move', angle=0)
119+
pub.sendMessage('jetservo:move', angle=0)
91120
elif state == Config.STATE_RESTING:
92121
pub.sendMessage('rest')
93122
pub.sendMessage("animate", action="sit")
94123
pub.sendMessage("animate", action="sleep")
95124
self.set_eye('blue')
96125
pub.sendMessage("led", identifiers=['status1'], color="red")
97-
pub.sendMessage('piservo:move', angle=-40)
126+
pub.sendMessage('jetservo:move', angle=-40)
98127
elif state == Config.STATE_IDLE:
99128
if self.state == Config.STATE_RESTING or self.state == Config.STATE_SLEEPING:
100129
pub.sendMessage('wake')
101130
pub.sendMessage('animate', action="wake")
102131
pub.sendMessage('animate', action="sit")
103132
pub.sendMessage("led", identifiers=['status1'], color="green")
104-
pub.sendMessage('piservo:move', angle=-20)
133+
pub.sendMessage('jetservo:move', angle=-20)
105134
self.set_eye('blue')
106135
elif state == Config.STATE_ALERT:
107136
if self.state == Config.STATE_RESTING or self.state == Config.STATE_SLEEPING:
108137
pub.sendMessage('wake')
109138
pub.sendMessage('animate', action="wake")
110139
# pub.sendMessage('animate', action="stand")
111-
pub.sendMessage('piservo:move', angle=0)
140+
pub.sendMessage('jetservo:move', angle=0)
112141
pub.sendMessage("led", identifiers=['status1'], color="blue")
113142
self.state = state
114143

@@ -122,4 +151,4 @@ def lt(self, date, compare):
122151
return date is None or date < compare
123152

124153
def past(self, seconds):
125-
return datetime.now() - timedelta(seconds=seconds)
154+
return datetime.now() - timedelta(seconds=seconds)

0 commit comments

Comments
 (0)