"""Default values provided are from the DS3218MG.

Pins GPIOs 34 to 39 can’t generate PWM on the ESP32.

Description:
    High-precision metal gears with hard anodizing
    CNC aluminum middle Shell
    water proof rubber seals screws
Specification:
    Stall Torque (5V): 19 kg/cm (263.8oz/in)
    Stall Torque (6.8V): 21.5 kg/cm (298.5 oz/in)
    Dead band: 3μs
    Speed : 0.16 sec/60°(5V) / 0.14 sec/60°(6.8V)
    Operating Voltage: 4.8 ~ 6.8 DC Volts
    Weight: 60 g (2.12 oz)
    Motor Type: DC Motor
    Gear Type: Copper & Aluminum
    Working frequence: 1520μs / 333hz
    CE Certification: Yes
    Size: 40 x 20 x 40.5 mm ( 1.58 x 0.79 x 1.60 in)
    Package Includes:
    4 X DS3218 Digital Servo 20KG
    4 X 25T Servo Arm

micro-seconds on 10**-6 seconds.


"""
from machine import Pin, PWM
import math


class Servo:

    def __init__(self, pin, freq=40, min_us=500, max_us=2500,
                 degrees=270):
        """
        :param pin: machine.Pin
            A PWM enabled Pin
        :param freq: int
            The frequency in Hz. Typical values for servos are 40 or 50.
        :param min_us: int
            The minimum number of microseconds
        :param max_us:
            The maximum number of microseconds
        :param degrees: int
            The range in degrees of the motor
        """
        self.period = 1000000 / freq
        self.min_duty = self._us2duty(min_us)
        self.max_duty = self._us2duty(max_us)
        self.degrees = degrees
        self.freq = freq
        self.pin = PWM(pin, freq=freq)

    def _us2duty(self, value):
        """Converts a microsecond value to a duty cycle value."""
        return int(1024 * value / self.period)

    def position(self, degrees=None, radians=None, us=None, duty=None):
        span = self.max_duty - self.min_duty  # 409.6
        if degrees is not None:
            duty = self.min_duty + span * degrees / self.degrees
        elif radians is not None:
            duty = self.min_duty + span * radians / math.radians(self.degrees)
        elif us is not None:
            duty = self._us2duty(us)
        elif duty is not None:
            pass
        else:
            return self.pin.duty()
        duty = min(self.max_duty, max(self.min_duty, int(duty)))
        self.pin.duty(duty)

    def release(self):
        self.pin.duty(0)
