camekommd

solartrack

Sep 4th, 2026 (edited)
14
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.31 KB | None | 0 0
  1. -- ============================================================
  2. -- SOLAR TRACKER
  3. -- CC:Tweaked + Create Rotation Speed Controller
  4. --
  5. -- SAFE / CONSERVATIVE VERSION
  6. --
  7. -- IMPORTANT:
  8. -- When this program starts, the panel MUST physically be
  9. -- facing EAST.
  10. --
  11. -- Coordinate system:
  12. --
  13. -- EAST SOUTH WEST
  14. -- -90° 0° +90°
  15. --
  16. -- The computer does not read the Mechanical Bearing's actual
  17. -- angle. It maintains an estimated position based on the
  18. -- rotation commands it sends.
  19. --
  20. -- ============================================================
  21.  
  22.  
  23. -- ============================================================
  24. -- FIND ROTATION SPEED CONTROLLER
  25. -- ============================================================
  26.  
  27. local rsc = peripheral.find("Create_RotationSpeedController")
  28.  
  29. if not rsc then
  30. error("ERROR: Create_RotationSpeedController not found.")
  31. end
  32.  
  33.  
  34. -- ============================================================
  35. -- CONFIGURATION
  36. -- ============================================================
  37.  
  38. -- Absolute software limits.
  39. local MIN_ANGLE = -90
  40. local MAX_ANGLE = 90
  41.  
  42. -- Known physical starting position.
  43. local START_ANGLE = -90
  44.  
  45. -- Park position for nighttime.
  46. local PARK_ANGLE = -90
  47.  
  48.  
  49. -- ------------------------------------------------------------
  50. -- SAFETY RPM
  51. -- ------------------------------------------------------------
  52. --
  53. -- DO NOT use 32 RPM for normal tracking.
  54. --
  55. -- The panel only needs roughly 0.5 RPM to follow the
  56. -- Minecraft sun continuously.
  57. --
  58. -- We use a low maximum speed so the mechanism cannot suddenly
  59. -- receive a huge command when there is a position error.
  60. --
  61.  
  62. local MAX_TRACKING_RPM = 2
  63.  
  64. -- Maximum speed allowed while returning to East.
  65. local MAX_PARK_RPM = 1
  66.  
  67.  
  68. -- ------------------------------------------------------------
  69. -- CONTROL
  70. -- ------------------------------------------------------------
  71.  
  72. local KP = 0.35
  73.  
  74. -- Ignore tiny errors.
  75. local DEADZONE = 1.0
  76.  
  77.  
  78. -- ------------------------------------------------------------
  79. -- GEAR RATIO
  80. -- ------------------------------------------------------------
  81. --
  82. -- Keep this at 1.0 unless your gearing changes.
  83. --
  84. -- With the way the position calculation below is written:
  85. --
  86. -- panel RPM = motor RPM * GEAR_RATIO
  87. --
  88.  
  89. local GEAR_RATIO = 1.0
  90.  
  91.  
  92. -- ------------------------------------------------------------
  93. -- LOOP TIMING
  94. -- ------------------------------------------------------------
  95. --
  96. -- 0.1 seconds gives frequent updates but can cause lots of
  97. -- target-speed changes.
  98. --
  99. -- 0.25 seconds is deliberately calmer.
  100. --
  101.  
  102. local LOOP_TIME = 0.25
  103.  
  104.  
  105. -- ------------------------------------------------------------
  106. -- DIRECTION CHANGE PROTECTION
  107. -- ------------------------------------------------------------
  108. --
  109. -- Don't immediately slam from positive RPM to negative RPM.
  110. --
  111. -- First command 0 RPM, wait, then reverse.
  112. --
  113.  
  114. local DIRECTION_CHANGE_DELAY = 0.5
  115.  
  116.  
  117. -- ============================================================
  118. -- PANEL POSITION
  119. -- ============================================================
  120. --
  121. -- This is intentionally NOT loaded from a file.
  122. --
  123. -- The physical assumption is:
  124. --
  125. -- PROGRAM START = PANEL FACING EAST
  126. --
  127. -- Therefore:
  128. --
  129.  
  130. local panelAngle = START_ANGLE
  131.  
  132.  
  133. -- ============================================================
  134. -- MOTOR STATE
  135. -- ============================================================
  136.  
  137. local currentRPM = 0
  138.  
  139.  
  140. -- ============================================================
  141. -- SAFE MOTOR CONTROL
  142. -- ============================================================
  143.  
  144. local function stopMotor()
  145.  
  146. if currentRPM ~= 0 then
  147. rsc.setTargetSpeed(0)
  148. currentRPM = 0
  149. end
  150.  
  151. end
  152.  
  153.  
  154. local function setMotor(targetRPM, maximumRPM)
  155.  
  156. maximumRPM = maximumRPM or MAX_TRACKING_RPM
  157.  
  158.  
  159. -- --------------------------------------------------------
  160. -- Round to Create's integer RPM requirement.
  161. -- --------------------------------------------------------
  162.  
  163. targetRPM = math.floor(targetRPM + 0.5)
  164.  
  165.  
  166. -- --------------------------------------------------------
  167. -- Clamp RPM.
  168. -- --------------------------------------------------------
  169.  
  170. if targetRPM > maximumRPM then
  171. targetRPM = maximumRPM
  172. elseif targetRPM < -maximumRPM then
  173. targetRPM = -maximumRPM
  174. end
  175.  
  176.  
  177. -- --------------------------------------------------------
  178. -- If we're already at the requested speed, do nothing.
  179. -- --------------------------------------------------------
  180.  
  181. if targetRPM == currentRPM then
  182. return currentRPM
  183. end
  184.  
  185.  
  186. -- --------------------------------------------------------
  187. -- Direction change protection.
  188. -- --------------------------------------------------------
  189.  
  190. if currentRPM ~= 0
  191. and targetRPM ~= 0
  192. and ((currentRPM > 0 and targetRPM < 0)
  193. or (currentRPM < 0 and targetRPM > 0)) then
  194.  
  195. -- Stop first.
  196. rsc.setTargetSpeed(0)
  197. currentRPM = 0
  198.  
  199. sleep(DIRECTION_CHANGE_DELAY)
  200.  
  201. end
  202.  
  203.  
  204. rsc.setTargetSpeed(targetRPM)
  205.  
  206. currentRPM = targetRPM
  207.  
  208. return currentRPM
  209.  
  210. end
  211.  
  212.  
  213. -- ============================================================
  214. -- SUN POSITION
  215. -- ============================================================
  216.  
  217. local function getSunAngle()
  218.  
  219. local hour = os.time("ingame")
  220.  
  221. -- 06:00 -> -90° East
  222. -- 12:00 -> 0° South
  223. -- 18:00 -> +90° West
  224.  
  225. local angle = (hour - 12) * 15
  226.  
  227.  
  228. -- --------------------------------------------------------
  229. -- Clamp to physical panel limits.
  230. -- --------------------------------------------------------
  231.  
  232. if angle < MIN_ANGLE then
  233. angle = MIN_ANGLE
  234. elseif angle > MAX_ANGLE then
  235. angle = MAX_ANGLE
  236. end
  237.  
  238.  
  239. return angle
  240.  
  241. end
  242.  
  243.  
  244. -- ============================================================
  245. -- DAY/NIGHT
  246. -- ============================================================
  247.  
  248. local function isDaytime()
  249.  
  250. local hour = os.time("ingame")
  251.  
  252. return hour >= 6 and hour < 18
  253.  
  254. end
  255.  
  256.  
  257. -- ============================================================
  258. -- SAFE POSITION UPDATE
  259. -- ============================================================
  260.  
  261. local function updatePanelPosition(rpm, seconds)
  262.  
  263. -- RPM -> degrees/second
  264. --
  265. -- 1 RPM = 6 degrees/second
  266. --
  267.  
  268. local deltaAngle =
  269. rpm * 6 * seconds * GEAR_RATIO
  270.  
  271.  
  272. panelAngle = panelAngle + deltaAngle
  273.  
  274.  
  275. -- --------------------------------------------------------
  276. -- HARD SOFTWARE LIMITS
  277. -- --------------------------------------------------------
  278.  
  279. if panelAngle >= MAX_ANGLE then
  280.  
  281. panelAngle = MAX_ANGLE
  282. stopMotor()
  283.  
  284. elseif panelAngle <= MIN_ANGLE then
  285.  
  286. panelAngle = MIN_ANGLE
  287. stopMotor()
  288.  
  289. end
  290.  
  291. end
  292.  
  293.  
  294. -- ============================================================
  295. -- PARK PANEL AT EAST
  296. -- ============================================================
  297.  
  298. local function parkPanel()
  299.  
  300. local error = PARK_ANGLE - panelAngle
  301.  
  302.  
  303. -- --------------------------------------------------------
  304. -- Already parked.
  305. -- --------------------------------------------------------
  306.  
  307. if math.abs(error) <= DEADZONE then
  308.  
  309. panelAngle = PARK_ANGLE
  310.  
  311. stopMotor()
  312.  
  313. return true
  314.  
  315. end
  316.  
  317.  
  318. -- --------------------------------------------------------
  319. -- Conservative proportional parking.
  320. -- --------------------------------------------------------
  321.  
  322. local rpm = error * KP
  323.  
  324.  
  325. -- NEVER exceed park RPM.
  326. if rpm > MAX_PARK_RPM then
  327. rpm = MAX_PARK_RPM
  328. elseif rpm < -MAX_PARK_RPM then
  329. rpm = -MAX_PARK_RPM
  330. end
  331.  
  332.  
  333. rpm = setMotor(rpm, MAX_PARK_RPM)
  334.  
  335.  
  336. -- --------------------------------------------------------
  337. -- Update estimated position.
  338. -- --------------------------------------------------------
  339.  
  340. updatePanelPosition(rpm, LOOP_TIME)
  341.  
  342.  
  343. return false
  344.  
  345. end
  346.  
  347.  
  348. -- ============================================================
  349. -- TRACK SUN
  350. -- ============================================================
  351.  
  352. local function trackSun()
  353.  
  354. local sunAngle = getSunAngle()
  355.  
  356. local error = sunAngle - panelAngle
  357.  
  358.  
  359. -- --------------------------------------------------------
  360. -- Deadzone.
  361. -- --------------------------------------------------------
  362.  
  363. if math.abs(error) <= DEADZONE then
  364.  
  365. stopMotor()
  366.  
  367. return sunAngle, error, 0
  368.  
  369. end
  370.  
  371.  
  372. -- --------------------------------------------------------
  373. -- Proportional controller.
  374. -- --------------------------------------------------------
  375.  
  376. local rpm = error * KP
  377.  
  378.  
  379. -- --------------------------------------------------------
  380. -- SAFETY LIMIT.
  381. -- --------------------------------------------------------
  382.  
  383. if rpm > MAX_TRACKING_RPM then
  384. rpm = MAX_TRACKING_RPM
  385. elseif rpm < -MAX_TRACKING_RPM then
  386. rpm = -MAX_TRACKING_RPM
  387. end
  388.  
  389.  
  390. rpm = setMotor(rpm, MAX_TRACKING_RPM)
  391.  
  392.  
  393. -- --------------------------------------------------------
  394. -- Update our estimated position.
  395. -- --------------------------------------------------------
  396.  
  397. updatePanelPosition(rpm, LOOP_TIME)
  398.  
  399.  
  400. return sunAngle, error, rpm
  401.  
  402. end
  403.  
  404.  
  405. -- ============================================================
  406. -- DISPLAY
  407. -- ============================================================
  408.  
  409. local function display(day, sunAngle, error)
  410.  
  411. term.clear()
  412. term.setCursorPos(1, 1)
  413.  
  414. print("================================")
  415. print(" SAFE SOLAR TRACKER")
  416. print("================================")
  417. print("")
  418.  
  419. print(string.format(
  420. "Minecraft Time : %05.2f",
  421. os.time("ingame")
  422. ))
  423.  
  424. print(string.format(
  425. "Panel Angle : %7.2f deg",
  426. panelAngle
  427. ))
  428.  
  429. print(string.format(
  430. "Motor RPM : %7d",
  431. currentRPM
  432. ))
  433.  
  434. print("")
  435.  
  436. if day then
  437.  
  438. print(string.format(
  439. "Sun Angle : %7.2f deg",
  440. sunAngle
  441. ))
  442.  
  443. print(string.format(
  444. "Tracking Error : %7.2f deg",
  445. error
  446. ))
  447.  
  448. print("")
  449.  
  450. if math.abs(error) <= DEADZONE then
  451.  
  452. print("STATUS: ALIGNED")
  453.  
  454. elseif error > 0 then
  455.  
  456. print("STATUS: ROTATING WEST")
  457.  
  458. else
  459.  
  460. print("STATUS: ROTATING EAST")
  461.  
  462. end
  463.  
  464. else
  465.  
  466. print(string.format(
  467. "Park Position : %7.2f deg",
  468. PARK_ANGLE
  469. ))
  470.  
  471. print("")
  472.  
  473. if math.abs(PARK_ANGLE - panelAngle) <= DEADZONE then
  474.  
  475. print("STATUS: PARKED EAST")
  476.  
  477. else
  478.  
  479. print("STATUS: RETURNING EAST")
  480.  
  481. end
  482.  
  483. end
  484.  
  485. end
  486.  
  487.  
  488. -- ============================================================
  489. -- STARTUP
  490. -- ============================================================
  491.  
  492. stopMotor()
  493.  
  494. panelAngle = START_ANGLE
  495.  
  496. term.clear()
  497. term.setCursorPos(1, 1)
  498.  
  499. print("================================")
  500. print(" SAFE SOLAR TRACKER")
  501. print("================================")
  502. print("")
  503. print("STARTING...")
  504. print("")
  505. print("Assumed panel position:")
  506. print("EAST (-90 degrees)")
  507. print("")
  508. print("Maximum tracking RPM:", MAX_TRACKING_RPM)
  509. print("Maximum park RPM:", MAX_PARK_RPM)
  510. print("")
  511. print("Starting in 2 seconds...")
  512.  
  513. sleep(2)
  514.  
  515.  
  516. -- ============================================================
  517. -- MAIN LOOP
  518. -- ============================================================
  519.  
  520. while true do
  521.  
  522. local day = isDaytime()
  523.  
  524.  
  525. -- ========================================================
  526. -- DAY
  527. -- ========================================================
  528.  
  529. if day then
  530.  
  531. local sunAngle, error, rpm =
  532. trackSun()
  533.  
  534. display(
  535. true,
  536. sunAngle,
  537. error
  538. )
  539.  
  540.  
  541. -- ========================================================
  542. -- NIGHT
  543. -- ========================================================
  544.  
  545. else
  546.  
  547. local parked = parkPanel()
  548.  
  549. local error =
  550. PARK_ANGLE - panelAngle
  551.  
  552. display(
  553. false,
  554. PARK_ANGLE,
  555. error
  556. )
  557.  
  558. end
  559.  
  560.  
  561. sleep(LOOP_TIME)
  562.  
  563. end
Advertisement
Add Comment
Please, Sign In to add comment