diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0fbb312 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +sim: + python simulation.py diff --git a/README.md b/README.md index 45786f7..b7ff6a7 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,16 @@ from adafruit_servokit import ServoKit kit = ServoKit(channels=16) kit.servo[0].angle = 180 ``` + +```text +https://pinout.xyz/ Full pinout for the rpi3 +https://components101.com/sites/default/files/component_datasheet/SG90%20Servo%20Motor%20Datasheet.pdf Datasheet for the servomotor used +https://ben.akrin.com/raspberry-pi-servo-jitter/ Blog post how to fix jittering +``` + +Local address +inet6 fe80::7e2c:ada5:5de7:9a2c/64 + +## Cables + + diff --git a/driver.py b/driver.py deleted file mode 100644 index bc8bac7..0000000 --- a/driver.py +++ /dev/null @@ -1,41 +0,0 @@ -# https://learn.adafruit.com/16-channel-pwm-servo-driver/python-circuitpython - -import time -from adafruit_servokit import ServoKit - -MIN = 550 -MAX = 2450 -CHANNELS = 16 - -kit = ServoKit(channels=CHANNELS, frequency=50) - -for i in range(CHANNELS): - kit.servo[i].angle = 0 - -time.sleep(2) - -# Testing the accuracy of the lib -num = 14 - -kit.servo[num].angle = 0 -time.sleep(2) - -kit.servo[num].angle = 90 -time.sleep(2) - -kit.servo[num].angle = 30 -time.sleep(2) - -kit.servo[num].angle = 120 -time.sleep(2) - -kit.servo[num].angle = 60 -time.sleep(2) - -kit.servo[num].angle = 150 -time.sleep(1.5) - -kit.servo[num].angle = 90 -time.sleep(1.5) - -kit.servo[num].angle = 180 diff --git a/gpt-servo.py b/gpt-servo.py deleted file mode 100644 index 8bd5b7c..0000000 --- a/gpt-servo.py +++ /dev/null @@ -1,96 +0,0 @@ -import time -from adafruit_servokit import ServoKit - -class MultiSmoothServoController: - def __init__(self, channels=16, min_pulse=500, max_pulse=2500): - self.kit = ServoKit(channels=channels) - self.channels = channels - self.min_pulse = min_pulse - self.max_pulse = max_pulse - - # Track last angle and pulse per servo - self._last_angles = [None] * channels - self._last_pulses = [None] * channels - - # Set pulse width range for all channels upfront - for ch in range(channels): - self.kit.servo[ch].set_pulse_width_range(min_pulse, max_pulse) - - def angle_to_pulse(self, angle): - """Convert angle (0-180) to pulse width in microseconds""" - return int(self.min_pulse + (self.max_pulse - self.min_pulse) * angle / 180) - - def set_angle(self, channel, angle): - """Set angle for a single servo channel, avoid jitter""" - if channel < 0 or channel >= self.channels: - raise ValueError(f"Channel must be between 0 and {self.channels - 1}") - if angle < 0 or angle > 180: - raise ValueError("Angle must be between 0 and 180") - - pulse = self.angle_to_pulse(angle) - if self._last_angles[channel] != angle or self._last_pulses[channel] != pulse: - self.kit.servo[channel].pulse_width = pulse - self._last_angles[channel] = angle - self._last_pulses[channel] = pulse - - def smooth_move(self, channel, start_angle, end_angle, step=1, delay=0.02): - """Smoothly move one servo from start_angle to end_angle""" - if start_angle < end_angle: - angles = range(start_angle, end_angle + 1, step) - else: - angles = range(start_angle, end_angle - 1, -step) - - for angle in angles: - self.set_angle(channel, angle) - time.sleep(delay) - - def smooth_move_all(self, start_angles, end_angles, step=1, delay=0.02): - """ - Smoothly move all servos from their respective start_angles to end_angles. - Both start_angles and end_angles should be lists/tuples of length = number of channels. - """ - if len(start_angles) != self.channels or len(end_angles) != self.channels: - raise ValueError("start_angles and end_angles must have length equal to number of channels") - - max_steps = 0 - # Calculate how many steps each servo needs and track max - steps_per_servo = [] - for start, end in zip(start_angles, end_angles): - steps = abs(end - start) // step - steps_per_servo.append(steps) - if steps > max_steps: - max_steps = steps - - for i in range(max_steps + 1): - for ch in range(self.channels): - start = start_angles[ch] - end = end_angles[ch] - - if start < end: - angle = min(start + i * step, end) - else: - angle = max(start - i * step, end) - - self.set_angle(ch, angle) - time.sleep(delay) - - def stop_all(self): - """Stop pulses to all servos (optional)""" - for ch in range(self.channels): - self.kit.servo[ch].pulse_width = 0 - self._last_angles[ch] = None - self._last_pulses[ch] = None - -controller = MultiSmoothServoController() - -# Move servo channel 2 smoothly from 0 to 180 degrees -controller.smooth_move(channel=2, start_angle=0, end_angle=180) - -# Smoothly move multiple servos simultaneously: -start_positions = [0] * 16 # all servos at 0° -end_positions = [90, 45, 180, 0, 30, 60, 90, 120, 150, 180, 0, 90, 45, 135, 180, 0] - -controller.smooth_move_all(start_positions, end_positions) - -# Stop all servos -controller.stop_all() diff --git a/host.txt b/host.txt deleted file mode 100644 index b46bb53..0000000 --- a/host.txt +++ /dev/null @@ -1,3 +0,0 @@ -Local addresses -inet 169.254.217.237/16 -inet6 fe80::d89b:cecc:9c55:e1c3/64 diff --git a/links.txt b/links.txt deleted file mode 100644 index a5e671e..0000000 --- a/links.txt +++ /dev/null @@ -1,3 +0,0 @@ -https://pinout.xyz/ Full pinout for the rpi3 -https://components101.com/sites/default/files/component_datasheet/SG90%20Servo%20Motor%20Datasheet.pdf Datasheet for the servomotor used -https://ben.akrin.com/raspberry-pi-servo-jitter/ Blog post how to fix jittering diff --git a/objects/__init__.py b/objects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/board.py b/objects/board.py similarity index 51% rename from board.py rename to objects/board.py index d6a4e84..6972df6 100644 --- a/board.py +++ b/objects/board.py @@ -1,12 +1,15 @@ from adafruit_servokit import ServoKit class Board: + MIN = 500 + MAX = 2500 + def __init__(self, channels=16, frequency=50): self.channels = channels self.frequency = frequency self.kit = ServoKit(channels=channels, frequency=frequency) - for i in range(CHANNELS): - kit.servo[i].set_pulse_width_range(MIN, MAX) - kit.servo[i].actuation_range = RANGE - kit.servo[i].angle = INIT + for i in range(channels): + self.kit.servo[i].set_pulse_width_range(Board.MIN, Board.MAX) + self.kit.servo[i].actuation_range = 180 + self.kit.servo[i].angle = 0 diff --git a/motor.py b/objects/motor.py similarity index 100% rename from motor.py rename to objects/motor.py diff --git a/solar.py b/objects/solar.py similarity index 94% rename from solar.py rename to objects/solar.py index 5b97cb0..8dcfadd 100644 --- a/solar.py +++ b/objects/solar.py @@ -2,6 +2,7 @@ """ import math +import objects.motor as motor class MovingEntity: """Embedded entity in the world with a position.""" @@ -14,6 +15,9 @@ class MovingEntity: """Return position rotated by world's tilt around y-axis.""" return self.world.rotate_point_y(self.pos) + def move(self, dx=0, dy=0, dz=0): + self.pos = (self.pos[0] + dx, self.pos[1] + dy, self.pos[2] + dz) + class Target(MovingEntity): def __init__(self, world, pos=(0.0, 0.0, 0.0)): super().__init__(world) @@ -25,7 +29,7 @@ class Source(MovingEntity): self.pos = pos class Mirror: - def __init__(self, world, pitch_pin, yaw_pin, cluster_x=0, cluster_y=0): + def __init__(self, world, cluster_x=0, cluster_y=0): self.world = world self.cluster_x = cluster_x self.cluster_y = cluster_y diff --git a/simulation.py b/simulation.py index 5eafe1c..789a645 100644 --- a/simulation.py +++ b/simulation.py @@ -1,13 +1,14 @@ import time +import math # Solar module for simulation of world -import solar # Modeling of the world +import objects.solar as solar # Modeling of the world -from motor import Motor # Small helper functions and constants -from motor import Board +from objects.motor import Motor # Small helper functions and constants +from objects.board import Board STEP = 10 -LOOP_DELAY = 0.01 # In seconds +LOOP_DELAY = 0.005 # In seconds # Testing embedding the mirrors in the world board = Board() @@ -15,29 +16,39 @@ world = solar.World(board, tilt_deg=15) # The world is tilted 15 degrees around HEIGHT = 30 -source = solar.Source(world, pos=(0, 0, 30)) -target = solar.Target(world, pos=(20, 0, 30)) +source = solar.Source(world, pos=(-30, -50, 100)) +target = solar.Target(world, pos=(30, -50, 40)) -# Create mirrors in a 9x9 grid +# Create mirrors in a 3x2 grid for x in range(3): - for y in range(3): + for y in range(2): mirror = solar.Mirror(world, cluster_x=x, cluster_y=y) world.add_mirror(mirror) world.update_mirrors_from_source_target(source, target) -for i, mirror in enumerate(world.mirrors): - pitch, yaw = mirror.get_angles() - print(f"Mirror {i} ({mirror.cluster_x}, {mirror.cluster_y}) angles -> pitch: {pitch:.2f}°, yaw: {yaw:.2f}°") +def print_status(): + for i, mirror in enumerate(world.mirrors): + pitch, yaw = mirror.get_angles() + print(f"Mirror {i} ({mirror.cluster_x}, {mirror.cluster_y}) angles -> pitch: {pitch:.2f}°, yaw: {yaw:.2f}°") + +print_status() + +a = 1 +t = time.time() # Main try: while True: - # Shutdown - if GPIO.input(SHUTDOWN_BTN) == GPIO.HIGH: - os.system("sudo shutdown now") + source.move(10 * math.sin(a * t), 10 * math.cos(a * t)) + print(source.pos) + + world.update_mirrors_from_source_target(source, target) + print_status() time.sleep(LOOP_DELAY) + t = time.time() + except KeyboardInterrupt: pass