feat: add power control

This commit is contained in:
SikongJueluo 2025-05-20 19:11:29 +08:00
parent 46621fdb40
commit dc64a65702
No known key found for this signature in database
5 changed files with 273 additions and 4 deletions

View File

@ -0,0 +1,46 @@
using System.Collections;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// 矩阵键控制器,用于管理矩阵键的启用、禁用和状态设置
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class PowerController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="enable">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetPowerOnOff")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> 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);
}
}
}

View File

@ -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;
}
/// <summary>
/// [TODO:description]
/// </summary>
public class Power
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
readonly int timeout;
readonly int port;
readonly string address;
private IPEndPoint ep;
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
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;
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="enable">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
public async ValueTask<Result<bool>> 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;
}
}

View File

@ -1129,6 +1129,78 @@ export class MatrixKeyClient {
}
}
export class PowerClient {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
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<boolean> {
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<boolean> {
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 : <any>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<boolean>(null as any);
}
}
export class RemoteUpdateClient {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
@ -1781,6 +1853,55 @@ export class UDPClient {
}
}
export class TutorialClient {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
this.http = http ? http : window as any;
this.baseUrl = baseUrl ?? "http://localhost:5000";
}
/**
*
* @return
*/
getTutorials(): Promise<void> {
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<void> {
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<void>(null as any);
}
}
export class Exception implements IException {
message?: string;
innerException?: Exception | undefined;

View File

@ -46,9 +46,16 @@
</div>
<div class="divider"></div>
<h1 class="font-bold text-center text-2xl">外设</h1>
<div class="flex flex-row">
<input type="checkbox" class="checkbox" :checked="eqps.enableMatrixKey" @change="handleMatrixkeyCheckboxChange" />
<p class="mx-2">启用矩阵键盘</p>
<div class="flex flex-row justify-around">
<div class="flex flex-row">
<input type="checkbox" class="checkbox" :checked="eqps.enableMatrixKey"
@change="handleMatrixkeyCheckboxChange" />
<p class="mx-2">启用矩阵键盘</p>
</div>
<div class="flex flex-row">
<input type="checkbox" class="checkbox" :checked="eqps.enablePower" @change="handlePowerCheckboxChange" />
<p class="mx-2">启用电源</p>
</div>
</div>
</div>
</template>
@ -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正在被占用");

View File

@ -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,
}
})