RunCommandShell.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import React, { useCallback, useState, useEffect } from "react";
  2. /*
  3. ____ _ ____ _ _
  4. | _ \ ___ __| |_ ___ __ / ___|| |_ __ _| |_ ___ ___
  5. | |_) / _ \/ _` | | | \ \/ / \___ \| __/ _` | __/ _ \/ __|
  6. | _ < __/ (_| | |_| |> < ___) | || (_| | || __/\__ \
  7. |_| \_\___|\__,_|\__,_/_/\_\ |____/ \__\__,_|\__\___||___/
  8. */
  9. import { useSelector, useDispatch } from "react-redux";
  10. import { updateCurrentTerminalCommand } from "../../states/global";
  11. import {
  12. AppState,
  13. updateSetToUploadFile,
  14. updateSetToReloadPath,
  15. updateIsDeviceUploadingFile,
  16. updatePrintData
  17. } from "../../states/global";
  18. /*
  19. _ _ ____
  20. / \ _ __ | |_| _ \
  21. / _ \ | '_ \| __| | | |
  22. / ___ \| | | | |_| |_| |
  23. /_/ \_\_| |_|\__|____/
  24. */
  25. import { Button } from "antd";
  26. import { notification } from "antd";
  27. import { upload } from "../../utils/fileManager";
  28. import openNotificationWithIcon from "../../utils/notifications";
  29. import FileManagerUpload from "../fileManager/Upload"
  30. import {
  31. AbortController,
  32. Consumable,
  33. WritableStream,
  34. } from "@yume-chan/stream-extra";
  35. /*
  36. ____ ____ _ ____ _ _ _
  37. | _ \ _ _ _ __ / ___|___ _ __ ___ _ __ ___ __ _ _ __ __| / ___|| |__ ___| | |
  38. | |_) | | | | '_ \| | / _ \| '_ ` _ \| '_ ` _ \ / _` | '_ \ / _` \___ \| '_ \ / _ \ | |
  39. | _ <| |_| | | | | |__| (_) | | | | | | | | | | | (_| | | | | (_| |___) | | | | __/ | |
  40. |_| \_\\__,_|_| |_|\____\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_|____/|_| |_|\___|_|_|
  41. */
  42. const RunCommandShell: React.FC = () => {
  43. const [api, contextHolder] = notification.useNotification();
  44. const [command, setCommand] = useState<any>("");
  45. const [runState, setRunState] = useState(false);
  46. const currentDevice = useSelector((state: AppState) => state.currentDevice);
  47. const pythonCode = useSelector((state: AppState) => state.blocklyCode);
  48. const printData = useSelector((state: AppState) => state.printData);
  49. const isBackendConnected = useSelector(
  50. (state: AppState) => state.isBackendConnected,
  51. );
  52. const isDeviceUploadingFile = useSelector(
  53. (state: AppState) => state.isDeviceUploadingFile,
  54. );
  55. const dispatch = useDispatch();
  56. useEffect(() => {
  57. }, [currentDevice]);
  58. const handleOnSearch = async () => {
  59. console.log("开始上传文件", pythonCode);
  60. try {
  61. await uploadFile()
  62. await uploadFileOut();
  63. await runCode();
  64. } catch (e: any) {
  65. console.log("上传错误:", e);
  66. openNotificationWithIcon(
  67. api,
  68. "error",
  69. "bottomRight",
  70. "Failed to upload file",
  71. e,
  72. );
  73. } finally {
  74. console.log("上传完成");
  75. // openNotificationWithIcon(
  76. // api,
  77. // "success",
  78. // "bottomRight",
  79. // "Successfully uploaded file",
  80. // "Uploaded to /mnt/UDISK/user_latest_code.py",
  81. // );
  82. }
  83. }
  84. const uploadFile = async () => {
  85. const deviceSync = currentDevice?.sync();
  86. if (!deviceSync) {
  87. return;
  88. }
  89. const strText = `import traceback
  90. import logging
  91. import sys
  92. import os
  93. import time
  94. logger = logging.getLogger()
  95. logger.setLevel(logging.INFO)
  96. file_handler = logging.FileHandler('/root/output.log')
  97. file_handler.setLevel(logging.INFO)
  98. formatter = logging.Formatter('%(message)s')
  99. file_handler.setFormatter(formatter)
  100. logger.addHandler(file_handler)
  101. def log_output(*args, **kwargs):
  102. output = ' '.join(map(str, args))
  103. logger.info(output.strip(), **kwargs)
  104. sys.stderr.write = sys.stdout.write = log_output
  105. os.close(1)
  106. os.close(2)
  107. try:
  108. source_code = r'''import time
  109. from maix import *
  110. import gc
  111. import os,sys
  112. import sys
  113. sys.path.append('/root/')
  114. from CocoPi import BUTTON
  115. from CocoPi import stm8s
  116. from CocoPi import multiFuncGpio
  117. from CocoPi import singleRgb
  118. from CocoPi import LED
  119. gc.enable()
  120. gc.collect()
  121. iic_slaver=stm8s()
  122. iic_slaver.clear()
  123. PIXEL_LED1= multiFuncGpio(0,0)
  124. PIXEL_LED2= multiFuncGpio(1,0)
  125. PIXEL_LED1.pixelInit_()
  126. PIXEL_LED2.pixelInit_()
  127. time.sleep(0.1)
  128. L1=singleRgb()
  129. L1.setColor(0,0,0)
  130. L1.show()
  131. time.sleep(0.2)
  132. L2=LED()
  133. L2.out(0)
  134. del iic_slaver
  135. del PIXEL_LED1
  136. del PIXEL_LED2
  137. del L1
  138. del L2
  139. ${pythonCode}
  140. '''
  141. exec(source_code)
  142. except Exception as e:
  143. error_info = traceback.format_exc()
  144. print(error_info)
  145. `;
  146. const file = new File([strText], "event", {
  147. type: "text/plain",
  148. });
  149. try {
  150. dispatch(updateIsDeviceUploadingFile(true));
  151. dispatch(updateSetToUploadFile(true));
  152. await upload(file, "/root/", await deviceSync);
  153. } catch (e: any) {
  154. openNotificationWithIcon(
  155. api,
  156. "error",
  157. "bottomRight",
  158. "Failed to upload file",
  159. e,
  160. );
  161. } finally {
  162. dispatch(updateSetToUploadFile(false));
  163. dispatch(updateSetToReloadPath(true));
  164. dispatch(updateIsDeviceUploadingFile(false));
  165. }
  166. }
  167. const uploadFileOut = async () => {
  168. const deviceSync = currentDevice?.sync();
  169. if (!deviceSync) {
  170. return;
  171. }
  172. let out = `import asyncio
  173. import subprocess
  174. import linecache
  175. async def tailf(filename):
  176. command = "python -u /root/event"
  177. process = subprocess.Popen(command, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
  178. print("")
  179. while True:
  180. await asyncio.sleep(0.03)
  181. try:
  182. linecache.clearcache()
  183. logarr = linecache.getlines(filename)[-20:]
  184. if len(logarr) == 0:
  185. if process.poll() is None:
  186. continue
  187. else:
  188. return
  189. open(filename, 'w').close()
  190. print("".join(logarr).strip())
  191. except asyncio.TimeoutError:
  192. pass
  193. filename = "/root/output.log"
  194. loop = asyncio.get_event_loop()
  195. loop.run_until_complete(tailf(filename))
  196. loop.close()
  197. `
  198. const fileOut = new File([out], "out.py", {
  199. type: "text/plain",
  200. });
  201. try {
  202. dispatch(updateIsDeviceUploadingFile(true));
  203. dispatch(updateSetToUploadFile(true));
  204. await upload(fileOut, "/root/", await deviceSync)
  205. } catch (e: any) {
  206. openNotificationWithIcon(
  207. api,
  208. "error",
  209. "bottomRight",
  210. "Failed to upload file",
  211. e,
  212. );
  213. } finally {
  214. dispatch(updateSetToUploadFile(false));
  215. dispatch(updateSetToReloadPath(true));
  216. dispatch(updateIsDeviceUploadingFile(false));
  217. }
  218. }
  219. const runCode = async () => {
  220. if (command) {
  221. command.kill().then(() => {
  222. runCommand()
  223. })
  224. } else {
  225. runCommand()
  226. }
  227. }
  228. // 运行命令
  229. const runCommand = async () => {
  230. window.device?.subprocess.shell(`(echo '' > /root/output.log ; touch /tmp/disable) && python -u /root/out.py;`).then(e => {
  231. setCommand(e);
  232. serverPrint(e);
  233. }).catch(err => {
  234. console.log(err);
  235. })
  236. };
  237. const serverPrint = async (e: any) => {
  238. let _socketAbortController = new AbortController()
  239. setRunState(true);
  240. let data = printData;
  241. e._stdout.pipeTo(new WritableStream<Uint8Array>({
  242. write: (chunk) => {
  243. // Decode the chunk to obtain the output text
  244. const outputText = new TextDecoder().decode(chunk);
  245. data = data +`<p>${outputText}</p>`;
  246. dispatch(updatePrintData(data));
  247. // this.output.push(outputText);
  248. },
  249. }),
  250. {
  251. signal: _socketAbortController.signal,
  252. },
  253. ).catch((error: any) => {
  254. console.error("Error writing to writable stream:", error);
  255. }
  256. );
  257. }
  258. const stopOperation = () => {
  259. // dispatch(updateCurrentTerminalCommand(`rm /tmp/disable`));
  260. command.kill().then(() => {
  261. window.device?.subprocess.shell(`rm /tmp/disable`).then(e => {
  262. setCommand(e);
  263. setRunState(false);
  264. })
  265. })
  266. }
  267. return (
  268. <>
  269. {contextHolder}
  270. <Button type="primary" disabled={!isBackendConnected || isDeviceUploadingFile} onClick={() => handleOnSearch()}>运行</Button>
  271. <FileManagerUpload />
  272. <Button type="primary" disabled={!runState} onClick={() => stopOperation()}>停止</Button>
  273. </>
  274. );
  275. };
  276. export default RunCommandShell;