diff --git a/server/src/Controllers/PowerController.cs b/server/src/Controllers/PowerController.cs
new file mode 100644
index 0000000..b30e927
--- /dev/null
+++ b/server/src/Controllers/PowerController.cs
@@ -0,0 +1,46 @@
+using System.Collections;
+using Microsoft.AspNetCore.Cors;
+using Microsoft.AspNetCore.Mvc;
+
+namespace server.Controllers;
+
+///
+/// 矩阵键控制器,用于管理矩阵键的启用、禁用和状态设置
+///
+[ApiController]
+[Route("api/[controller]")]
+public class PowerController : ControllerBase
+{
+ private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:return]
+ [HttpPost("SetPowerOnOff")]
+ [EnableCors("Users")]
+ [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
+ public async ValueTask SetPowerOnOff(string address, int port, bool enable)
+ {
+ var powerCtrl = new Peripherals.PowerClient.Power(address, port);
+ var ret = await powerCtrl.SetPowerOnOff(enable);
+
+ if (ret.IsSuccessful)
+ {
+ var powerStatus = enable ? "ON" : "OFF";
+ logger.Info($"Set device {address}:{port.ToString()} power {powerStatus} finished: {ret.Value}.");
+ return TypedResults.Ok(ret.Value);
+ }
+ else
+ {
+ logger.Error(ret.Error);
+ return TypedResults.InternalServerError(ret.Error);
+ }
+ }
+
+}
+
diff --git a/server/src/Peripherals/PowerClient.cs b/server/src/Peripherals/PowerClient.cs
new file mode 100644
index 0000000..95b9ca1
--- /dev/null
+++ b/server/src/Peripherals/PowerClient.cs
@@ -0,0 +1,56 @@
+using System.Net;
+using DotNext;
+
+namespace Peripherals.PowerClient;
+
+class PowerAddr
+{
+ public const UInt32 Base = 0x10_00_00_00;
+ public const UInt32 PowerCtrl = Base + 7;
+}
+
+///
+/// [TODO:description]
+///
+public class Power
+{
+ private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
+ readonly int timeout;
+
+ readonly int port;
+ readonly string address;
+ private IPEndPoint ep;
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:parameter]
+ /// [TODO:return]
+ public Power(string address, int port, int timeout = 1000)
+ {
+ this.address = address;
+ this.port = port;
+ this.ep = new IPEndPoint(IPAddress.Parse(address), port);
+ this.timeout = timeout;
+ }
+
+ ///
+ /// [TODO:description]
+ ///
+ /// [TODO:parameter]
+ /// [TODO:return]
+ public async ValueTask> SetPowerOnOff(bool enable)
+ {
+ if (MsgBus.IsRunning)
+ await MsgBus.UDPServer.ClearUDPData(this.address);
+ else return new(new Exception("Message Bus not work!"));
+
+ var ret = await UDPClientPool.WriteAddr(this.ep, PowerAddr.PowerCtrl, Convert.ToUInt32(enable), this.timeout);
+ if (!ret.IsSuccessful) return new(ret.Error);
+ return ret.Value;
+ }
+}
+
diff --git a/src/APIClient.ts b/src/APIClient.ts
index 83383ad..44c8f70 100644
--- a/src/APIClient.ts
+++ b/src/APIClient.ts
@@ -1129,6 +1129,78 @@ export class MatrixKeyClient {
}
}
+export class PowerClient {
+ private http: { fetch(url: RequestInfo, init?: RequestInit): Promise };
+ private baseUrl: string;
+ protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
+
+ constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) {
+ this.http = http ? http : window as any;
+ this.baseUrl = baseUrl ?? "http://localhost:5000";
+ }
+
+ /**
+ * [TODO:description]
+ * @param address (optional) [TODO:parameter]
+ * @param port (optional) [TODO:parameter]
+ * @param enable (optional) [TODO:parameter]
+ * @return [TODO:return]
+ */
+ setPowerOnOff(address: string | undefined, port: number | undefined, enable: boolean | undefined): Promise {
+ let url_ = this.baseUrl + "/api/Power/SetPowerOnOff?";
+ if (address === null)
+ throw new Error("The parameter 'address' cannot be null.");
+ else if (address !== undefined)
+ url_ += "address=" + encodeURIComponent("" + address) + "&";
+ if (port === null)
+ throw new Error("The parameter 'port' cannot be null.");
+ else if (port !== undefined)
+ url_ += "port=" + encodeURIComponent("" + port) + "&";
+ if (enable === null)
+ throw new Error("The parameter 'enable' cannot be null.");
+ else if (enable !== undefined)
+ url_ += "enable=" + encodeURIComponent("" + enable) + "&";
+ url_ = url_.replace(/[?&]$/, "");
+
+ let options_: RequestInit = {
+ method: "POST",
+ headers: {
+ "Accept": "application/json"
+ }
+ };
+
+ return this.http.fetch(url_, options_).then((_response: Response) => {
+ return this.processSetPowerOnOff(_response);
+ });
+ }
+
+ protected processSetPowerOnOff(response: Response): Promise {
+ const status = response.status;
+ let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
+ if (status === 200) {
+ return response.text().then((_responseText) => {
+ let result200: any = null;
+ let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
+ result200 = resultData200 !== undefined ? resultData200 : null;
+
+ return result200;
+ });
+ } else if (status === 500) {
+ return response.text().then((_responseText) => {
+ let result500: any = null;
+ let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
+ result500 = Exception.fromJS(resultData500);
+ return throwException("A server side error occurred.", status, _responseText, _headers, result500);
+ });
+ } else if (status !== 200 && status !== 204) {
+ return response.text().then((_responseText) => {
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
+ });
+ }
+ return Promise.resolve(null as any);
+ }
+}
+
export class RemoteUpdateClient {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise };
private baseUrl: string;
@@ -1781,6 +1853,55 @@ export class UDPClient {
}
}
+export class TutorialClient {
+ private http: { fetch(url: RequestInfo, init?: RequestInit): Promise };
+ private baseUrl: string;
+ protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
+
+ constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) {
+ this.http = http ? http : window as any;
+ this.baseUrl = baseUrl ?? "http://localhost:5000";
+ }
+
+ /**
+ * 获取所有可用的教程目录
+ * @return 教程目录列表
+ */
+ getTutorials(): Promise {
+ let url_ = this.baseUrl + "/api/Tutorial";
+ url_ = url_.replace(/[?&]$/, "");
+
+ let options_: RequestInit = {
+ method: "GET",
+ headers: {
+ }
+ };
+
+ return this.http.fetch(url_, options_).then((_response: Response) => {
+ return this.processGetTutorials(_response);
+ });
+ }
+
+ protected processGetTutorials(response: Response): Promise {
+ const status = response.status;
+ let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
+ if (status === 200) {
+ return response.text().then((_responseText) => {
+ return;
+ });
+ } else if (status === 500) {
+ return response.text().then((_responseText) => {
+ return throwException("A server side error occurred.", status, _responseText, _headers);
+ });
+ } else if (status !== 200 && status !== 204) {
+ return response.text().then((_responseText) => {
+ return throwException("An unexpected server error occurred.", status, _responseText, _headers);
+ });
+ }
+ return Promise.resolve(null as any);
+ }
+}
+
export class Exception implements IException {
message?: string;
innerException?: Exception | undefined;
diff --git a/src/components/equipments/MotherBoardCaps.vue b/src/components/equipments/MotherBoardCaps.vue
index 351422d..01170d7 100644
--- a/src/components/equipments/MotherBoardCaps.vue
+++ b/src/components/equipments/MotherBoardCaps.vue
@@ -46,9 +46,16 @@
外设
-
@@ -119,6 +126,16 @@ async function handleMatrixkeyCheckboxChange(event: Event) {
}
}
+async function handlePowerCheckboxChange(event: Event) {
+ const target = event.target as HTMLInputElement;
+ const ret = await eqps.powerSetOnOff(target.checked);
+ if (target.checked) {
+ eqps.enablePower = ret;
+ } else {
+ eqps.enablePower = !ret;
+ }
+}
+
async function toggleJtagBoundaryScan() {
if (eqps.jtagClientMutex.isLocked()) {
dialog.warn("Jtag正在被占用");
diff --git a/src/stores/equipments.ts b/src/stores/equipments.ts
index e259b54..5d00ee6 100644
--- a/src/stores/equipments.ts
+++ b/src/stores/equipments.ts
@@ -4,7 +4,7 @@ import { isString, toNumber } from 'lodash';
import { Common } from '@/Common';
import z from "zod"
import { isNumber } from 'mathjs';
-import { JtagClient, MatrixKeyClient } from "@/APIClient";
+import { JtagClient, MatrixKeyClient, PowerClient } from "@/APIClient";
import { Mutex, withTimeout } from 'async-mutex';
import { useConstraintsStore } from "@/stores/constraints";
import { useDialogStore } from './dialog';
@@ -29,9 +29,14 @@ export const useEquipments = defineStore('equipments', () => {
const matrixKeypadClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
const matrixKeypadClient = new MatrixKeyClient();
+ // Power
+ const powerClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
+ const powerClient = new PowerClient();
+
// Enable Setting
const enableJtagBoundaryScan = ref(false);
const enableMatrixKey = ref(false);
+ const enablePower = ref(false)
// Watch
watchPostEffect(async () => {
@@ -212,6 +217,24 @@ export const useEquipments = defineStore('equipments', () => {
}
}
+ async function powerSetOnOff(enable: boolean) {
+ const release = await powerClientMutex.acquire();
+ try {
+ const resp = await powerClient.setPowerOnOff(
+ boardAddr.value,
+ boardPort.value,
+ enable
+ );
+ return resp;
+ } catch (e) {
+ dialog.error("无法开关电源");
+ console.error(e);
+ return false;
+ } finally {
+ release();
+ }
+ }
+
return {
boardAddr,
boardPort,
@@ -237,6 +260,12 @@ export const useEquipments = defineStore('equipments', () => {
matrixKeypadClient,
matrixKeypadEnable,
matrixKeypadSetKeyStates,
+
+ // Power
+ enablePower,
+ powerClient,
+ powerClientMutex,
+ powerSetOnOff,
}
})