Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ============================================================
- -- SOLAR TRACKER
- -- CC:Tweaked + Create Rotation Speed Controller
- --
- -- SAFE / CONSERVATIVE VERSION
- --
- -- IMPORTANT:
- -- When this program starts, the panel MUST physically be
- -- facing EAST.
- --
- -- Coordinate system:
- --
- -- EAST SOUTH WEST
- -- -90° 0° +90°
- --
- -- The computer does not read the Mechanical Bearing's actual
- -- angle. It maintains an estimated position based on the
- -- rotation commands it sends.
- --
- -- ============================================================
- -- ============================================================
- -- FIND ROTATION SPEED CONTROLLER
- -- ============================================================
- local rsc = peripheral.find("Create_RotationSpeedController")
- if not rsc then
- error("ERROR: Create_RotationSpeedController not found.")
- end
- -- ============================================================
- -- CONFIGURATION
- -- ============================================================
- -- Absolute software limits.
- local MIN_ANGLE = -90
- local MAX_ANGLE = 90
- -- Known physical starting position.
- local START_ANGLE = -90
- -- Park position for nighttime.
- local PARK_ANGLE = -90
- -- ------------------------------------------------------------
- -- SAFETY RPM
- -- ------------------------------------------------------------
- --
- -- DO NOT use 32 RPM for normal tracking.
- --
- -- The panel only needs roughly 0.5 RPM to follow the
- -- Minecraft sun continuously.
- --
- -- We use a low maximum speed so the mechanism cannot suddenly
- -- receive a huge command when there is a position error.
- --
- local MAX_TRACKING_RPM = 2
- -- Maximum speed allowed while returning to East.
- local MAX_PARK_RPM = 1
- -- ------------------------------------------------------------
- -- CONTROL
- -- ------------------------------------------------------------
- local KP = 0.35
- -- Ignore tiny errors.
- local DEADZONE = 1.0
- -- ------------------------------------------------------------
- -- GEAR RATIO
- -- ------------------------------------------------------------
- --
- -- Keep this at 1.0 unless your gearing changes.
- --
- -- With the way the position calculation below is written:
- --
- -- panel RPM = motor RPM * GEAR_RATIO
- --
- local GEAR_RATIO = 1.0
- -- ------------------------------------------------------------
- -- LOOP TIMING
- -- ------------------------------------------------------------
- --
- -- 0.1 seconds gives frequent updates but can cause lots of
- -- target-speed changes.
- --
- -- 0.25 seconds is deliberately calmer.
- --
- local LOOP_TIME = 0.25
- -- ------------------------------------------------------------
- -- DIRECTION CHANGE PROTECTION
- -- ------------------------------------------------------------
- --
- -- Don't immediately slam from positive RPM to negative RPM.
- --
- -- First command 0 RPM, wait, then reverse.
- --
- local DIRECTION_CHANGE_DELAY = 0.5
- -- ============================================================
- -- PANEL POSITION
- -- ============================================================
- --
- -- This is intentionally NOT loaded from a file.
- --
- -- The physical assumption is:
- --
- -- PROGRAM START = PANEL FACING EAST
- --
- -- Therefore:
- --
- local panelAngle = START_ANGLE
- -- ============================================================
- -- MOTOR STATE
- -- ============================================================
- local currentRPM = 0
- -- ============================================================
- -- SAFE MOTOR CONTROL
- -- ============================================================
- local function stopMotor()
- if currentRPM ~= 0 then
- rsc.setTargetSpeed(0)
- currentRPM = 0
- end
- end
- local function setMotor(targetRPM, maximumRPM)
- maximumRPM = maximumRPM or MAX_TRACKING_RPM
- -- --------------------------------------------------------
- -- Round to Create's integer RPM requirement.
- -- --------------------------------------------------------
- targetRPM = math.floor(targetRPM + 0.5)
- -- --------------------------------------------------------
- -- Clamp RPM.
- -- --------------------------------------------------------
- if targetRPM > maximumRPM then
- targetRPM = maximumRPM
- elseif targetRPM < -maximumRPM then
- targetRPM = -maximumRPM
- end
- -- --------------------------------------------------------
- -- If we're already at the requested speed, do nothing.
- -- --------------------------------------------------------
- if targetRPM == currentRPM then
- return currentRPM
- end
- -- --------------------------------------------------------
- -- Direction change protection.
- -- --------------------------------------------------------
- if currentRPM ~= 0
- and targetRPM ~= 0
- and ((currentRPM > 0 and targetRPM < 0)
- or (currentRPM < 0 and targetRPM > 0)) then
- -- Stop first.
- rsc.setTargetSpeed(0)
- currentRPM = 0
- sleep(DIRECTION_CHANGE_DELAY)
- end
- rsc.setTargetSpeed(targetRPM)
- currentRPM = targetRPM
- return currentRPM
- end
- -- ============================================================
- -- SUN POSITION
- -- ============================================================
- local function getSunAngle()
- local hour = os.time("ingame")
- -- 06:00 -> -90° East
- -- 12:00 -> 0° South
- -- 18:00 -> +90° West
- local angle = (hour - 12) * 15
- -- --------------------------------------------------------
- -- Clamp to physical panel limits.
- -- --------------------------------------------------------
- if angle < MIN_ANGLE then
- angle = MIN_ANGLE
- elseif angle > MAX_ANGLE then
- angle = MAX_ANGLE
- end
- return angle
- end
- -- ============================================================
- -- DAY/NIGHT
- -- ============================================================
- local function isDaytime()
- local hour = os.time("ingame")
- return hour >= 6 and hour < 18
- end
- -- ============================================================
- -- SAFE POSITION UPDATE
- -- ============================================================
- local function updatePanelPosition(rpm, seconds)
- -- RPM -> degrees/second
- --
- -- 1 RPM = 6 degrees/second
- --
- local deltaAngle =
- rpm * 6 * seconds * GEAR_RATIO
- panelAngle = panelAngle + deltaAngle
- -- --------------------------------------------------------
- -- HARD SOFTWARE LIMITS
- -- --------------------------------------------------------
- if panelAngle >= MAX_ANGLE then
- panelAngle = MAX_ANGLE
- stopMotor()
- elseif panelAngle <= MIN_ANGLE then
- panelAngle = MIN_ANGLE
- stopMotor()
- end
- end
- -- ============================================================
- -- PARK PANEL AT EAST
- -- ============================================================
- local function parkPanel()
- local error = PARK_ANGLE - panelAngle
- -- --------------------------------------------------------
- -- Already parked.
- -- --------------------------------------------------------
- if math.abs(error) <= DEADZONE then
- panelAngle = PARK_ANGLE
- stopMotor()
- return true
- end
- -- --------------------------------------------------------
- -- Conservative proportional parking.
- -- --------------------------------------------------------
- local rpm = error * KP
- -- NEVER exceed park RPM.
- if rpm > MAX_PARK_RPM then
- rpm = MAX_PARK_RPM
- elseif rpm < -MAX_PARK_RPM then
- rpm = -MAX_PARK_RPM
- end
- rpm = setMotor(rpm, MAX_PARK_RPM)
- -- --------------------------------------------------------
- -- Update estimated position.
- -- --------------------------------------------------------
- updatePanelPosition(rpm, LOOP_TIME)
- return false
- end
- -- ============================================================
- -- TRACK SUN
- -- ============================================================
- local function trackSun()
- local sunAngle = getSunAngle()
- local error = sunAngle - panelAngle
- -- --------------------------------------------------------
- -- Deadzone.
- -- --------------------------------------------------------
- if math.abs(error) <= DEADZONE then
- stopMotor()
- return sunAngle, error, 0
- end
- -- --------------------------------------------------------
- -- Proportional controller.
- -- --------------------------------------------------------
- local rpm = error * KP
- -- --------------------------------------------------------
- -- SAFETY LIMIT.
- -- --------------------------------------------------------
- if rpm > MAX_TRACKING_RPM then
- rpm = MAX_TRACKING_RPM
- elseif rpm < -MAX_TRACKING_RPM then
- rpm = -MAX_TRACKING_RPM
- end
- rpm = setMotor(rpm, MAX_TRACKING_RPM)
- -- --------------------------------------------------------
- -- Update our estimated position.
- -- --------------------------------------------------------
- updatePanelPosition(rpm, LOOP_TIME)
- return sunAngle, error, rpm
- end
- -- ============================================================
- -- DISPLAY
- -- ============================================================
- local function display(day, sunAngle, error)
- term.clear()
- term.setCursorPos(1, 1)
- print("================================")
- print(" SAFE SOLAR TRACKER")
- print("================================")
- print("")
- print(string.format(
- "Minecraft Time : %05.2f",
- os.time("ingame")
- ))
- print(string.format(
- "Panel Angle : %7.2f deg",
- panelAngle
- ))
- print(string.format(
- "Motor RPM : %7d",
- currentRPM
- ))
- print("")
- if day then
- print(string.format(
- "Sun Angle : %7.2f deg",
- sunAngle
- ))
- print(string.format(
- "Tracking Error : %7.2f deg",
- error
- ))
- print("")
- if math.abs(error) <= DEADZONE then
- print("STATUS: ALIGNED")
- elseif error > 0 then
- print("STATUS: ROTATING WEST")
- else
- print("STATUS: ROTATING EAST")
- end
- else
- print(string.format(
- "Park Position : %7.2f deg",
- PARK_ANGLE
- ))
- print("")
- if math.abs(PARK_ANGLE - panelAngle) <= DEADZONE then
- print("STATUS: PARKED EAST")
- else
- print("STATUS: RETURNING EAST")
- end
- end
- end
- -- ============================================================
- -- STARTUP
- -- ============================================================
- stopMotor()
- panelAngle = START_ANGLE
- term.clear()
- term.setCursorPos(1, 1)
- print("================================")
- print(" SAFE SOLAR TRACKER")
- print("================================")
- print("")
- print("STARTING...")
- print("")
- print("Assumed panel position:")
- print("EAST (-90 degrees)")
- print("")
- print("Maximum tracking RPM:", MAX_TRACKING_RPM)
- print("Maximum park RPM:", MAX_PARK_RPM)
- print("")
- print("Starting in 2 seconds...")
- sleep(2)
- -- ============================================================
- -- MAIN LOOP
- -- ============================================================
- while true do
- local day = isDaytime()
- -- ========================================================
- -- DAY
- -- ========================================================
- if day then
- local sunAngle, error, rpm =
- trackSun()
- display(
- true,
- sunAngle,
- error
- )
- -- ========================================================
- -- NIGHT
- -- ========================================================
- else
- local parked = parkPanel()
- local error =
- PARK_ANGLE - panelAngle
- display(
- false,
- PARK_ANGLE,
- error
- )
- end
- sleep(LOOP_TIME)
- end
Advertisement
Add Comment
Please, Sign In to add comment