pattern_manager.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import os
  2. import json
  3. import random
  4. import threading
  5. from datetime import datetime
  6. import time
  7. from ..serial.serial_manager import send_coordinate_batch, reset_theta, send_command
  8. # Configuration
  9. THETA_RHO_DIR = './patterns'
  10. CLEAR_PATTERNS = {
  11. "clear_from_in": "./patterns/clear_from_in.thr",
  12. "clear_from_out": "./patterns/clear_from_out.thr",
  13. "clear_sideway": "./patterns/clear_sideway.thr"
  14. }
  15. os.makedirs(THETA_RHO_DIR, exist_ok=True)
  16. # Global variables for execution state
  17. stop_requested = False
  18. pause_requested = False
  19. pause_condition = threading.Condition()
  20. current_playing_file = None
  21. execution_progress = None
  22. current_playing_index = None
  23. current_playlist = None
  24. is_clearing = False
  25. PLAYLISTS_FILE = os.path.join(os.getcwd(), "playlists.json")
  26. # Ensure the playlists file exists
  27. if not os.path.exists(PLAYLISTS_FILE):
  28. with open(PLAYLISTS_FILE, "w") as f:
  29. json.dump({}, f, indent=2)
  30. def parse_theta_rho_file(file_path):
  31. """Parse a theta-rho file and return a list of (theta, rho) pairs."""
  32. coordinates = []
  33. try:
  34. with open(file_path, 'r') as file:
  35. for line in file:
  36. line = line.strip()
  37. if not line or line.startswith("#"):
  38. continue
  39. try:
  40. theta, rho = map(float, line.split())
  41. coordinates.append((theta, rho))
  42. except ValueError:
  43. print(f"Skipping invalid line: {line}")
  44. continue
  45. except Exception as e:
  46. print(f"Error reading file: {e}")
  47. return coordinates
  48. # Normalize coordinates
  49. if coordinates:
  50. first_theta = coordinates[0][0]
  51. normalized = [(theta - first_theta, rho) for theta, rho in coordinates]
  52. coordinates = normalized
  53. return coordinates
  54. def get_clear_pattern_file(pattern_name):
  55. """Return a .thr file path based on pattern_name."""
  56. if pattern_name == "random":
  57. return random.choice(list(CLEAR_PATTERNS.values()))
  58. return CLEAR_PATTERNS.get(pattern_name, CLEAR_PATTERNS["clear_from_in"])
  59. def schedule_checker(schedule_hours):
  60. """Check if execution should be paused/resumed based on schedule."""
  61. global pause_requested
  62. if not schedule_hours:
  63. return
  64. start_time, end_time = schedule_hours
  65. now = datetime.now().time()
  66. if start_time <= now < end_time:
  67. if pause_requested:
  68. print("Starting execution: Within schedule.")
  69. pause_requested = False
  70. with pause_condition:
  71. pause_condition.notify_all()
  72. else:
  73. if not pause_requested:
  74. print("Pausing execution: Outside schedule.")
  75. pause_requested = True
  76. threading.Thread(target=wait_for_start_time, args=(schedule_hours,), daemon=True).start()
  77. def wait_for_start_time(schedule_hours):
  78. """Keep checking if it's time to resume execution."""
  79. global pause_requested
  80. start_time, end_time = schedule_hours
  81. while pause_requested:
  82. now = datetime.now().time()
  83. if start_time <= now < end_time:
  84. print("Resuming execution: Within schedule.")
  85. pause_requested = False
  86. with pause_condition:
  87. pause_condition.notify_all()
  88. break
  89. else:
  90. time.sleep(30)
  91. def run_theta_rho_file(file_path, schedule_hours=None):
  92. """Run a single theta-rho file."""
  93. global stop_requested, current_playing_file, execution_progress
  94. stop_requested = False
  95. current_playing_file = file_path
  96. execution_progress = (0, 0)
  97. coordinates = parse_theta_rho_file(file_path)
  98. total_coordinates = len(coordinates)
  99. if total_coordinates < 2:
  100. print("Not enough coordinates for interpolation.")
  101. current_playing_file = None
  102. execution_progress = None
  103. return
  104. execution_progress = (0, total_coordinates)
  105. batch_size = 10
  106. for i in range(0, total_coordinates, batch_size):
  107. if stop_requested:
  108. print("Execution stopped by user after completing the current batch.")
  109. break
  110. with pause_condition:
  111. while pause_requested:
  112. print("Execution paused...")
  113. pause_condition.wait()
  114. batch = coordinates[i:i + batch_size]
  115. if i == 0:
  116. send_coordinate_batch(batch)
  117. execution_progress = (i + batch_size, total_coordinates)
  118. continue
  119. while True:
  120. schedule_checker(schedule_hours)
  121. response = send_command("R")
  122. if response == "R":
  123. send_coordinate_batch(batch)
  124. execution_progress = (i + batch_size, total_coordinates)
  125. break
  126. reset_theta()
  127. send_command("FINISHED")
  128. current_playing_file = None
  129. execution_progress = None
  130. print("Pattern execution completed.")
  131. def run_theta_rho_files(
  132. file_paths,
  133. pause_time=0,
  134. clear_pattern=None,
  135. run_mode="single",
  136. shuffle=False,
  137. schedule_hours=None
  138. ):
  139. """Run multiple theta-rho files with various options."""
  140. global stop_requested, current_playlist, current_playing_index, is_clearing
  141. stop_requested = False
  142. if shuffle:
  143. random.shuffle(file_paths)
  144. print("Playlist shuffled.")
  145. current_playlist = file_paths
  146. while True:
  147. for idx, path in enumerate(file_paths):
  148. current_playing_index = idx
  149. schedule_checker(schedule_hours)
  150. if stop_requested:
  151. print("Execution stopped before starting next pattern.")
  152. return
  153. if clear_pattern:
  154. if stop_requested:
  155. print("Execution stopped before running the next clear pattern.")
  156. return
  157. clear_file_path = get_clear_pattern_file(clear_pattern)
  158. print(f"Running clear pattern: {clear_file_path}")
  159. is_clearing = True
  160. run_theta_rho_file(clear_file_path, schedule_hours)
  161. is_clearing = False
  162. if not stop_requested:
  163. print(f"Running pattern {idx + 1} of {len(file_paths)}: {path}")
  164. run_theta_rho_file(path, schedule_hours)
  165. if idx < len(file_paths) - 1:
  166. if stop_requested:
  167. print("Execution stopped before running the next clear pattern.")
  168. return
  169. if pause_time > 0:
  170. print(f"Pausing for {pause_time} seconds...")
  171. time.sleep(pause_time)
  172. if run_mode == "indefinite":
  173. print("Playlist completed. Restarting as per 'indefinite' run mode.")
  174. if pause_time > 0:
  175. print(f"Pausing for {pause_time} seconds before restarting...")
  176. time.sleep(pause_time)
  177. if shuffle:
  178. random.shuffle(file_paths)
  179. print("Playlist reshuffled for the next loop.")
  180. continue
  181. else:
  182. print("Playlist completed.")
  183. break
  184. reset_theta()
  185. send_command("FINISHED")
  186. print("All requested patterns completed (or stopped).")
  187. def get_execution_status():
  188. """Get the current execution status."""
  189. return {
  190. "stop_requested": stop_requested,
  191. "pause_requested": pause_requested,
  192. "current_playing_file": current_playing_file,
  193. "execution_progress": execution_progress,
  194. "current_playing_index": current_playing_index,
  195. "current_playlist": current_playlist,
  196. "is_clearing": is_clearing
  197. }
  198. def stop_execution():
  199. """Stop the current execution."""
  200. global stop_requested, pause_requested, current_playing_index
  201. global current_playlist, is_clearing, current_playing_file, execution_progress
  202. with pause_condition:
  203. pause_requested = False
  204. pause_condition.notify_all()
  205. stop_requested = True
  206. current_playing_index = None
  207. current_playlist = None
  208. is_clearing = False
  209. current_playing_file = None
  210. execution_progress = None
  211. def pause_execution():
  212. """Pause the current execution."""
  213. global pause_requested
  214. with pause_condition:
  215. pause_requested = True
  216. def resume_execution():
  217. """Resume the current execution."""
  218. global pause_requested
  219. with pause_condition:
  220. pause_requested = False
  221. pause_condition.notify_all()