feat: add power control
This commit is contained in:
		
							
								
								
									
										46
									
								
								server/src/Controllers/PowerController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								server/src/Controllers/PowerController.cs
									
									
									
									
									
										Normal 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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										56
									
								
								server/src/Peripherals/PowerClient.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								server/src/Peripherals/PowerClient.cs
									
									
									
									
									
										Normal 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										121
									
								
								src/APIClient.ts
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								src/APIClient.ts
									
									
									
									
									
								
							@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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正在被占用");
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user