diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/MotorController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/MotorController.java new file mode 100644 index 00000000..8eb449ba --- /dev/null +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/MotorController.java @@ -0,0 +1,45 @@ +package com.opensourcewithslu.components.controllers; + +import com.opensourcewithslu.outputdevices.MotorHelper; +import com.pi4j.io.gpio.digital.DigitalOutput; +import com.pi4j.io.pwm.Pwm; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import jakarta.inject.Named; + +//tag::ex[] +@Controller("/motor") +public class MotorController { + private final MotorHelper MotorHelper; + + public MotorController(@Named("motor") Pwm motor, @Named("pin1") DigitalOutput pin1, + @Named("pin2") DigitalOutput pin2) { + this.MotorHelper = new MotorHelper(motor, pin1, pin2); + } + + @Get("/enable") + public void enableDCMotor() { + MotorHelper.enable(); + } + + @Get("/disable") + public void disableDCMotor() { + MotorHelper.disable(); + } + + @Get("/setSpeed/{speed}") + public void setSpeed(double speed) { + MotorHelper.setSpeed(speed); + } + + @Get("/setClockwise/{clockwise}") + public void setClockwise(boolean clockwise) { + MotorHelper.setClockwise(clockwise); + } + + @Get("/switchDirection") + public void switchDirection() { + MotorHelper.switchDirection(); + } +} +//end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 9fff77ac..42b18352 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -35,15 +35,20 @@ pi4j: provider: pigpio-pwm initial: 0 shutdown: 0 + motor: + name: Motor + address: 18 + pwmType: SOFTWARE + provider: pigpio-pwm + initial: 0 + shutdown: 0 fan: name: FAN address: 18 + pwm-type: SOFTWARE + provider: pigpio-pwm initial: 0 shutdown: 0 - provider: pigpio-pwm - pwm-type: hardware - - # end::pwm[] # tag::i2c[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/MotorHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/MotorHelper.java new file mode 100644 index 00000000..753b128b --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/MotorHelper.java @@ -0,0 +1,132 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.io.gpio.digital.DigitalOutput; +import com.pi4j.io.pwm.Pwm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class to control a DC motor using PWM (Pulse Width Modulation). + * This class provides methods to enable, disable, and set the speed of a motor. + */ +public class MotorHelper { + + private static Logger log = LoggerFactory.getLogger(MotorHelper.class); + private static final int FREQUENCY = 50; // Frequency for PWM signal in Hz. + private boolean isEnabled = false; // State tracking variable for the motor. + private final Pwm motor; // PWM interface for the motor. + private final DigitalOutput pin1; // GPIO pin 1 for motor direction. + private final DigitalOutput pin2; // GPIO pin 2 for motor direction. + private boolean isClockwise = true; // Direction of the motor. + + /** + * Constructs a new MotorHelper. + * + * @param motor A PWM interface to control the motor. + * @param pin1 A DigitalOutput interface for the first GPIO pin. + * @param pin2 A DigitalOutput interface for the second GPIO pin. + */ + //tag::const[] + public MotorHelper(Pwm motor, DigitalOutput pin1, DigitalOutput pin2) + //end::const[] + { + this.motor = motor; + this.pin1 = pin1; + this.pin2 = pin2; + } + + /** + * Enables the DC motor by setting an initial duty cycle and frequency. + * The motor remains disabled until this method is called. + */ + //tag::[method] + public void enable() + //end::[method] + { + log.info("Enabling DC motor"); + motor.on(0, FREQUENCY); // Initializes PWM signal with 0% duty cycle. + isEnabled = true; + } + + /** + * Disables the motor, effectively stopping any ongoing PWM signal. + */ + //tag::[method] + public void disable() + //end::[method] + { + log.info("Disabling DC motor"); + motor.off(); // Stops the PWM signal. + isEnabled = false; + } + + /** + * Sets the speed of the DC motor. + * This method calculates the necessary pulse width and duty cycle to achieve the specified speed. + * + * @param speed the target speed for the motor, as a percentage between 0 and 1. + */ + //tag::[method] + public void setSpeed(double speed) + //end::[method] + { + if (!isEnabled) { + log.info("You must enable the DC motor first."); + return; + } + + if (speed < 0 || speed > 100) { + log.info("You must enter a speed between 0 and 100."); + return; + } + + log.info("Setting motor speed to {}%", speed); + + motor.on(speed, FREQUENCY); + } + + /** + * Sets the direction of the DC motor. + * + * @param clockwise whether the motor should rotate clockwise. + */ + //tag::[method] + public void setClockwise(boolean clockwise) + //end::[method] + { + if (!isEnabled) { + log.info("You must enable the DC motor first."); + return; + } + + log.info("Setting motor direction clockwise to {}", clockwise); + if (clockwise) { + pin1.high(); + pin2.low(); + } else { + pin1.low(); + pin2.high(); + } + + isClockwise = clockwise; + } + + /** + * Switches the direction of the motor. + */ + //tag::[method] + public void switchDirection() + //end::[method] + { + setClockwise(!isClockwise); + } + + /** + * Sets the logger object. + * + * @param log Logger object to set the logger to. + */ + public void setLog(Logger log) { + this.log = log; + } +} diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/MotorHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/MotorHelperTest.java new file mode 100644 index 00000000..5a534ab6 --- /dev/null +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/MotorHelperTest.java @@ -0,0 +1,135 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.io.gpio.digital.DigitalOutput; +import com.pi4j.io.pwm.Pwm; +import org.junit.jupiter.api.*; +import org.slf4j.Logger; + +import static org.mockito.Mockito.*; + +public class MotorHelperTest { + DigitalOutput pin1 = mock(DigitalOutput.class); + DigitalOutput pin2 = mock(DigitalOutput.class); + Pwm motor = mock(Pwm.class); + MotorHelper motorHelper = new MotorHelper(motor, pin1, pin2); + Logger log = mock(Logger.class); + + @BeforeEach + public void openMocks() { + motorHelper.setLog(log); + } + + @Test + void enables() { + motorHelper.enable(); + verify(motor).on(0, 50); + verify(log).info("Enabling DC motor"); + } + + @Test + void disables() { + motorHelper.disable(); + verify(motor).off(); + verify(log).info("Disabling DC motor"); + } + + @Test + void setSpeedWorksWhenEnabled() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setSpeed(10); + verify(motor).on(10.0d, 50); + verify(log).info("Setting motor speed to {}%", 10.0d); + } + + @Test + void setSpeedFailsWhenDisabled() { + motorHelper.setSpeed(10); + verify(log).info("You must enable the DC motor first."); + verify(motor, never()).on(10.0d, 50); + verify(log, never()).info("Setting motor speed to {}%", 10); + } + + @Test + void setSpeedFailsWhenSpeedIsNegative() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setSpeed(-10); + verify(log).info("You must enter a speed between 0 and 100."); + verify(motor, never()).on(-10.0d, 50); + verify(log, never()).info("Setting speed to {}%", -10.0d); + } + + @Test + void setSpeedFailsWhenSpeedIsAbove100() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setSpeed(110); + verify(log).info("You must enter a speed between 0 and 100."); + verify(motor, never()).on(110.0d, 50); + verify(log, never()).info("Setting motor speed to {}%", 110.0d); + } + + @Test + void setClockwiseTrueWorksWhenEnabled() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setClockwise(true); + verify(pin1).high(); + verify(pin2).low(); + verify(log).info("Setting motor direction clockwise to {}", true); + } + + @Test + void setClockwiseFalseWorksWhenEnabled() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setClockwise(false); + verify(pin1).low(); + verify(pin2).high(); + verify(log).info("Setting motor direction clockwise to {}", false); + } + + @Test + void setClockwiseFailsWhenDisabled() { + motorHelper.setClockwise(true); + verify(log).info("You must enable the DC motor first."); + verify(pin1, never()).high(); + verify(pin2, never()).low(); + verify(log, never()).info("Setting motor direction clockwise to {}", true); + } + + @Test + void switchDirectionFromClockwiseWorksWhenEnabled() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setClockwise(true); + motorHelper.switchDirection(); + verify(pin1).low(); + verify(pin2).high(); + verify(log).info("Setting motor direction clockwise to {}", false); + } + + @Test + void switchDirectionToClockwiseWorksWhenEnabled() { + motorHelper.enable(); + verify(log).info("Enabling DC motor"); + motorHelper.setClockwise(false); + motorHelper.switchDirection(); + verify(pin1).high(); + verify(pin2).low(); + verify(log).info("Setting motor direction clockwise to {}", false); + } + + @Test + void switchDirectionFailsWhenDisabled() { + motorHelper.switchDirection(); + verify(log).info("You must enable the DC motor first."); + verify(pin1, never()).high(); + verify(pin1, never()).low(); + verify(pin2, never()).high(); + verify(pin2, never()).low(); + verify(log, never()).info("Setting motor direction clockwise to {}", true); + verify(log, never()).info("Setting motor direction clockwise to {}", false); + } +}