Commit 8242e769 by dongshufeng

remove all rust codes

parent d2683ead
[workspace]
resolver = "2"
members = [
"eig-expr",
"eig-domain",
"eig-aoe",
"mems",
"mems/examples/ds-powerflow/ds-common",
"mems/examples/ds-powerflow/ds-static-topo",
"mems/examples/ds-powerflow/ds-dev-ohm-cal",
"mems/examples/ds-powerflow/ds-dyn-topo",
"mems/examples/ds-powerflow/ds-pf-input",
"mems/examples/ds-powerflow/ds-3phase-pf",
"mems/examples/ds-guizhou",
"mems/examples/iesplan",
]
[workspace.package]
edition = "2021"
rust-version = "1.80.0" # MSRV
[package]
name = "eig-aoe"
version = "0.1.0"
authors = ["dongshufeng <dongshufeng@zju.edu.cn>"]
edition.workspace = true
rust-version.workspace = true
[dependencies]
log = "^0.4"
serde = { version = "1", features = ["derive"] }
petgraph = "0.8"
# this repo
eig-domain = { path = "../eig-domain"}
eig-expr = { path = "../eig-expr"}
\ No newline at end of file
// flowing should as same as in sparrowzz
use std::fmt;
use std::fmt::Display;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use eig_expr::{Expr, MyF};
use crate::MeasureBuf;
/**
* @api {时间对象} /Duration Duration
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} secs 秒
* @apiSuccess {u32} nanos 纳秒
*/
/**
* @api {枚举_启动方式} /TriggerType TriggerType
* @apiPrivate
* @apiGroup A_Enum
* @apiSuccess {Object} SimpleRepeat 简单固定周期触发,{"SimpleRepeat": Duration}
* @apiSuccess {Object} TimeDrive cron expression,{"TimeDrive": String}
* @apiSuccess {String} EventDrive 事件驱动,AOE开始节点条件满足即触发
* @apiSuccess {Object} EventRepeatMix 事件驱动 && Simple drive,{"EventRepeatMix": Duration}
* @apiSuccess {Object} EventTimeMix 事件驱动 && Time drive,{"EventTimeMix": String}
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum TriggerType {
// 简单固定周期触发
SimpleRepeat(Duration),
// cron expression
TimeDrive(String),
// 事件驱动,AOE开始节点条件满足即触发
EventDrive,
// 事件驱动 && Simple drive
EventRepeatMix(Duration),
// 事件驱动 && Time drive
EventTimeMix(String),
}
impl Display for TriggerType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/**
* @api {枚举_失败模式} /FailureMode FailureMode
* @apiPrivate
* @apiGroup A_Enum
* @apiSuccess {String} Default 如果存在指向该节点的动作运行成功(可以理解为有路径到达该事件),则后续动作继续进行
* @apiSuccess {String} Ignore 忽略,不影响其他action
* @apiSuccess {String} StopAll 停止整个aoe
* @apiSuccess {String} StopFailed 只停止受影响的节点
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum FailureMode {
// 如果存在指向该节点的动作运行成功(可以理解为有路径到达该事件),则后续动作继续进行
Default,
// 忽略,不影响其他action
Ignore,
// 停止整个aoe
StopAll,
// 只停止受影响的节点
StopFailed,
}
impl Display for FailureMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/**
* @api {枚举_节点类型} /NodeType NodeType
* @apiPrivate
* @apiGroup A_Enum
* @apiSuccess {String} ConditionNode 带表达式的节点,表达式结果>0说明事件发生,进入后续事件
* @apiSuccess {String} SwitchNode 带表达式的节点,表达式结果>0进入第一条支路,否则进入第二条支路
* @apiSuccess {String} SwitchOfActionResult 不带表达式的节点,前序Action运行成功进入第一条支路,否则进入第二条支路
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum NodeType {
// 带表达式的节点,表达式结果>0说明事件发生,进入后续事件
ConditionNode,
// 带表达式的节点,表达式结果>0进入第一条支路,否则进入第二条支路
SwitchNode,
// 不带表达式的节点,前序Action运行成功进入第一条支路,否则进入第二条支路
SwitchOfActionResult,
}
impl Display for NodeType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/**
* @api {EventNode} /EventNode EventNode
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} id 节点id
* @apiSuccess {u64} aoe_id AOE_id
* @apiSuccess {String} name 节点名
* @apiSuccess {NodeType} node_type 节点类型
* @apiSuccess {Expr} expr 表达式
* @apiSuccess {u64} timeout 事件还未发生的等待超时时间
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct EventNode {
pub id: u64,
pub aoe_id: u64,
pub name: String,
pub node_type: NodeType,
pub expr: Expr,
/// 事件还未发生的等待超时时间
pub timeout: u64,
}
/**
* @api {ActionEdge} /ActionEdge ActionEdge
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} aoe_id AOE_id
* @apiSuccess {String} name 节点名
* @apiSuccess {u64} source_node 源节点
* @apiSuccess {u64} target_node 目标节点
* @apiSuccess {FailureMode} failure_mode action失败时的处理方式
* @apiSuccess {EigAction} action 动作定义
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct ActionEdge {
pub aoe_id: u64,
pub name: String,
pub source_node: u64,
pub target_node: u64,
/// action失败时的处理方式
pub failure_mode: FailureMode,
pub action: EigAction,
}
/**
* @api {枚举_动作} /EigAction EigAction
* @apiPrivate
* @apiGroup A_Enum
* @apiSuccess {String} None 无动作
* @apiSuccess {Object} SetPoints 设点动作,{"SetPoints": SetPoints}
* @apiSuccess {Object} SetPointsWithCheck 设点动作,{"SetPointsWithCheck": SetPoints}
* @apiSuccess {Object} SetPoints2 设点动作,{"SetPoints2": SetPoints2}
* @apiSuccess {Object} SetPointsWithCheck2 设点动作,{"SetPointsWithCheck2": SetPoints2}
* @apiSuccess {Object} Solve 求方程,{"Solve": SparseSolver}
* @apiSuccess {Object} Nlsolve Nlsolve,{"Nlsolve": NewtonSolver}
* @apiSuccess {Object} Milp 混合整数线性规划稀疏表示,{"Milp": SparseMILP}
* @apiSuccess {Object} SimpleMilp 混合整数线性规划稠密表示,{"SimpleMilp": MILP}
* @apiSuccess {Object} Nlp 非整数线性规划,{"Nlp": NLP}
* @apiSuccess {Object} Url 调用webservice获取EigAction并执行,{"Url": String}
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum EigAction {
/// 无动作
None,
/// 设点动作
SetPoints(SetPoints),
/// 设点动作
SetPointsWithCheck(SetPoints),
/// 设点动作
SetPoints2(SetPoints2),
/// 设点动作
SetPointsWithCheck2(SetPoints2),
/// 求方程
Solve(crate::solvers::SparseSolver),
/// Nlsolve
Nlsolve(crate::solvers::NewtonSolver),
/// 混合整数线性规划稀疏表示
Milp(crate::solvers::SparseMILP),
/// 混合整数线性规划稠密表示
SimpleMilp(crate::solvers::MILP),
/// 非整数线性规划
Nlp(crate::solvers::NLP),
/// 调用webservice获取EigAction并执行
Url(String),
}
/**
* @api {SetPoints} /SetPoints SetPoints
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {String[]} discrete_id discrete_id
* @apiSuccess {Expr[]} discrete_v discrete_v
* @apiSuccess {String[]} analog_id analog_id
* @apiSuccess {Expr[]} analog_v analog_v
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct SetPoints {
pub discrete_id: Vec<String>,
pub discrete_v: Vec<Expr>,
pub analog_id: Vec<String>,
pub analog_v: Vec<Expr>,
}
/**
* @api {PointsToExp} /PointsToExp PointsToExp
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {String[]} ids id列表
* @apiSuccess {Expr} expr 表达式
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct PointsToExp {
pub ids: Vec<String>,
pub expr: Expr,
}
/**
* @api {SetPoints2} /SetPoints2 SetPoints2
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {PointsToExp[]} discretes discretes
* @apiSuccess {PointsToExp[]} analogs analogs
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct SetPoints2 {
pub discretes: Vec<PointsToExp>,
pub analogs: Vec<PointsToExp>,
}
#[derive(Debug, Clone)]
pub enum VarOrMeasures {
Vars(Vec<(String, MyF)>),
Measures(MeasureBuf),
}
/**
* @api {AoeModel} /AoeModel AoeModel
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} id AOE_id
* @apiSuccess {String} name AOE名
* @apiSuccess {EventNode[]} events 节点
* @apiSuccess {ActionEdge[]} actions 边
* @apiSuccess {TriggerType} trigger_type 启动的方式
* @apiSuccess {tuple[]} variables 用户自定义的变量,这些变量不在计算点的范围,tuple格式为(变量名:String, 变量表达式:Expr)
*/
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AoeModel {
/// aoe id
pub id: u64,
/// aoe name
pub name: String,
/// 节点
pub events: Vec<EventNode>,
/// 边
pub actions: Vec<ActionEdge>,
/// aoe启动的方式
pub trigger_type: TriggerType,
/// 用户自定义的变量,这些变量不在计算点的范围
pub variables: Vec<(String, Expr)>,
}
impl Default for AoeModel {
fn default() -> Self {
Self {
id: 0,
name: String::default(),
events: vec![],
actions: vec![],
trigger_type: TriggerType::EventDrive,
variables: vec![],
}
}
}
impl PartialEq for AoeModel {
fn eq(&self, other: &Self) -> bool {
let b = self.id.eq(&other.id)
&& self.name.eq(&other.name)
&& self.trigger_type.eq(&other.trigger_type)
&& self.variables.eq(&other.variables)
&& self.events.len() == other.events.len()
&& self.actions.len() == other.actions.len();
if b {
for i in 0..self.events.len() {
if self.events[i] != other.events[i] {
return false;
}
}
for i in 0..self.actions.len() {
if self.actions[i] != other.actions[i] {
return false;
}
}
}
b
}
}
// above should as same as in sparrowzz
\ No newline at end of file
pub use model::*;
pub use utils::*;
pub mod model;
pub mod utils;
\ No newline at end of file
[package]
name = "eig-domain"
version = "0.1.0"
authors = ["dongshufeng <dongshufeng@zju.edu.cn>"]
edition.workspace = true
rust-version.workspace = true
build = "build.rs"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
protobuf = { version = "3.7", features = ["with-bytes"] }
# this project
eig-expr = { path = "../eig-expr" }
csv = "1.3"
calamine = { version = "0.30", features = []}
encoding_rs = "0.8"
[build-dependencies]
protobuf-codegen = "3.7"
protobuf = { version = "3.7", features = ["with-bytes"] }
\ No newline at end of file
use protobuf::descriptor::field_descriptor_proto::Type;
use protobuf::reflect::FieldDescriptor;
use protobuf::reflect::MessageDescriptor;
use protobuf_codegen::Codegen;
use protobuf_codegen::Customize;
use protobuf_codegen::CustomizeCallback;
fn main() {
struct GenSerde;
impl CustomizeCallback for GenSerde {
fn message(&self, _message: &MessageDescriptor) -> Customize {
Customize::default().before("#[derive(::serde::Serialize, ::serde::Deserialize)]")
}
fn field(&self, field: &FieldDescriptor) -> Customize {
if field.proto().type_() == Type::TYPE_ENUM {
// `EnumOrUnknown` is not a part of rust-protobuf, so external serializer is needed.
Customize::default().before(
"#[serde(serialize_with = \"crate::serialize_enum_or_unknown\", deserialize_with = \"crate::deserialize_enum_or_unknown\")]")
} else {
Customize::default()
}
}
fn special_field(&self, _message: &MessageDescriptor, _field: &str) -> Customize {
Customize::default().before("#[serde(skip)]")
}
}
Codegen::new()
.pure()
.cargo_out_dir("protos")
.include("protos")
.inputs([
"protos/eig.proto",
"protos/aoe.proto",
"protos/pbhymqtt.proto",
])
.customize_callback(GenSerde)
.run()
.expect("protobuf codegen failed.");
}
message PbEventResult {
enum EventEvalResult {
Happen = 1;
NotHappen = 2;
Canceled = 3;
Error = 4;
}
required uint64 id = 1;
required uint64 start_time = 2;
required uint64 end_time = 3;
required EventEvalResult final_result = 4;
}
message PbActionResult {
enum ActionExeResult {
NotRun = 1;
Success = 2;
Failed = 3;
}
required uint64 source_id = 1;
required uint64 target_id = 2;
required uint64 start_time = 3;
required uint64 end_time = 4;
required ActionExeResult final_result = 5;
optional uint32 fail_code = 6;
repeated uint64 yk_points = 7;
repeated int64 yk_values = 8;
repeated uint64 yt_points = 9;
repeated double yt_values = 10;
repeated string variables = 11;
repeated double var_values = 12;
}
message PbAoeResult {
// aoe id
required uint64 aoe_id = 1;
required uint64 start_time = 2;
required uint64 end_time = 3;
repeated PbEventResult event_results = 4;
repeated PbActionResult action_results = 5;
}
message PbAoeResults {
repeated PbAoeResult results = 1;
}
message PbAoeOperation {
enum Op {
START = 1;
STOP = 2;
}
required uint64 aoe_id = 1; // AOE ID
required Op operation = 2; // AOE ID
}
\ No newline at end of file
message PbAnalogValue {
// 测点Id
required uint64 pointId = 1;
// 测量值
required double measValue = 2;
// 时标
optional uint64 timestamp = 3;
// 原始值
optional double origValue = 4;
// change init
optional bool change_init = 5;
// source
optional uint32 source = 6;
}
message PbDiscreteValue {
// 测点Id
required uint64 pointId = 1;
// 新的测量值
required int64 measValue = 2;
// 时标
optional uint64 timestamp = 3;
// 原始值
optional int64 origValue = 4;
// change init
optional bool change_init = 5;
// source
optional uint32 source = 6;
}
message PbPointValues {
repeated PbDiscreteValue dValues = 1;
repeated PbAnalogValue aValues = 2;
}
message PbSetIntPoint {
// 发起者id
required uint64 senderId = 1;
// 测点Id
required uint64 pointId = 2;
required int64 value = 3;
optional uint64 timestamp = 4;
}
message PbSetFloatPoint {
// 发起者id
required uint64 senderId = 1;
// 测点Id
required uint64 pointId = 2;
required double value = 3;
optional uint64 timestamp = 4;
}
// 设点命令
message PbSetPoints {
repeated PbSetIntPoint dValues = 1;
repeated PbSetFloatPoint aValues = 2;
}
// 通过mqtt传输的文件
message PbFile {
enum FileOperation {
UPDATE = 1;
DELETE = 2;
RENAME = 3;
}
optional string fileName = 1;
required bytes fileContent = 2;
optional FileOperation op = 3;
optional bool is_zip = 4[default = false];
}
message PbFiles {
repeated PbFile files = 1;
}
// ping响应消息
message PbEigPingRes {
required string id = 1;
required string name = 2;
required string ip = 3;
optional string desc = 4;
optional bool is_ems = 5;
optional bool is_standby = 6;
}
message PbProperty {
required string key = 1;
required string value = 2;
}
message PbFileInfo {
required string file_name = 1;
required uint64 file_size = 2;
}
// eig配置文件、通道配置文件、测点配置文件、svg文件概况
message PbEigProfile {
repeated PbProperty properties = 1;
repeated PbFileInfo transport_files = 2;
repeated PbFileInfo point_files = 3;
repeated PbFileInfo svg_files = 4;
repeated PbFileInfo aoe_files = 5;
}
message PbAlarmDefine {
enum AlarmLevel {
Common = 1;
Important = 2;
Emergency = 3;
}
required uint32 id = 1;
//告警触发规则
required string rule = 2;
//级别
required AlarmLevel level = 3;
optional string name = 4;
//此告警内容的详情
optional string desc = 5;
// 此告警规则所对应的设备或用户,只有配置了用户才能收到短信
optional string owners = 6;
}
message PbEigAlarm {
enum AlarmStatus {
occur = 1;
disappear = 2;
}
enum AlarmType {
invalidPoints = 1;
invalidTransport = 2;
invalidAOE = 3;
alarmLevel1 = 4;
alarmLevel2 = 5;
badData = 6;
userDefine = 7;
}
required uint64 timestamp = 1;
optional uint64 id = 2;
optional AlarmType alarm_type = 3;
optional AlarmStatus status = 4;
optional uint32 define_id = 5;
optional string content = 6;
}
message PbAlarmDefines {
repeated PbAlarmDefine defines = 1;
}
message PbEigAlarms {
repeated PbEigAlarm alarms = 1;
}
message PbSetPointResult {
enum SetPointStatus {
YkCreated = 1;
YtCreated = 2;
YkSuccess = 3;
YtSuccess = 4;
YkFailTimeout = 5;
YtFailTimeout = 6;
YkFailTooBusy = 7;
YtFailTooBusy = 8;
YkFailProtocol = 9;
YtFailProtocol = 10;
}
required uint64 sender_id = 1;
required uint64 point_id = 2;
required uint64 create_time = 3;
required uint64 finish_time = 4;
required uint64 command = 5;
required SetPointStatus status = 6;
}
message PbSetPointResults {
repeated PbSetPointResult results = 1;
}
message PbMessage {
required string topic = 1;
required bytes content = 2;
}
message PbRequest {
enum RequestType {
Get = 1;
Post = 2;
Put = 3;
Delete = 4;
Test = 5;
}
optional uint64 id = 1;
required string url = 2;
required RequestType function = 3;
// base64 string
optional string content = 4;
repeated string header_keys = 5;
repeated string header_values = 6;
}
message PbResponse {
required uint64 request_id = 1;
required bool is_ok = 2;
// base64 encoded string
optional string content = 3;
// is 7z
optional bool is_zip = 4;
}
\ No newline at end of file
// 确认回复报文(用于注册模型、注册设备、数据上报)
message PbHYAckResponse {
// token
required string token = 1;
// 时标
optional string timestamp = 2;
// 成功/失败
required string status = 3;
}
// guid结构
message PbHYGuid {
optional string model = 1;
optional string port = 2;
optional string addr = 3;
optional string desc = 4;
// GUID
required string guid = 5;
required string dev = 6;
}
// 获取guid回复报文
message PbHYGuidResponse {
required string token = 1;
optional string timestamp = 2;
// 成功/失败
repeated PbHYGuid body = 3;
}
// 数据读写过程:
// 信息体结构
message PbHYPointValue {
required string name = 1;
required string val = 2;
optional string quality = 3;
optional string secret = 4;
optional string timestamp = 5;
}
// 数据读取结构
message PbHYReadPoints {
required string dev = 1;
repeated PbHYPointValue body = 2;
}
// 数据查询回复报文
message PbHYReadResponse {
required string token = 1;
optional string timestamp = 2;
repeated PbHYReadPoints body = 3;
}
// 数据写入请求报文
message PbHYWriteRequest {
required string token = 1;
optional string timestamp = 2;
required string data_row = 3;
repeated PbHYPointValue body = 4;
}
use std::collections::HashMap;
use std::io::{Cursor, Write};
use std::path::Path;
use calamine::{open_workbook_auto_from_rs, Data, Reader, Sheets, Xlsx, open_workbook_from_rs};
pub fn excel_to_csv_bytes<P: AsRef<Path>>(path: P) -> Option<Vec<Vec<u8>>> {
let bytes = std::fs::read(path).ok()?;
excel_bytes_to_csv_bytes(bytes.as_slice())
}
// return: row count, col count, merged dimensions(key is start, value is end), values(key is i*n_j)
pub fn get_first_sheet_merged_cells(bytes: Vec<u8>)
-> Option<(u32, u32, HashMap<(u32,u32), (u32, u32)>, HashMap<usize, String>)> {
let c = Cursor::new(bytes);
let mut excel: Xlsx<_> = open_workbook_from_rs(c).ok()?;
excel.load_merged_regions().ok()?;
let sheet_names = excel.sheet_names();
let mut max_col = 0;
if sheet_names.len() > 0 {
let v = excel.merged_regions_by_sheet(&sheet_names[0]);
let mut merged_cells = HashMap::with_capacity(v.len());
for (_, _, c) in v {
merged_cells.insert(c.start, c.end);
if c.end.1 > max_col {
max_col = c.end.1;
}
}
let range = excel.worksheet_range(&sheet_names[0]).ok()?;
let max_col = max_col as usize;
let (m, w) = range.get_size();
let n = if w > max_col + 1 { w } else { max_col + 1 };
let mut values = HashMap::with_capacity(m * n);
for (i, r) in range.rows().enumerate() {
for (j, c) in r.iter().enumerate() {
let key = i * n + j;
let value = match *c {
Data::Empty => String::new(),
Data::String(ref s) => format!("{s}"),
Data::Float(ref f) => format!("{f}"),
Data::DateTime(ref data) => format!("{data}"),
Data::DurationIso(ref s) | Data::DateTimeIso(ref s) => format!("{s}"),
Data::Int(ref i) => format!("{i}"),
Data::Error(ref e) => format!("{:?}", e),
Data::Bool(ref b) => format!("{b}"),
};
values.insert(key, value);
}
}
return Some((m as u32, n as u32, merged_cells, values));
}
None
}
pub fn excel_bytes_to_csv_bytes(bytes: &[u8]) -> Option<Vec<Vec<u8>>> {
let c = Cursor::new(bytes.to_vec());
if let Ok(mut xl) = open_workbook_auto_from_rs(c) {
let mut sheet_names = xl.sheet_names();
sheet_names.retain(|name| !name.starts_with('_'));
sheets_to_csv(&mut xl, sheet_names)
} else {
let is_csv = csv::ReaderBuilder::new()
.has_headers(true)
.from_reader(bytes)
.records()
.next().is_some_and(|x| x.is_ok());
if is_csv {
Some(vec![bytes.to_vec()])
} else {
None
}
}
}
pub fn excel_bytes_to_csv_bytes_by_sheet_names(
bytes: &[u8],
names: Vec<String>,
) -> Option<Vec<Vec<u8>>> {
let c = Cursor::new(bytes.to_vec());
let mut xl = open_workbook_auto_from_rs(c).ok()?;
sheets_to_csv(&mut xl, names)
}
fn sheets_to_csv<T>(xl: &mut Sheets<T>, names: Vec<String>) -> Option<Vec<Vec<u8>>>
where
T: std::io::Read + std::io::Seek,
{
let mut result = Vec::with_capacity(names.len());
for name in names {
let range = xl.worksheet_range(name.as_str()).ok()?;
let n = range.get_size().1 - 1;
let mut dest = Vec::new();
for r in range.rows() {
for (i, c) in r.iter().enumerate() {
match *c {
Data::Empty => Ok(()),
Data::String(ref s) => {
if s.contains(',')
|| s.contains('\r')
|| s.contains('\n')
|| s.contains('"')
{
let new_s = s.replace('\"', "\"\"");
write!(dest, "\"{new_s}\"")
} else {
write!(dest, "{s}")
}
}
Data::Float(ref f) => write!(dest, "{f}"),
Data::DateTime(ref data) => write!(dest, "{data}"),
Data::DurationIso(ref s) | Data::DateTimeIso(ref s) => write!(dest, "{s}"),
Data::Int(ref i) => write!(dest, "{i}"),
Data::Error(ref e) => write!(dest, "{:?}", e),
Data::Bool(ref b) => write!(dest, "{b}"),
}
.ok()?;
if i != n {
write!(dest, ",").ok()?;
}
}
write!(dest, "\r\n").ok()?;
}
if !dest.is_empty() {
result.push(dest);
}
}
Some(result)
}
#[derive(Debug, PartialEq)]
enum FileEncode {
UTF8,
UTF16LE,
UTF16BE,
GBK,
}
pub fn transfer_to_utf8(data: Vec<u8>) -> Result<Vec<u8>,()> {
let encode = get_encoding(data.as_slice());
// encoding_rs::max_utf8_buffer_length
let mut decoder = match encode {
FileEncode::UTF8 => encoding_rs::UTF_8.new_decoder(),
FileEncode::UTF16LE => {
encoding_rs::UTF_16LE.new_decoder()
}
FileEncode::UTF16BE => {
encoding_rs::UTF_16BE.new_decoder()
}
FileEncode::GBK => {
encoding_rs::GBK.new_decoder()
}
};
let mut result = Vec::with_capacity(
decoder.max_utf8_buffer_length(data.len()).unwrap()
);
result.resize(result.capacity(), 0u8);
let (_, _, written, has_errors) = decoder.decode_to_utf8(data.as_slice(), &mut result, true);
if has_errors {
Err(())
} else {
result.truncate(written);
Ok(result)
}
}
fn get_encoding(data: &[u8]) -> FileEncode {
// let data: Vec<u8> = vec![0xFF, 0xFE, 0x41, 0x00, 0x42, 0x00];
// let data = data.to_owned();
// let data_clone = data.to_owned();
let len = data.len();
if len > 2 && data[0] == 0xFF && data[1] == 0xFE {
return FileEncode::UTF16LE;
} else if len > 2 && data[0] == 0xFE && data[1] == 0xFF {
return FileEncode::UTF16BE;
} else if len > 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
// UTF8-BOM
return FileEncode::UTF8;
} else {
// 根据编码规则判断编码格式是GBK/UTF-8
//无文件头根据编码规律来判断编码格式
//UTF-8的编码规则很简单,只有二条:
//1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF - 8编码和ASCII码是相同的。
//2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
// let mut byte_number = 0;
let mut utf8_number = 0;
let mut index = 0;
while index < len {
//取第一个字节判断第一位是否为1,以及获取第一位为1时后面位连续为1的数量
let mut byte_number = 0;
for i in 0..8 {
if data[index] & (0b10000000 >> i) != 0 {
byte_number += 1;
} else {
break;
}
}
//若byte等于0,则非中文,中文数量清零
if byte_number == 0 {
utf8_number = 0;
index += 1;
} else if byte_number == 1 || byte_number > 4 {
return FileEncode::GBK;
} else {
//如果该字节开头几位连续为1,且数量byte超过1,则判断d该自己后面byte-1个字节是否符合UTF-8编码规则, 即10开头;
for i in 1..byte_number {
if data[index + i] & 0b11000000 != 0b10000000 {
return FileEncode::GBK;
}
}
//即使满足UTF-8,仍可能为GBK
//如果连续的UTF-8编码的中文数量超过3个,则判断为utf-8
utf8_number += 1;
index += byte_number;
if utf8_number >= 3 {
return FileEncode::UTF8;
}
}
}
}
FileEncode::UTF8
}
\ No newline at end of file
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use crate::proto::eig::PbEigPingRes;
/**
* @api {Eig配置对象} /EigConfig EigConfig
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {Map} properties HashMap<String, String>
*/
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct EigConfig {
pub properties: HashMap<String, String>,
pub properties2: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ConfigMsg {
pub code: u8,
pub config: Option<EigConfig>,
pub ping: Option<PbEigPingRes>,
}
\ No newline at end of file
[package]
name = "eig-expr"
version = "0.1.0"
authors = ["dongshufeng <dongshufeng@zju.edu.cn>"]
edition.workspace = true
rust-version.workspace = true
[dependencies]
fnv = "1.0"
nom = "8.0"
serde = { version = "1.0", features = ["derive"] }
num-traits = "0.2"
num-complex = "0.4"
ndarray = "0.16"
\ No newline at end of file
[package]
name = "mems"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
authors = ["dongshufeng <dongshufeng@zju.edu.cn>"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
# this project
eig-domain = { path = "../eig-domain" }
[package]
name = "ds-guizhou"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
log = "0.4"
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
mems = { path = "../.." }
csv = "1.3.0"
bytes = "1.10"
chrono = "0.4.38"
chrono-tz = "0.10"
use arrow_schema::{DataType, TimeUnit, Field, Schema};
use bytes::{Buf, BufMut, BytesMut};
use log::info;
use mems::model::{PluginInput, PluginOutput};
use chrono::{Days, Duration, Timelike, Utc};
use chrono_tz::Asia::Shanghai;
static mut OUTPUT: Vec<u8> = vec![];
#[no_mangle]
pub unsafe fn run(ptr: i32, len: u32) -> u64 {
info!("Read plugin input firstly");
// 从内存中获取字符串
let input = unsafe {
let slice = std::slice::from_raw_parts(ptr as _, len as _);
let input: PluginInput = ciborium::from_reader(slice).unwrap();
input
};
let schema = Schema::new(vec![
Field::new("datetime", DataType::Timestamp(TimeUnit::Millisecond, Some("Asia/Shanghai".into())), false),
Field::new("value", DataType::Float64, false),
]);
let mut csv_str = String::from("datetime,value\n");
let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(&*input.bytes);
let records = rdr.records();
let mut values = vec![];
for record in records {
if let Ok(f) = record {
let s = f.get(0).unwrap().trim();
let value = s.parse::<f64>().unwrap();
values.push(value);
}
}
let now = Utc::now().with_timezone(&Shanghai);
let today = now.date_naive();
let starttime = if values.len() >= 24 {
let startday = if now.hour() < 1 {
today
} else {
today.checked_add_days(Days::new(1)).unwrap()
};
startday.and_hms_opt(0, 0, 0)
.unwrap()
.and_local_timezone(Shanghai)
.unwrap()
} else {
let minutes = now.minute() / 15 * 15;
let time0 = today.and_hms_opt(now.hour(), minutes, 0)
.unwrap()
.and_local_timezone(Shanghai)
.unwrap();
time0 + Duration::minutes(15)
};
for (i, value) in values.into_iter().enumerate() {
let date = starttime + Duration::minutes((15 * i) as i64);
let date_str = date.format("%Y-%m-%d %H:%M:%S");
csv_str.push_str(&format!("{date_str}, {value}\n"));
}
let csv_bytes = vec![("".to_string(), csv_str.into_bytes())];
let output = PluginOutput {
error_msg: None,
schema: Some(vec![schema]),
csv_bytes,
};
#[allow(static_mut_refs)]
ciborium::into_writer(&output, &mut OUTPUT).unwrap();
let offset = OUTPUT.as_ptr() as i32;
let len = OUTPUT.len() as u32;
let mut bytes = BytesMut::with_capacity(8);
bytes.put_i32(offset);
bytes.put_u32(len);
return bytes.get_u64();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let now = Utc::now().with_timezone(&Shanghai);
let today = now.date_naive();
let startday = if now.hour() < 1 {
today
} else {
today.checked_add_days(chrono::Days::new(1)).unwrap()
};
let a = startday.and_hms_opt(0, 0, 0)
.unwrap()
.and_local_timezone(Shanghai)
.unwrap();
println!("{:?}", a);
}
}
\ No newline at end of file
# 插件化开发实例之配电网潮流计算软件
## 1. 项目简介
### 1.1 项目背景
## 2. 拓扑分析
### 2.1 静态拓扑分析
#### 输入
1. 电气岛
2. 属性定义
3. 资源定义
#### 输出
- static_topo,如果输出的边是该名称,或者输出的边不是下面两种情况,输出静态拓扑,即下面六列
<table>
<th>source</th>
<th>target</th>
<th>id</th>
<th>type</th>
<th>open</th>
<th>name</th>
<tr>
<td>UInt64</td>
<td>UInt64</td>
<td>UInt64</td>
<td>UInt32</td>
<td>Boolean</td>
<td>Utf8</td>
</tr>
</table>
- terminal_cn_dev,如果输出的边是该名称,则输出下面几列
<table>
<th>terminal</th>
<th>cn</th>
<th>dev</th>
<th>type</th>
<tr>
<td>UInt64</td>
<td>UInt64</td>
<td>UInt64</td>
<td>UInt32</td>
</tr>
</table>
- point_terminal_phase: 如果输出的边是该名称,则输出下面几列
<table>
<th>point</th>
<th>terminal</th>
<th>phase</th>
<tr>
<td>UInt64</td>
<td>UInt64</td>
<td>Utf8</td>
</tr>
</table>
### 2.2 动态拓扑分析
#### 输入
- 电气岛
- 量测
- 静态拓扑:上述输出的三个表格
#### 输出
- dyn_topo: 如果输出的边是该名称,或者不是下面的情况,默认输出下面几列
<table>
<th>cn</th>
<th>tn</th>
<tr>
<td>UInt64</td>
<td>UInt64</td>
</tr>
</table>
- dev_topo: 如果输出的边是该名称,则输出下面几列
<table>
<th>terminal</th>
<th>cn</th>
<th>tn</th>
<th>dev</th>
<tr>
<td>UInt64</td>
<td>UInt64</td>
<td>UInt64</td>
<td>UInt64</td>
</tr>
</table>
## 3. 输入参数准备
### 3.1 设备电气参数计算
#### 输入
- 电气岛
- 配置表格,第1列是config的key,第二列是json格式的矩阵
<table>
<th>config</th>
<th>ohm_per_km</th>
<tr>
<td>Utf8</td>
<td>Utf8</td>
</tr>
</table>
#### 输出
<table>
<th>dev_id</th>
<th>a_re</th>
<th>a_im</th>
<th>b_re</th>
<th>b_im</th>
<th>c_re</th>
<th>c_im</th>
<th>d_re</th>
<th>d_im</th>
<tr>
<td>UInt64</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Utf8</td>
</tr>
</table>
### 3.2 潮流方程准备脚本
#### shunt_meas
需要输入以下两个表格
- terminal_cn_dev
- point_terminal_phase
输出下面几列
<table>
<th>point</th>
<th>terminal</th>
<th>phase</th>
<tr>
<td>UInt64</td>
<td>UInt64</td>
<td>Utf8</td>
</tr>
</table>
#### tn_input
需要输入以下两个表格
- dev_topo
- shunt_meas
输出下面几列
<table>
<th>tn</th>
<th>phase</th>
<th>unit</th>
<th>value</th>
<tr>
<td>UInt64</td>
<td>Utf8</td>
<td>Utf8</td>
<td>Float64</td>
</tr>
</table>
## 4. 潮流计算
### 4.1 潮流计算模型
\ No newline at end of file
[package]
name = "ds-3phase-pf"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
serde_json = "1.0"
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
csv = "1.3.0"
num-complex = "0.4"
ndarray = "0.16"
nalgebra = "0.34"
ds-common = { path = "../ds-common" }
eig-domain = { path = "../../../../eig-domain" }
mems = { path = "../../.." }
bytes = "1.10"
\ No newline at end of file
#![allow(non_snake_case)]
use std::collections::HashMap;
use arrow_schema::{DataType, Field, Schema};
use bytes::{Buf, BufMut, BytesMut};
use ndarray::Array2;
use ds_common::{DEV_CONDUCTOR_DF_NAME, DEV_TOPO_DF_NAME, DS_PF_NLP_CONS, DS_PF_NLP_OBJ, DYN_TOPO_DF_NAME, TN_INPUT_DF_NAME};
use ds_common::dyn_topo::{read_dev_topo, read_dyn_topo};
use ds_common::tn_input::read_tn_input;
use mems::model::{PluginInput, PluginOutput};
use crate::read::read_dev_ohm;
mod read;
mod nlp;
static mut OUTPUT: Vec<u8> = vec![];
#[no_mangle]
pub unsafe fn run(ptr: i32, len: u32) -> u64 {
// 从内存中获取字符串
let input = unsafe {
let slice = std::slice::from_raw_parts(ptr as _, len as _);
let input: PluginInput = ciborium::from_reader(slice).unwrap();
input
};
let from = 0;
let mut error = None;
// get from dynamic topology wasm node
// cn, tn
let mut dyn_topo: Vec<Vec<u64>>;
// terminal, cn, tn, dev
let mut dev_topo: Vec<Vec<u64>>;
// dev id, conductor matrix, get from conductor impedance cal wasm node
let mut dev_conductor: HashMap<u64, Vec<Array2<f64>>>;
// tn id with input
let mut input_tns;
// input pos
let mut input_phases;
// input types
let mut input_types;
// input values
let mut input_values;
for i in 0..input.dfs_len.len() {
let size = input.dfs_len[i] as usize;
let end = from + size;
let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(&input.bytes[from..end]);
let mut records = rdr.records();
// 对第i个边输入该节点的 dataframe 进行处理
if input.dfs[i] == DYN_TOPO_DF_NAME {
match read_dyn_topo(&mut records) {
Ok(v) => dyn_topo = v,
Err(s) => {
error = Some(s);
break;
}
}
} else if input.dfs[i] == DEV_TOPO_DF_NAME {
match read_dev_topo(&mut records) {
Ok(v) => dev_topo = v,
Err(s) => {
error = Some(s);
break;
}
}
} else if input.dfs[i] == DEV_CONDUCTOR_DF_NAME {
match read_dev_ohm(&mut records) {
Ok(v) => dev_conductor = v,
Err(s) => {
error = Some(s);
break;
}
}
} else if input.dfs[i] == TN_INPUT_DF_NAME {
match read_tn_input(&mut records) {
Ok(v) => (input_tns, input_phases, input_types, input_values) = v,
Err(s) => {
error = Some(s);
break;
}
}
}
}
let output = if error.is_some() {
PluginOutput {
error_msg: error,
schema: None,
csv_bytes: vec![],
}
} else {
let mut obj_csv_str = String::from("cn,tn\n");
// build schema
let obj_schema = Schema::new(vec![
Field::new("cn", DataType::UInt64, false),
Field::new("tn", DataType::UInt64, false),
]);
let mut cons_csv_str = String::from("cn,tn\n");
// build schema
let cons_schema = Schema::new(vec![
Field::new("cn", DataType::UInt64, false),
Field::new("tn", DataType::UInt64, false),
]);
let csv_bytes = vec![
(DS_PF_NLP_OBJ.to_string(), obj_csv_str.into_bytes()),
(DS_PF_NLP_CONS.to_string(), cons_csv_str.into_bytes()),
];
PluginOutput {
error_msg: None,
schema: Some(vec![obj_schema, cons_schema]),
csv_bytes,
}
};
ciborium::into_writer(&output, &mut OUTPUT).unwrap();
let offset = OUTPUT.as_ptr() as i32;
let len = OUTPUT.len() as u32;
let mut bytes = BytesMut::with_capacity(8);
bytes.put_i32(offset);
bytes.put_u32(len);
bytes.get_u64()
}
\ No newline at end of file
use std::collections::HashMap;
use csv::StringRecordsIter;
use ndarray::Array2;
use eig_domain::prop::DataUnit;
const MAT_SIZE: usize = 18;
pub(crate) fn read_dev_ohm(records: &mut StringRecordsIter<&[u8]>)
-> Result<HashMap<u64, Vec<Array2<f64>>>, String> {
let mut map = HashMap::new();
let mut dev_id = 0u64;
let mut matrix: Vec<f64> = Vec::with_capacity(MAT_SIZE);
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
let mut col = 0;
for str in record.iter() {
if col == 0 {
if let Ok(id) = str.parse() {
if dev_id != id {
if dev_id != 0 {
if matrix.len() != MAT_SIZE {
return Err(format!("matrix len must be {MAT_SIZE}"));
} else {
let v = create_rx(&matrix);
map.insert(dev_id, v);
}
}
dev_id = id;
matrix.clear();
}
} else {
return Err(format!("Wrong dev matrix, row {row} col {col}"));
}
} else {
if let Ok(f) = str.parse() {
matrix.push(f);
} else {
return Err(format!("Wrong dev matrix, row {row} col {col}"));
}
}
col += 1;
}
}
Some(Err(e)) => {
return Err(format!("Wrong dev matrix, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
if dev_id != 0 {
if matrix.len() != MAT_SIZE {
return Err(format!("matrix len must be {MAT_SIZE}"));
} else {
let v = create_rx(&matrix);
map.insert(dev_id, v);
}
}
Ok(map)
}
fn create_rx(matrix: &[f64]) -> Vec<Array2<f64>> {
let r = Array2::from_shape_vec((3, 3), matrix[0..9].to_vec()).unwrap();
let x = Array2::from_shape_vec((3, 3), matrix[9..18].to_vec()).unwrap();
let v = vec![r, x];
v
}
#[test]
fn test_unit_parse() {
let units = vec![DataUnit::kVar, DataUnit::kW];
let s = serde_json::to_string(&units).unwrap();
assert_eq!("[\"kVar\",\"kW\"]", s);
let r: Vec<DataUnit> = serde_json::from_str("[\"kVar\", \"kW\"]").unwrap();
assert_eq!(r, units);
}
\ No newline at end of file
dy_topo = [];
dev_topo = [];
terminal_cn_dev = [];
tn_input = [];
shunt_meas = [];
// general matrix
gm = [];
\ No newline at end of file
% 测试ieee13配电网算例的单段导线
clc;
mpopt = mpoption('verbose',2);
mpc = loadcase('t_case3p_a_1');
run_pf(mpc,mpopt,'mpx',mp.xt_3p);
% 以下为手工计算结果
r = [ 0.3465 0.1560 0.1580 ; 0.1560 0.3375 0.1535 ; 0.1580 0.1535 0.3414 ];
x= [1.0179 0.5017 0.4236; 0.5017 1.0478 0.3849; 0.4236 0.3849 1.0348];
len = 2000/5280;
y = inv(r+1j*x)/len;
v1 = [complexd(7.1996,0.00) ; complexd(7.1996,-120.00) ; complexd(7.1996,120.00)].*1000;
v2 = [complexd(7.1769,-0.08) ; complexd(7.1396,-120.37) ; complexd(7.1500,119.25)].*1000;
i = y*(v2-v1);
abs(i)
rad2deg(angle(i))
pd = mpc.load3p(1,4:6);
pf = mpc.load3p(1,7:9);
qd = pd .* tan(acos(pf));
function mpc = t_case3p_a_1
% t_case3p_a - Four bus, unbalanced 3-phase test case.
%
% This data comes from ``4Bus-YY-UnB.DSS``, a modified version (with unbalanced
% load) of ``4Bus-YY-Bal.DSS`` [1], the OpenDSS 4 bus IEEE test case with
% grounded-wye to grounded-wye transformer.
%
% [1] https://sourceforge.net/p/electricdss/code/HEAD/tree/trunk/Distrib/IEEETestCases/4Bus-YY-Bal/4Bus-YY-Bal.DSS
%% MATPOWER Case Format : Version 2
mpc.version = '2';
%%----- Power Flow Data -----%%
%% system MVA base
mpc.baseMVA = 100;
mpc.bus = [];
mpc.gen = [];
mpc.branch = [];
mpc.gencost = [];
%%----- 3 Phase Model Data -----%%
%% system data
mpc.freq = 60; %% frequency, Hz
mpc.basekVA = 1000; %% system kVA base
%% bus data
% busid type basekV Vm1 Vm2 Vm3 Va1 Va2 Va3
mpc.bus3p = [
1 3 12.47 1 1 1 0 -120 120;
2 1 12.47 1 1 1 0 -120 120;
% 3 1 4.16 1 1 1 0 -120 120;
% 4 1 4.16 1 1 1 0 -120 120;
];
%% branch data
% brid fbus tbus status lcid len
mpc.line3p = [
1 1 2 1 1 2000/5280;
% 2 3 4 1 1 2500/5280;
];
%% transformer
% xfid fbus tbus status R X basekVA basekV
mpc.xfmr3p = [
% 1 2 3 1 0.01 0.06 6000 12.47;
];
%% load
% ldid ldbus status Pd1 Pd2 Pd3 ldpf1 ldpf2 ldpf3
mpc.load3p = [
1 2 1 1275 1800 2375 0.85 0.9 0.95;
% 1 4 1 1275 1800 2375 0.85 0.9 0.95;
];
%% gen
% genid gbus status Vg1 Vg2 Vg3 Pg1 Pg2 Pg3 Qg1 Qg2 Qg3
mpc.gen3p = [
1 1 1 1 1 1 2000 2000 2000 0 0 0;
];
%% line construction
% lcid R11 R21 R31 R22 R32 R33 X11 X21 X31 X22 X32 X33 C11 C21 C31 C22 C32 C33
mpc.lc = [
1 0.3465 0.1560 0.1580 0.3375 0.1535 0.3414 1.0179 0.5017 0.4236 1.0478 0.3849 1.0348 0 0 0 0 0 0
%1 0.457541 0.15594 0.153474 0.466617 0.157996 0.461462 1.078 0.501648 0.384909 1.04813 0.423624 1.06502 15.0671 -4.86241 -1.85323 15.875 -3.09098 14.3254
];
[package]
name = "ds-common"
version = "0.1.0"
edition = "2021"
[dependencies]
serde_json = "1.0"
csv = "1.3"
eig-domain = { path = "../../../../eig-domain" }
mems = { path = "../../.." }
\ No newline at end of file
use csv::StringRecordsIter;
pub fn read_dyn_topo(records: &mut StringRecordsIter<&[u8]>)
-> Result<Vec<Vec<u64>>, String> {
let mut dyn_topo = Vec::new();
// 按行读取csv
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
let mut col = 0;
dyn_topo.push(vec![0u64; 2]);
for str in record.iter() {
if let Ok(id) = str.parse() {
dyn_topo[row][col] = id;
} else {
return Err(format!("Wrong dynamic topology input, row {row} col {col}"));
}
col += 1;
if col == 2 {
break;
}
}
if col != 2 {
return Err(format!("Wrong dynamic topology input, expected col 2, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong dynamic topology input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok(dyn_topo)
}
pub fn read_dev_topo(records: &mut StringRecordsIter<&[u8]>)
-> Result<Vec<Vec<u64>>, String> {
let mut dev_topo = Vec::new();
// 按行读取csv
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
let mut col = 0;
dev_topo.push(vec![0u64; 4]);
for str in record.iter() {
if let Ok(id) = str.parse() {
dev_topo[row][col] = id;
} else {
return Err(format!("Wrong device topology, row {row} col {col}"));
}
col += 1;
if col == 4 {
break;
}
}
if col != 4 {
return Err(format!("Wrong device topology input, expected col 4, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong device topology input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok(dev_topo)
}
pub mod dyn_topo;
pub mod static_topo;
pub mod tn_input;
// static topo
pub const STATIC_TOPO_DF_NAME: &str = "static_topo";
pub const TERMINAL_DF_NAME: &str = "terminal_cn_dev";
pub const POINT_DF_NAME: &str = "point_terminal_phase";
// dynamic topo
pub const DYN_TOPO_DF_NAME: &str = "dyn_topo";
pub const DEV_TOPO_DF_NAME: &str = "dev_topo";
// impedance matrix
pub const DEV_CONDUCTOR_DF_NAME: &str = "dev_ohm";
// pf input
pub const SHUNT_MEAS_DF_NAME: &str = "shunt_meas";
pub const TN_INPUT_DF_NAME: &str = "tn_input";
// pf nlp
pub const DS_PF_NLP_OBJ: &str = "3phase_pf_obj";
pub const DS_PF_NLP_CONS: &str = "3phase_pf_cons";
\ No newline at end of file
use std::collections::HashMap;
use csv::StringRecordsIter;
use mems::model::dev::{MeasPhase, PsRsrType};
pub fn read_point_terminal(records: &mut StringRecordsIter<&[u8]>,
mut meas_phase: Option<&mut Vec<MeasPhase>>) -> Result<Vec<Vec<u64>>, String> {
let mut points = Vec::new();
// 按行读取csv
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
points.push(vec![0u64; 2]);
let mut col = 0;
for str in record.iter() {
if col < 2 {
if let Ok(id) = str.parse() {
points[row][col] = id;
} else {
return Err(format!("Wrong point input, row {row} col {col}"));
}
} else if meas_phase.is_some() {
meas_phase.as_mut().unwrap().push(MeasPhase::from(str))
}
col += 1;
if col == 3 {
break;
}
}
if col != 3 {
return Err(format!("Wrong point input, expected col at least 3, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong point input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok(points)
}
pub fn read_terminal_cn_dev(records: &mut StringRecordsIter<&[u8]>, mut dev_type: Option<&mut HashMap<u64, u16>>)
-> Result<Vec<Vec<u64>>, String> {
let mut terminals: Vec<Vec<u64>> = Vec::new();
// 按行读取csv
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
terminals.push(vec![0u64; 3]);
let mut col = 0;
for str in record.iter() {
if col < 3 {
if let Ok(id) = str.parse() {
terminals[row][col] = id;
} else {
return Err(format!("Wrong terminal input, row {row} col {col}: {str}"));
}
} else if col == 3 && dev_type.is_some() {
if let Ok(type_u16) = str.parse::<u16>() {
dev_type.as_mut().unwrap().insert(terminals[row][2], type_u16);
} else {
return Err(format!("Wrong terminal input, row {row} col {col}: {str}"));
}
}
col += 1;
if col == 4 {
break;
}
}
if col != 4 {
return Err(format!("Wrong terminal input, expected col 4, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong terminal input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok(terminals)
}
pub fn read_static_topo(records: &mut StringRecordsIter<&[u8]>,
mut normal_open: Option<&mut HashMap<u64, bool>>)
-> Result<Vec<Vec<u64>>, String> {
let mut edges = Vec::new();
let mut row = 0;
let swich_type = PsRsrType::Switch as u16;
// 按行读取csv
loop {
match records.next() {
Some(Ok(record)) => {
edges.push(vec![0u64; 3]);
let mut col = 0;
let mut is_switch = false;
for str in record.iter() {
if col < 3 {
if let Ok(id) = str.parse() {
edges[row][col] = id;
} else {
return Err(format!("Wrong static topology input, row {row} col {col}: {str}"));
}
} else if col == 3 {
if let Ok(type_u16) = str.parse::<u16>() {
is_switch = type_u16 == swich_type;
} else {
return Err(format!("Wrong static topology input, row {row} col {col}: {str}"));
}
} else if col == 4 && is_switch && normal_open.is_some() {
if let Ok(b) = str.parse::<bool>() {
normal_open.as_mut().unwrap().insert(edges[row][2], b);
} else {
return Err(format!("Wrong static topology input, row {row} col {col}: {str}"));
}
}
col += 1;
if col == 5 {
break;
}
}
if col != 5 {
return Err(format!("Wrong static topology input, expected col at least 5, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong static topology input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok(edges)
}
\ No newline at end of file
use std::collections::HashMap;
use csv::StringRecordsIter;
use eig_domain::prop::DataUnit;
use mems::model::dev::MeasPhase;
pub fn read_shunt_measures(records: &mut StringRecordsIter<&[u8]>)
-> Result<HashMap<u64, (u64, MeasPhase)>, String> {
let mut meas = HashMap::new();
// 按行读取csv
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
let mut col = 0;
let mut point = 0u64;
let mut terminal = 0u64;
for str in record.iter() {
if col == 0 {
if let Ok(id) = str.parse() {
point = id;
} else {
return Err(format!("Wrong shunt measure input, row {row} col {col}"));
}
} else if col == 1 {
if let Ok(id) = str.parse() {
terminal = id;
} else {
return Err(format!("Wrong shunt measure input, row {row} col {col}"));
}
} else if col == 2 {
meas.insert(point, (terminal, MeasPhase::from(str)));
}
col += 1;
if col == 3 {
break;
}
}
if col != 3 {
return Err(format!("Wrong shunt measure input, expected col at least 3, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong shunt measure input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok(meas)
}
pub fn read_tn_input(records: &mut StringRecordsIter<&[u8]>)
-> Result<(Vec<u64>, Vec<MeasPhase>, Vec<DataUnit>, Vec<f64>), String> {
let mut tn = Vec::new();
let mut input_type = Vec::new();
let mut input_phase = Vec::new();
let mut value = Vec::new();
// 按行读取csv
let mut row = 0;
loop {
match records.next() {
Some(Ok(record)) => {
let mut col = 0;
for str in record.iter() {
if col == 0 {
if let Ok(v) = str.parse() {
tn.push(v);
} else {
return Err(format!("Wrong bus input, row {row} col {col}"));
}
} else if col == 1 {
if let Ok(v) = serde_json::from_str(str) {
input_phase.push(v);
} else {
return Err(format!("Wrong bus input, row {row} col {col}"));
}
} else if col == 2 {
if let Ok(v) = serde_json::from_str(str) {
input_type.push(v);
} else {
return Err(format!("Wrong bus input, row {row} col {col}"));
}
} else if col == 3 {
if let Ok(v) = serde_json::from_str(str) {
value.push(v);
} else {
return Err(format!("Wrong bus input, row {row} col {col}"));
}
}
col += 1;
if col == 4 {
break;
}
}
if col != 4 {
return Err(format!("Wrong bus input, expected col 4, actual {col}"));
}
}
Some(Err(e)) => {
return Err(format!("Wrong bus input, err: {:?}", e));
}
None => {
break;
}
}
row += 1;
}
Ok((tn, input_phase, input_type, value))
}
\ No newline at end of file
[package]
name = "ds-dev-ohm-cal"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
log = "0.4"
serde_json = "1.0"
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
eig-domain = { path = "../../../../eig-domain" }
mems = { path = "../../.." }
csv = "1.3.0"
ndarray = "0.16"
bytes = "1.10"
use std::collections::HashMap;
use arrow_schema::{DataType, Field, Schema};
use bytes::{Buf, BufMut, BytesMut};
use csv::StringRecordsIter;
use log::{info, warn};
use ndarray::{Array2, ArrayBase, Ix2, OwnedRepr};
use eig_domain::prop::PropValue;
use mems::model::{get_csv_str, get_df_from_in_plugin, get_island_from_plugin_input, PluginInput, PluginOutput};
use mems::model::dev::PsRsrType;
static mut OUTPUT: Vec<u8> = vec![];
#[no_mangle]
pub unsafe fn run(ptr: i32, len: u32) -> u64 {
info!("Read plugin input firstly");
// 从内存中获取字符串
let input = unsafe {
let slice = std::slice::from_raw_parts(ptr as _, len as _);
let input: PluginInput = ciborium::from_reader(slice).unwrap();
input
};
let mut error = None;
let r = get_island_from_plugin_input(&input);
if let Err(s) = &r {
error = Some(s.clone());
}
let r2 = get_df_from_in_plugin(&input);
if let Err(s) = &r2 {
error = Some(s.clone());
}
let mut config= HashMap::with_capacity(0);
let mut csv_str = String::from("dev_id,ohm\n");
if error.is_none() {
let (island, prop_defs, defines) = r.unwrap();
let from = r2.unwrap();
info!("input dataframe num from edges is {}", input.dfs.len());
for i in 0..input.dfs_len.len() {
let size = input.dfs_len[i] as usize;
let end = from + size;
let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(&input.bytes[from..end]);
let mut records = rdr.records();
match read_config( &mut records) {
Ok(v) => config = v,
Err(s) => error = Some(s),
}
break;
}
if error.is_none() {
let mut prop_defines = HashMap::with_capacity(prop_defs.len());
for def in prop_defs.into_iter() {
prop_defines.insert(def.id, def);
}
for (_, rsr) in &island.resources {
if let Some(def) = defines.get(&rsr.define_id) {
if def.rsr_type == PsRsrType::ACline {
let dev_id = rsr.id;
let line_conf = rsr.get_prop_value("model", &island.prop_groups, &prop_defines);
let length = rsr.get_prop_value("length", &island.prop_groups, &prop_defines);
if let PropValue::Str(s) = line_conf {
if let Some((mat_re, mat_im)) = config.get(&s) {
if let Some(f) = length.get_f64() {
let ratio = f / 1000.0;
let u_re = vec![1., 0., 0., 0., 1., 0., 0., 0., 1.];
let u_im = vec![0., 0., 0., 0., 0., 0., 0., 0., 0.];
let (z_re, _) = (mat_re * ratio).into_raw_vec_and_offset();
let (z_im, _) = (mat_im * ratio).into_raw_vec_and_offset();
let u_re_json = get_csv_str(&serde_json::to_string(&u_re).unwrap());
let u_im_json = get_csv_str(&serde_json::to_string(&u_im).unwrap());
let z_re_json = get_csv_str(&serde_json::to_string(&z_re).unwrap());
let z_im_json = get_csv_str(&serde_json::to_string(&z_im).unwrap());
csv_str.push_str(&format!("{dev_id},{u_re_json},{u_im_json},{z_re_json},{z_im_json},{u_im_json},{u_im_json},{z_re_json},{z_im_json}\n"));
} else {
warn!("Length is not set for acline {}", rsr.name);
continue;
}
} else {
warn!("!!Failed to find matrix for line_conf: {s}");
}
}
}
// todo: add other types: transformer\regulator
}
}
}
}
let output = if error.is_none() {
// build schema
let schema = Schema::new(vec![
Field::new("dev_id", DataType::UInt64, false),
Field::new("a_re", DataType::Utf8, false),
Field::new("a_im", DataType::Utf8, false),
Field::new("b_re", DataType::Utf8, false),
Field::new("b_im", DataType::Utf8, false),
Field::new("c_re", DataType::Utf8, false),
Field::new("c_im", DataType::Utf8, false),
Field::new("d_re", DataType::Utf8, false),
Field::new("d_im", DataType::Utf8, false),
]);
let csv_bytes = vec![("".to_string(), csv_str.into_bytes())];
PluginOutput {
error_msg: None,
schema: Some(vec![schema]),
csv_bytes,
}
} else {
PluginOutput {
error_msg: error,
schema: None,
csv_bytes: vec![],
}
};
ciborium::into_writer(&output, &mut OUTPUT).unwrap();
let offset = OUTPUT.as_ptr() as i32;
let len = OUTPUT.len() as u32;
let mut bytes = BytesMut::with_capacity(8);
bytes.put_i32(offset);
bytes.put_u32(len);
bytes.get_u64()
}
fn read_config(records: &mut StringRecordsIter<&[u8]>)
-> Result<HashMap<String, (ArrayBase<OwnedRepr<f64>, Ix2>, ArrayBase<OwnedRepr<f64>, Ix2>)>, String>{
let mut config: HashMap<String, (ArrayBase<OwnedRepr<f64>, Ix2>, ArrayBase<OwnedRepr<f64>, Ix2>)> = HashMap::new();
// 按行读取csv
loop {
match records.next() {
Some(Ok(record)) => {
let mut col = 0;
let mut name = "".to_string();
let mut ohm_per_km = "".to_string();
for str in record.iter() {
if col == 0 {
name = str.to_string();
} else {
ohm_per_km = str.to_string();
}
col += 1;
if col == 2 {
break;
}
}
if col != 2 {
return Err(format!("Wrong config input, expected col more than 2, actual {col}"));
}
match serde_json::from_str::<[f64; 18]>(&ohm_per_km) {
Ok(ohm) => {
let mat_re = Array2::from_shape_vec((3, 3), ohm[0..9].to_vec()).unwrap();
let mat_im = Array2::from_shape_vec((3, 3), ohm[9..18].to_vec()).unwrap();
config.insert(name, (mat_re, mat_im));
}
Err(e) => {
return Err(format!("Failed to parse matrix from {ohm_per_km}, err: {:?}", e));
}
}
}
Some(Err(e)) => {
return Err(format!("{:?}", e));
}
None => {
break;
}
}
}
Ok(config)
}
\ No newline at end of file
[package]
name = "ds-dyn-topo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
log = "0.4"
csv = "1.3.0"
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
eig-domain = { path = "../../../../eig-domain" }
mems = { path = "../../.." }
ds-common = { path = "../ds-common" }
bytes = "1.10"
\ No newline at end of file
use std::collections::HashMap;
use arrow_schema::{DataType, Field, Schema};
use bytes::{Buf, BufMut, BytesMut};
use ds_common::{DEV_TOPO_DF_NAME, DYN_TOPO_DF_NAME, POINT_DF_NAME, STATIC_TOPO_DF_NAME, TERMINAL_DF_NAME};
use ds_common::static_topo::{read_point_terminal, read_static_topo, read_terminal_cn_dev};
use eig_domain::prop::DataUnit;
use mems::model::{get_df_from_in_plugin, get_meas_from_plugin_input, ModelType, PluginInput, PluginOutput};
static mut OUTPUT: Vec<u8> = vec![];
#[no_mangle]
pub unsafe fn run(ptr: i32, len: u32) -> u64 {
// 从内存中获取字符串
let input = unsafe {
let slice = std::slice::from_raw_parts(ptr as _, len as _);
let input: PluginInput = ciborium::from_reader(slice).unwrap();
input
};
let mut error = None;
let r1 = get_meas_from_plugin_input(&input);
if let Err(s) = &r1 {
error = Some(s.clone());
}
let r2 = get_df_from_in_plugin(&input);
// source, target, dev
let mut edges: Vec<Vec<u64>> = vec![];
// switch id to normal open
let mut normal_open: HashMap<u64, bool> = HashMap::new();
// terminal, cn, dev
let mut terminals: Vec<Vec<u64>> = vec![];
// point, terminal
let mut points: Vec<Vec<u64>> = vec![];
if error.is_none() {
if let Err(s) = &r2 {
error = Some(s.clone());
} else {
let mut from = r2.unwrap();
for i in 0..input.dfs_len.len() {
let size = input.dfs_len[i] as usize;
let end = from + size;
let mut rdr = csv::ReaderBuilder::new().has_headers(true).from_reader(&input.bytes[from..end]);
let mut records = rdr.records();
// 开始读取输入的static topology DataFrame
if input.dfs[i] == STATIC_TOPO_DF_NAME {
match read_static_topo(&mut records, Some(&mut normal_open)) {
Ok(v) => edges = v,
Err(s) => error = Some(s),
}
} else if input.dfs[i] == TERMINAL_DF_NAME {
match read_terminal_cn_dev(&mut records, None) {
Ok(v) => terminals = v,
Err(s) => error = Some(s),
}
} else if input.dfs[i] == POINT_DF_NAME {
match read_point_terminal(&mut records, None) {
Ok(v) => points = v,
Err(s) => error = Some(s),
}
}
from += size;
}
}
}
let output = if error.is_none() {
let (meas, units) = r1.unwrap();
let mut point_map: HashMap<u64, u64> = HashMap::with_capacity(points.len());
let mut terminal_cn: HashMap<u64, u64> = HashMap::with_capacity(points.len());
let mut terminal_dev: HashMap<u64, u64> = HashMap::with_capacity(points.len());
for ids in points {
point_map.insert(ids[0], ids[1]);
}
for ids in terminals {
terminal_cn.insert(ids[0], ids[1]);
terminal_dev.insert(ids[0], ids[2]);
}
// 开始构建
let mut closed_switch_to_cn: HashMap<u64, u64> = HashMap::with_capacity(terminal_cn.len() / 2);
// 开始处理开关量
for m in meas {
if let Some(unit) = units.get(&m.point_id) {
if DataUnit::OnOrOff == *unit {
if let Some(terminal_id) = point_map.get(&m.point_id) {
if let Some(cn_id) = terminal_cn.get(terminal_id) {
if let Some(dev_id) = terminal_dev.get(terminal_id) {
if m.get_value2() > 0 {
closed_switch_to_cn.insert(*dev_id, *cn_id);
}
}
}
}
}
}
}
// build tns
let mut cn_tn: HashMap<u64, usize> = HashMap::with_capacity(terminal_cn.len() / 2);
let mut not_dealed = Vec::new();
for v in edges {
let cn1 = v[0];
let cn2 = v[1];
let dev_id = v[2];
// switch with measure
if let Some(cn) = closed_switch_to_cn.get(&dev_id) {
if *cn == cn1 {
if let Some(tn) = cn_tn.get(cn) {
cn_tn.insert(cn2, *tn);
} else {
let tn = cn_tn.len() + 1;
cn_tn.insert(cn1, tn);
cn_tn.insert(cn2, tn);
}
} else if *cn == cn2 {
if let Some(tn) = cn_tn.get(cn) {
cn_tn.insert(cn1, *tn);
} else {
let tn = cn_tn.len() + 1;
cn_tn.insert(cn1, tn);
cn_tn.insert(cn2, tn);
}
}
}
// this is a closed switch with no measure
else if let Some(false) = normal_open.get(&dev_id) {
if let Some(tn) = cn_tn.get(&cn1) {
cn_tn.insert(cn2, *tn);
} else if let Some(tn) = cn_tn.get(&cn2) {
cn_tn.insert(cn1, *tn);
} else {
let tn = cn_tn.len() + 1;
cn_tn.insert(cn1, tn);
cn_tn.insert(cn2, tn);
}
}
// this is open switch or not switch
else {
if !cn_tn.contains_key(&cn1) {
not_dealed.push(cn1);
}
if !cn_tn.contains_key(&cn2) {
not_dealed.push(cn2);
}
}
}
for cn in not_dealed {
if !cn_tn.contains_key(&cn) {
let tn = cn_tn.len() + 1;
cn_tn.insert(cn, tn);
}
}
// get outgoing edges
let mut outgoing = vec![];
for model_input in &input.model {
match model_input {
ModelType::Outgoing(edge_name) => {
outgoing = edge_name.clone();
}
_ => {}
}
}
let mut csv_bytes = Vec::with_capacity(2);
let mut schema = Vec::with_capacity(2);
// 默认或者指定名称
if outgoing.is_empty() || outgoing.contains(&DYN_TOPO_DF_NAME.to_string()) ||
(!outgoing.contains(&DYN_TOPO_DF_NAME.to_string()) && !outgoing.contains(&DEV_TOPO_DF_NAME.to_string())) {
// build topology
let mut topo_csv = String::from("cn,tn\n");
for (cn, tn) in &cn_tn {
topo_csv.push_str(&format!("{cn},{tn}\n"));
}
// build topology schema
let topo_schema = Schema::new(vec![
Field::new("cn", DataType::UInt64, false),
Field::new("tn", DataType::UInt64, false),
]);
csv_bytes.push((DYN_TOPO_DF_NAME.to_string(), topo_csv.into_bytes()));
schema.push(topo_schema);
} else if outgoing.contains(&DEV_TOPO_DF_NAME.to_string()) {
// build dev connection
let mut dev_csv = String::from("terminal,cn,tn,dev\n");
for (terminal, dev) in terminal_dev {
if closed_switch_to_cn.contains_key(&dev) {
continue;
}
if let Some(cn) = terminal_cn.get(&terminal) {
if let Some(tn) = cn_tn.get(cn) {
dev_csv.push_str(&format!("{terminal},{cn},{tn},{dev}\n"));
}
}
}
// build dev connection schema
let dev_schema = Schema::new(vec![
Field::new("terminal", DataType::UInt64, false),
Field::new("cn", DataType::UInt64, false),
Field::new("tn", DataType::UInt64, false),
Field::new("dev", DataType::UInt64, false),
]);
csv_bytes.push((DEV_TOPO_DF_NAME.to_string(), dev_csv.into_bytes()));
schema.push(dev_schema);
}
PluginOutput {
error_msg: None,
schema: Some(schema),
csv_bytes,
}
} else {
PluginOutput {
error_msg: error,
schema: None,
csv_bytes: vec![],
}
};
ciborium::into_writer(&output, &mut OUTPUT).unwrap();
let offset = OUTPUT.as_ptr() as i32;
let len = OUTPUT.len() as u32;
let mut bytes = BytesMut::with_capacity(8);
bytes.put_i32(offset);
bytes.put_u32(len);
bytes.get_u64()
}
\ No newline at end of file
[package]
name = "ds-tn-input"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
log = "0.4"
csv = "1.3.0"
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
ds-common = { path = "../ds-common" }
eig-domain = { path = "../../../../eig-domain" }
mems = { path = "../../.." }
bytes = "1.10"
\ No newline at end of file
[package]
name = "ds-static-topo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
log = "0.4"
ciborium = "0.2"
arrow-schema = { version = "56.1", features = ["serde"] }
petgraph = "0.8"
mems = { path = "../../.." }
bytes = "1.10"
\ No newline at end of file
use std::collections::HashMap;
use arrow_schema::{DataType, Field, Schema};
use bytes::{Buf, BufMut, BytesMut};
use petgraph::graph::UnGraph;
use mems::model::{get_csv_str, get_island_from_plugin_input, ModelType, PluginInput, PluginOutput};
use mems::model::dev::{CN, Island, PropDefine, PsRsrType, RsrDefine};
// use std::fs;
// use std::io::Write;
// use std::path::PathBuf;
const NORMAL_OPEN: &str = "normalOpen";
const STATIC_TOPO_DF_NAME: &str = "static_topo";
const TERMINAL_DF_NAME: &str = "terminal_cn_dev";
const POINT_DF_NAME: &str = "point_terminal_phase";
static mut OUTPUT: Vec<u8> = vec![];
#[no_mangle]
pub unsafe fn run(ptr: i32, len: u32) -> u64 {
// 从内存中获取字符串
let input = unsafe {
let slice = std::slice::from_raw_parts(ptr as _, len as _);
let input: PluginInput =ciborium::from_reader(slice).unwrap();
input
};
let mut error = None;
// 获取island
let r = get_island_from_plugin_input(&input);
if let Err(s) = &r {
error = Some(s.clone());
}
let output = if error.is_none() {
// 获取电气岛、属性定义、资源定义
let (island, prop_defs, defines) = r.unwrap();
let mut outgoing = vec![];
// 获取输出的
for model_input in &input.model {
match model_input {
ModelType::Outgoing(edge_name) => {
outgoing = edge_name.clone();
}
_ => {}
}
}
let mut csv_bytes = vec![];
let mut schema = vec![];
// create file
// let mut base = PathBuf::from("/");
// base.push("static_graph.csv");
// let mut file = fs::OpenOptions::new()
// .create(true)
// .write(true)
// .truncate(true)
// .open(&base)
// .expect("Could not create file");
// write graph
// 根据输出名称来确定形成不同的data frame
if outgoing.is_empty() || outgoing.contains(&STATIC_TOPO_DF_NAME.to_string()) {
create_static_topo(&island, &prop_defs, &defines, &mut csv_bytes, &mut schema);
}
else if outgoing.contains(&TERMINAL_DF_NAME.to_string()) {
let mut terminal_csv_str = String::from("terminal,cn,dev,type\n");
let mut terminal_to_cn = HashMap::with_capacity(2 * island.cns.len());
// 先建立CN对应的节点
for cn in &island.cns {
for terminal in &cn.terminals {
terminal_to_cn.insert(*terminal, cn.id);
}
}
for (id, dev) in &island.resources {
for terminal in &dev.terminals {
let terminal_id = terminal.id;
if let Some(cn_id) = terminal_to_cn.get(&terminal_id) {
let dev_type = if let Some(def) = defines.get(&dev.define_id) {
def.rsr_type as u16
} else {
0u16
};
terminal_csv_str.push_str(format!("{terminal_id},{cn_id},{id},{dev_type}\n").as_str());
}
}
}
csv_bytes.push((TERMINAL_DF_NAME.to_string(), terminal_csv_str.into_bytes()));
schema.push(Schema::new(vec![
Field::new("terminal", DataType::UInt64, false),
Field::new("cn", DataType::UInt64, false),
Field::new("dev", DataType::UInt64, false),
Field::new("type", DataType::UInt32, false),
]));
}
// if let Err(e) = file.write_all(csv_str.as_bytes()) {
// log::warn!("!!Failed to write file, err: {:?}", e);
// } else {
// let _ = file.sync_all();
// }
else if outgoing.contains(&POINT_DF_NAME.to_string()) {
let mut point_csv_str = String::from("point,terminal,phase\n");
for (_, defines) in &island.measures {
for def in defines {
let point_id = def.point_id;
let terminal_id = def.terminal_id;
let phase = def.phase.to_string();
point_csv_str.push_str(&format!("{point_id},{terminal_id},{phase}\n"))
}
}
csv_bytes.push((POINT_DF_NAME.to_string(), point_csv_str.into_bytes()));
schema.push(Schema::new(vec![
Field::new("point", DataType::UInt64, false),
Field::new("terminal", DataType::UInt64, false),
Field::new("phase", DataType::Utf8, false),
]));
}
PluginOutput {
error_msg: None,
schema: Some(schema),
csv_bytes,
}
} else {
PluginOutput {
error_msg: error,
schema: None,
csv_bytes: vec![],
}
};
ciborium::into_writer(&output, &mut OUTPUT).unwrap();
let offset = OUTPUT.as_ptr() as i32;
let len = OUTPUT.len() as u32;
let mut bytes = BytesMut::with_capacity(8);
bytes.put_i32(offset);
bytes.put_u32(len);
bytes.get_u64()
}
fn create_static_topo(island: &Island, prop_defs: &[PropDefine], defines: &HashMap<u64, RsrDefine>, csv_bytes: &mut Vec<(String, Vec<u8>)>, schema: &mut Vec<Schema>) {
let mut topo_csv_str = String::from("source,target,id,type,open,name\n");
// build node_switch_model
let mut ori_graph: UnGraph<CN, u64> = UnGraph::new_undirected();
let mut terminal_to_idx = HashMap::with_capacity(2 * island.cns.len());
// 先建立CN对应的节点
for cn in &island.cns {
let index = ori_graph.add_node(cn.clone());
for terminal in &cn.terminals {
terminal_to_idx.insert(*terminal, index);
}
}
// 建立有两个terminal设备形成的边
for (id, dev) in &island.resources {
if dev.terminals.len() != 2 {
continue;
}
if let Some(cn1) = terminal_to_idx.get(&dev.terminals[0].id) {
if let Some(cn2) = terminal_to_idx.get(&dev.terminals[1].id) {
ori_graph.add_edge(*cn1, *cn2, *id);
}
}
}
let mut prop_defines = HashMap::with_capacity(prop_defs.len());
for def in prop_defs.into_iter() {
prop_defines.insert(def.id, def);
}
for edge in ori_graph.raw_edges() {
let s = edge.source();
let t = edge.target();
let cn1 = ori_graph.node_weight(s);
let cn2 = ori_graph.node_weight(t);
if cn1.is_none() || cn2.is_none() {
log::warn!("!!Failed to find nodes for edge {}", edge.weight);
topo_csv_str = String::from("source,target,id,type,name\n");
break;
}
let id1 = cn1.unwrap().id;
let id2 = cn2.unwrap().id;
let dev_id = edge.weight;
let mut dev_name = "".to_string();
let mut dev_type = 0u16;
let mut normal_open = "".to_string();
if let Some(rsr) = island.resources.get(&dev_id) {
dev_name = get_csv_str(&rsr.name);
if let Some(def) = defines.get(&rsr.define_id) {
dev_type = def.rsr_type as u16;
if def.rsr_type == PsRsrType::Switch {
let v = rsr.get_prop_value2(NORMAL_OPEN, &island.prop_groups, &prop_defines);
if let Some(b) = v.get_bool() {
normal_open = b.to_string();
}
}
}
}
topo_csv_str.push_str(format!("{id1},{id2},{dev_id},{dev_type},{normal_open},{dev_name}\n").as_str());
}
csv_bytes.push((STATIC_TOPO_DF_NAME.to_string(), topo_csv_str.into_bytes()));
schema.push(Schema::new(vec![
Field::new("source", DataType::UInt64, false),
Field::new("target", DataType::UInt64, false),
Field::new("id", DataType::UInt64, false),
// if using uint16, will get: unsupported data type when reading CSV: u16
Field::new("type", DataType::UInt32, false),
Field::new("open", DataType::Boolean, true),
Field::new("name", DataType::Utf8, true),
]));
}
\ No newline at end of file
[package]
name = "iesplan"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = "1"
serde_cbor = "0.11"
wasm-bindgen = "0.2"
yew = { version = "0.21", features = ["csr"] }
gloo-timers = "0.3"
#getrandom = { version = "0.3", features = ["wasm_js"] }
csv = "1.3"
yew-bulma = { git = "https://github.com/shufengdong/yew-bulma.git" }
eig-domain = { path = "../../../eig-domain" }
# web-sys and js-sys
js-sys = "0.3"
[dependencies.web-sys]
version = "0.3"
features = ["FormData", "File"]
[profile.release]
# Tell `rustc` to optimize for small code size.
# 二进制文件会包含更少的代码
panic = 'abort'
# 对所有代码库进行优化(优化更好,构建更慢)
codegen-units = 1
# 优化大小(更加激进)
opt-level = 0
# 优化大小
# opt-level = 's'
# 对整个程序进行链接时优化(link time optimization)
lto = true
#[package.metadata.wasm-pack.profile.release]
#wasm-opt = false
\ No newline at end of file
0,计算模式选择,,
4000002,计算模式,Radio,经济性最优:1.0;环保性最优:2;能效性最优:3
10,冷负荷曲线设置,,
4000028,过渡季,TextField,
4000029,夏季,TextField,
4000030,冬季,TextField,
11,其他参数,,
4000043,年利率,TextField,
4000044,天然气价,TextField,
2,设备选择,,
4000006,燃气轮机,Checkbox,
4000007,燃气锅炉,Checkbox,
4000008,热泵,Checkbox,
4000009,吸收式制冷机,Checkbox,
4000010,电制冷机,Checkbox,
4000011,电储能,Checkbox,
4000012,蓄热罐,Checkbox,
4000013,蓄冷罐,Checkbox,
3,机组最大数量,,
4000014,燃气轮机,TextField,
4000015,燃气锅炉,TextField,
4000016,热泵,TextField,
4000017,吸收式制冷机,TextField,
4000018,电制冷机,TextField,
4000019,电储能,TextField,
4000020,蓄热罐,TextField,
4000021,蓄冷罐,TextField,
4,电负荷曲线设置,,
4000022,过渡季,TextField,
4000023,夏季,TextField,
4000024,冬季,TextField,
5,风电曲线设置,,
4000031,过渡季,TextField,
4000032,夏季,TextField,
4000033,冬季,TextField,
6,热负荷曲线设置,,
4000025,过渡季,TextField,
4000026,夏季,TextField,
4000027,冬季,TextField,
7,光伏曲线设置,,
4000034,过渡季,TextField,
4000035,夏季,TextField,
4000036,冬季,TextField,
8,电价曲线设置,,
4000040,过渡季,TextField,
4000041,夏季,TextField,
4000042,冬季,TextField,
9,环境平均温度,,
4000037,过渡季,TextField,
4000038,夏季,TextField,
4000039,冬季,TextField,
use crate::startpage::StartPage;
use eig_domain::excel::get_first_sheet_merged_cells;
use eig_domain::{csv_str, csv_string, csv_u64, csv_usize, SetPointValue};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use web_sys::{Element, Headers};
use yew_bulma::layout::tiles::Tiles;
pub mod startpage;
mod paracard;
#[wasm_bindgen]
pub fn create_view(e: Element) {
yew::Renderer::<StartPage>::with_root(e).render();
}
#[wasm_bindgen(raw_module = "/mems-view-bin.js")]
extern "C" {
pub fn get_headers() -> Headers;
pub fn get_user_id() -> u16;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PointControl3 {
pub commands: Vec<SetPointValue>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct QueryWithId {
pub id: Option<u64>,
pub ids: Option<String>,
}
impl QueryWithId {
pub fn query_str(&self) -> String {
let mut query = String::new();
if let Some(id) = self.id {
query.push_str(&format!("?id={}", id));
} else if let Some(ids) = &self.ids {
query.push_str(&format!("?ids={ids}"));
}
query
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParaType {
// show expression, true expression, false expression
Checkbox,
Switch,
Radio(Vec<(String, f64)>),
Select(Vec<(String, f64)>),
// min, max, step
Slider(f64, f64, f64),
TextField,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Parameters {
id: usize,
name: String,
labels: Vec<String>,
points: Vec<u64>,
para_types: Vec<ParaType>,
}
pub fn create_parameters(content: &[u8]) -> Parameters {
let mut rdr = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(content);
let mut records = rdr.records();
let record = records.next().unwrap().unwrap();
let id = csv_usize(&record, 0).unwrap();
let name = csv_string(&record, 1).unwrap();
let mut labels = Vec::new();
let mut points = Vec::new();
let mut para_types = Vec::new();
for record in records {
let row = record.unwrap();
points.push(csv_u64(&row, 0).unwrap());
labels.push(csv_string(&row, 1).unwrap());
let para_type_s = csv_str(&row, 2).unwrap().to_uppercase();
let para_type = match para_type_s.as_str() {
"CHECKBOX" => ParaType::Checkbox,
"SWITCH" => ParaType::Switch,
"TEXTFIELD" => ParaType::TextField,
"SLIDER" => {
let v = csv_str(&row, 3).unwrap();
let s_vec: Vec<&str> = v.split(";").collect();
let min = s_vec[0].parse().unwrap();
let max = s_vec[1].parse().unwrap();
let step = s_vec[2].parse().unwrap();
ParaType::Slider(min, max, step)
},
"SELECT" => {
let v = csv_str(&row, 3).unwrap();
let options = parse_options(v);
ParaType::Select(options)
}
"RADIO" => {
let v = csv_str(&row, 3).unwrap();
let options = parse_options(v);
ParaType::Radio(options)
}
_ => ParaType::TextField
};
para_types.push(para_type);
}
Parameters { id, name, labels, points, para_types }
}
fn parse_options(v: &str) -> Vec<(String, f64)> {
let options = v.split(";")
.map(|s| {
let options: Vec<&str> = s.split(":").collect();
if options.len() == 2 {
(options[0].to_string(), options[1].parse::<f64>().unwrap())
} else {
("".to_string(), s.parse::<f64>().unwrap())
}
}).collect();
options
}
pub fn build_tiles(xlsx_bytes: Vec<u8>) -> Option<Tiles> {
let (m, n, merge_map, values) = get_first_sheet_merged_cells(xlsx_bytes)?;
let mut class_str = Vec::new();
let mut style_str = Vec::new();
let mut is_dealt = vec![false; (m * n) as usize];
for i in 0..m {
for j in 0..n {
let index = (i * n + j) as usize;
if is_dealt[index] {
continue;
}
let mut class_s = "kanban-div cell".to_string();
let coordinate = (i, j);
if let Some((end_row, end_col)) = merge_map.get(&coordinate) {
let row_span = *end_row - i + 1;
let col_span = *end_col - j + 1;
if row_span > 1 {
class_s.push_str(&format!(" is-row-span-{row_span}"))
}
if col_span > 1 {
class_s.push_str(&format!(" is-col-span-{col_span}"))
}
class_str.push(class_s);
for row in i..=*end_row {
for col in j..=*end_col {
let pos = (row * n + col) as usize;
is_dealt[pos] = true;
}
}
} else {
class_str.push(class_s);
}
let h = values.get(&index).cloned().unwrap_or("100".to_string());
let s = format!("height:{h}px");
style_str.push(s);
}
}
let tiles = Tiles { id: "".to_string(), class_str, style_str, with_box: true };
Some(tiles)
}
\ No newline at end of file
use gloo_timers::callback::Timeout;
use std::collections::HashMap;
use yew::prelude::*;
use yew_bulma::layout::tiles::Tiles;
use yew_bulma::*;
use crate::paracard::ParaCard;
use crate::{build_tiles, create_parameters, Parameters};
pub enum Msg {
Start,
Stop,
}
pub struct StartPage {
tiles: Tiles,
cards: Vec<Parameters>,
timer: Option<Timeout>,
is_running: bool
}
impl Component for StartPage {
type Message = Msg;
type Properties = ();
fn create(_: &Context<Self>) -> Self {
let tiles = build_tiles(include_bytes!("../layoutV3.xlsx").to_vec()).unwrap();
let card0 = create_parameters(include_bytes!("../card/card0.csv"));
let card2 = create_parameters(include_bytes!("../card/card2.csv"));
let card3 = create_parameters(include_bytes!("../card/card3.csv"));
let card4 = create_parameters(include_bytes!("../card/card4.csv"));
let card5 = create_parameters(include_bytes!("../card/card5.csv"));
let card6 = create_parameters(include_bytes!("../card/card6.csv"));
let card7 = create_parameters(include_bytes!("../card/card7.csv"));
let card8 = create_parameters(include_bytes!("../card/card8.csv"));
let card9 = create_parameters(include_bytes!("../card/card9.csv"));
let card10 = create_parameters(include_bytes!("../card/card10.csv"));
let card11 = create_parameters(include_bytes!("../card/card11.csv"));
let cards = vec![card0, card2, card3, card4, card5, card6, card7, card8, card9, card10, card11];
Self { tiles, cards, timer: None, is_running: false }
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
let link = ctx.link();
match msg {
Msg::Start => {
self.is_running = true;
let stop_running = link.callback(|()| Msg::Stop);
let timeout = Timeout::new(5_000, move || {
stop_running.emit(());
});
self.timer = Some(timeout);
}
Msg::Stop => {
self.is_running = false;
}
}
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
let mut nodes = HashMap::with_capacity(12);
nodes.insert(1, html! {
<Button loading={self.is_running} classes={classes!("is-primary", "is-fullwidth", "is-large")}
onclick={link.callback(|_|Msg::Start)}>{"开始计算"}</Button>
});
for card in &self.cards {
nodes.insert(card.id, html! {
<ParaCard paras={card.clone()} />
});
}
self.tiles.create_html(nodes)
}
fn destroy(&mut self, _: &Context<Self>) {
if let Some(timer) = self.timer.take() {
timer.cancel();
}
}
}
pub mod model;
// ============= 对webapp.rs中额外暴露给mems的API进行apidoc注释-开始
// ============= 因为在mems的API中过滤了webapp.rs,所以要在此处额外添加
// ============= 另一种方式是把webapp.rs拆分开两个文件,但尽量还是不动代码,就使用这种变通方式
/// public api
/**
* @api {get} /api/v1/measures 查询历史量测
* @apiGroup Webapp_Result
* @apiUse HisQuery
* @apiSuccess {PbPointValues} PbPointValues 测点值对象
*/
/**
* @api {get} /api/v1/soes 查询SOE
* @apiPrivate
* @apiGroup Webapp_Result
* @apiUse HisQuery
* @apiSuccess {PbPointValues} PbPointValues SOE结果,结果按照时间排序
*/
/// public api
/**
* @api {get} /api/v1/aoe_results 查询AOE执行结果
* @apiGroup Webapp_Result
* @apiUse HisQuery
* @apiSuccess {PbAoeResults} PbAoeResults AOE执行结果
*/
/**
* @api {get} /api/v1/commands 查询历史设点执行结果
* @apiPrivate
* @apiGroup Webapp_Result
* @apiUse HisSetPointQuery
* @apiSuccess {PbSetPointResults} PbSetPointResults 历史设点执行结果
*/
/// public api
/**
* @api {get} /api/v1/alarms 查询告警
* @apiGroup Webapp_Result
* @apiUse HisQuery
* @apiSuccess {PbEigAlarms} PbEigAlarms 告警结果,结果按照时间排序
*/
// ============= 对webapp.rs中额外暴露给mems的API进行apidoc注释-结束
/// 解析URL路径中带逗号,的值,返回数组
pub fn parse_path_values<T: std::str::FromStr>(path: &str) -> Vec<T> {
let values_str: Vec<&str> = path.split(',').collect();
let mut vec: Vec<T> = Vec::with_capacity(values_str.len());
for value_str in values_str {
if let Ok(v) = value_str.trim().parse() {
vec.push(v);
}
}
vec
}
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use eig_domain::{prop::DataUnit, MeasureValue};
use crate::model::dev::{Island, PropDefine, RsrDefine};
pub mod dev;
pub mod plan;
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub enum ModelType {
Island,
Meas,
File(Vec<String>),
Outgoing(Vec<String>),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PluginInput {
pub model: Vec<ModelType>,
pub model_len: Vec<u32>,
pub dfs: Vec<String>,
pub dfs_len: Vec<u32>,
pub bytes: Vec<u8>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PluginOutput {
pub error_msg: Option<String>,
pub schema: Option<Vec<arrow_schema::Schema>>,
pub csv_bytes: Vec<(String, Vec<u8>)>,
}
pub fn get_island_from_plugin_input(input: &PluginInput) -> Result<(Island, Vec<PropDefine>, HashMap<u64, RsrDefine>), String> {
let mut from = 0;
let mut index = 0;
for i in 0..input.model.len() {
match input.model[i] {
ModelType::Island => {
if input.model_len.len() <= index {
return Err("model_len length error".to_string());
}
let size = input.model_len[index] as usize;
let end = from + size;
let r = ciborium::from_reader(&input.bytes[from..end]);
if r.is_err() {
return Err(format!("{:?}", r));
}
from += size;
let island = r.unwrap();
index += 1;
if input.model_len.len() <= index {
return Err("model_len length error".to_string());
}
let size = input.model_len[index] as usize;
let end = from + size;
let r = ciborium::from_reader(&input.bytes[from..end]);
if r.is_err() {
return Err(format!("{:?}", r));
}
from += size;
let defines = r.unwrap();
index += 1;
if input.model_len.len() <= index {
return Err("model_len length error".to_string());
}
let size = input.model_len[index] as usize;
let end = from + size;
let r = ciborium::from_reader(&input.bytes[from..end]);
if r.is_err() {
return Err(format!("{:?}", r));
}
let prop_defs = r.unwrap();
return Ok((island, prop_defs, defines));
}
ModelType::Meas => {
if input.model_len.len() <= index + 2 {
return Err("model_len length error".to_string());
}
let size1 = input.model_len[index] as usize;
let size2 = input.model_len[index + 1] as usize;
from += size1;
from += size2;
index += 2;
}
_ => {}
}
}
Err("Island not found in plugin input".to_string())
}
pub fn get_meas_from_plugin_input(input: &PluginInput) -> Result<(Vec<MeasureValue>, HashMap<u64, DataUnit>), String> {
let mut from = 0;
let mut index = 0;
for i in 0..input.model.len() {
match input.model[i] {
ModelType::Meas => {
if input.model_len.len() < index {
return Err("model_len length error".to_string());
}
let size = input.model_len[index] as usize;
let end = from + size;
let r = ciborium::from_reader(&input.bytes[from..end]);
if r.is_err() {
return Err(format!("{:?}", r));
}
from += size;
let meas = r.unwrap();
index += 1;
if input.model_len.len() <= index {
return Err("model_len length error".to_string());
}
let size = input.model_len[index] as usize;
let end = from + size;
let r = ciborium::from_reader(&input.bytes[from..end]);
if r.is_err() {
return Err(format!("{:?}", r));
}
let units = r.unwrap();
return Ok((meas, units));
}
ModelType::Island => {
if input.model_len.len() < index + 3 {
return Err("model_len length error".to_string());
}
let size1 = input.model_len[index] as usize;
let size2 = input.model_len[index + 1] as usize;
let size3 = input.model_len[index + 2] as usize;
from += size1;
from += size2;
from += size3;
index += 3;
}
_ => {}
}
}
Err("Measure not found in plugin input".to_string())
}
pub fn get_df_from_in_plugin(input: &PluginInput) -> Result<usize, String> {
let mut from = 0;
let mut index = 0;
for i in 0..input.model.len() {
match input.model[i] {
ModelType::Meas => {
if input.model_len.len() < index + 2 {
return Err(format!("model_len length error, expect more then {}, actual {}",
index + 2, input.model_len.len()));
}
let size1 = input.model_len[index] as usize;
let size2 = input.model_len[index + 1] as usize;
from += size1;
from += size2;
index += 2;
}
ModelType::Island => {
if input.model_len.len() < index + 3 {
return Err(format!("model_len length error, expect more then {}, actual {}",
index + 3, input.model_len.len()));
}
let size1 = input.model_len[index] as usize;
let size2 = input.model_len[index + 1] as usize;
let size3 = input.model_len[index + 2] as usize;
from += size1;
from += size2;
from += size3;
index += 3;
}
_ => {}
}
}
Ok(from)
}
// #[inline]
// pub fn get_wasm_result(output: PluginOutput) -> u64 {
// // 下面的unwrap是必要的,否则输出的字节无法解析
// let mut v = Vec::new();
// ciborium::into_writer(&output, &mut v).unwrap();
// v.shrink_to_fit();
// let offset = v.as_ptr() as i32;
// let len = v.len() as u32;
// let mut bytes = BytesMut::with_capacity(8);
// bytes.put_i32(offset);
// bytes.put_u32(len);
// return bytes.get_u64();
// }
#[inline]
pub fn get_csv_str(s: &str) -> String {
if s.contains(',') || s.contains('\n') || s.contains('"')
|| s.starts_with(' ') || s.ends_with(' ') {
format!("\"{}\"", s.replace('\"', "\"\""))
} else {
s.to_string()
}
}
use serde::{Deserialize, Serialize};
/**
* @api {计划对象} /DayPlan DayPlan
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} id 计划id
* @apiSuccess {String} name 计划名称
* @apiSuccess {String} [desc] 计划描述
* @apiSuccess {tuple[]} plan 计划内容数组,tuple格式为(开始时间:u64, 结束时间:u64, 功率值:f64)
*/
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct DayPlan {
pub id: u64,
pub name: String,
pub desc: String,
pub plan: Vec<(u64, u64, f64)>,
}
/**
* @api {计划树节点} /PlanTreeNode PlanTreeNode
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {String} path 路径
* @apiSuccess {String} name 名称
* @apiSuccess {String} [desc] 描述
* @apiSuccess {u64} [ref_id] 计划ID,如果是普通节点,则为None
*/
/// 计划树节点
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct PlanTreeNode {
pub path: String,
pub name: String,
pub desc: Option<String>,
// 计划ID,如果是普通节点,则为None
pub ref_id: Option<u64>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub enum ScriptTarget {
Aoe,
Dff,
}
/**
* @api {MemsScript} /MemsScript MemsScript
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} id 脚本id
* @apiSuccess {String} path 脚本路径
* @apiSuccess {String} desc 脚本描述
* @apiSuccess {bool} is_need_island 是否需要电气岛
* @apiSuccess {u64[]} plans 计划列表
* @apiSuccess {String} wasm_module_name wasm模块名称
* @apiSuccess {u64} wasm_update_time wasm上传时间
* @apiSuccess {bool} is_file_uploaded 文件是否已上传
* @apiSuccess {bool} is_js 是否是javascript文件
*/
// 脚本
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct MemsScript {
pub id: u64,
pub target : ScriptTarget,
pub path: String,
pub desc: String,
// 生成aoe script
pub wasm_module_name: String,
pub wasm_update_time: u64,
pub is_file_uploaded: bool,
pub is_js: bool,
}
/**
* @api {ScriptWasmFile} /ScriptWasmFile ScriptWasmFile
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} script_id 脚本id
* @apiSuccess {String} module_name 模块名称
* @apiSuccess {u8[]} wasm_file wasm文件
* @apiSuccess {u8[]} js_file js文件
*/
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ScriptWasmFile {
pub script_id: u64,
pub module_name: String,
pub wasm_file: Vec<u8>,
pub js_file: Vec<u8>,
}
/**
* @api {AoeMakeResult} /AoeMakeResult AoeMakeResult
* @apiPrivate
* @apiGroup A_Object
* @apiSuccess {u64} script_id script_id
* @apiSuccess {u64} make_time make_time
* @apiSuccess {u64} aoe_model_id aoe_model_id
* @apiSuccess {u32} island_version 电气岛版本号
*/
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ScriptResult {
pub script_id: u64,
pub make_time: u64,
pub model_id: u64,
pub target: ScriptTarget,
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论