feat: 添加变量异常过滤功能

This commit is contained in:
Diego
2026-03-19 17:55:23 +08:00
parent 3ae2801386
commit 12a4843516
14 changed files with 585 additions and 18 deletions
+5 -5
View File
@@ -12,12 +12,12 @@
<NoWarn>
CS8002;NU1510;SYSLIB5007;CS8603;CS8618;CS1591;CS8625;CS8602;CS8604;CS8600;CS8601;CS8714;CS8619;CS8629;CS8765;CS8634;CS8621;CS8767;CS8633;CS8620;CS8610;CS8631;CS8605;CS8622;CS8613;NU5100;NU5104;NU1903;NU1902;CA1863;CA1812;CA1805;CA1515;CA1508;CA1819;CA1852;CA5394;CA1822;CA1815;CA1813;CA5358;CA5384;CA5400;CA5401;CA1814;CA1835;CA5392;CA5350;CA2100;CA1848;CA1810;CA1513;CA5351;CA1510;CA1512;CA1823;RCS1102;RCS1194;NETSDK1206
</NoWarn>
<TouchSocketVersion>4.1.10</TouchSocketVersion>
<AdminVersion>12.5.2</AdminVersion>
<FoundationVersion>12.5.5</FoundationVersion>
<PluginVersion>12.5.6</PluginVersion>
<TouchSocketVersion>4.1.12</TouchSocketVersion>
<AdminVersion>12.5.3</AdminVersion>
<FoundationVersion>12.5.6</FoundationVersion>
<PluginVersion>12.5.7</PluginVersion>
<ProPluginVersion>12.5.4</ProPluginVersion>
<GatewayVersion>12.5.4</GatewayVersion>
<GatewayVersion>12.5.5</GatewayVersion>
<Version>$(GatewayVersion)</Version>
<ManagementVersion>12.5.1</ManagementVersion>
+264
View File
@@ -0,0 +1,264 @@
# 变量异常值过滤功能说明
## 1. 功能概述
为 `Variable`(设备变量)新增 **异常值过滤(Outlier Filter)** 能力。当采集到的变量值发生变化时,系统会自动对新值进行异常检测,拦截不合理的数据,防止异常数据进入业务流程。
### 设计原则
- **仅在线时生效**:设备离线时不执行过滤,避免干扰离线状态处理。
- **仅在值变化时检查**:值未发生变化时不会触发过滤逻辑,减少不必要的计算开销。
- **多级过滤、顺序执行**:三种过滤模式按顺序依次检查,任一模式判定异常即标记为异常值。
- **连续异常容错**:当异常值连续出现达到设定次数时,认定数据确实发生了变化,自动接受该值。
---
## 2. 三种过滤模式详解
### 2.1 合理范围过滤
检查新值是否在预设的合理范围 `[最小值, 最大值]` 内。超出范围的值被视为异常。
**典型场景**:温度传感器量程为 -40°C ~ 120°C,超出此范围的值显然是采集异常。
#### 配置属性
| 属性名 | 数据库列 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
| `RangeFilterEnable` | `合理范围过滤使能` | `bool` | `false` | 是否启用合理范围过滤 |
| `RangeFilterMinValue` | `最小值` | `decimal` | `0` | 允许的最小值(含) |
| `RangeFilterMaxValue` | `最大值` | `decimal` | `100` | 允许的最大值(含) |
#### 判定逻辑
```
如果 新值 < 最小值 或 新值 > 最大值:
→ 标记为异常
```
---
### 2.2 变化幅度过滤
检查新值与上次值之间的变化幅度是否超过允许上限。支持 **绝对值** 和 **百分比** 两种判定方式,两者同时生效。
**典型场景**:压力变送器正常波动范围为 ±5 单位 或 ±10%,突然跳变 100 单位显然是采集异常。
#### 配置属性
| 属性名 | 数据库列 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
| `AmplitudeFilterEnable` | `变化幅度过滤使能` | `bool` | `false` | 是否启用变化幅度过滤 |
| `MaxAmplitude` | `最大变化幅度` | `decimal` | `100` | 允许的最大绝对变化量(**> 0 时生效**,设为 0 则跳过绝对值检查) |
| `MaxAmplitudePercent` | `最大变化幅度%` | `decimal` | `100` | 允许的最大变化百分比(单位:%)(**> 0 时生效**,设为 0 则跳过百分比检查) |
#### 判定逻辑
```
绝对变化量 = |新值 - 旧值|
步骤1 — 绝对值检查(仅 MaxAmplitude > 0 时执行):
如果 绝对变化量 > MaxAmplitude:
→ 标记为异常
步骤2 — 百分比检查(仅 MaxAmplitudePercent > 0 且 步骤1未触发 且 旧值≠0 时执行):
变化百分比 = 绝对变化量 / |旧值| × 100
如果 变化百分比 > MaxAmplitudePercent:
→ 标记为异常
```
> **注意**:
> - 当 `MaxAmplitude` 设为 `0` 时,跳过绝对值检查,仅执行百分比检查。
> - 当 `MaxAmplitudePercent` 设为 `0` 时,跳过百分比检查,仅执行绝对值检查。
> - 两者同时设为 `0` 时,即使 `AmplitudeFilterEnable` 为 `true`,幅度过滤也不会触发任何拦截。
> - 当旧值为 `0` 时,跳过百分比检查(避免除零错误)。
#### 两种方式的区别
| 对比维度 | 绝对值过滤 (`MaxAmplitude`) | 百分比过滤 (`MaxAmplitudePercent`) |
|---|---|---|
| 适用场景 | 变量值数量级固定 | 变量值数量级差异大 |
| 示例 | 温度波动不超过 ±5°C | 流量变化不超过 ±20% |
| 优势 | 简单直观 | 自适应不同数量级 |
---
### 2.3 连续异常过滤
当异常值 **连续出现** 达到设定次数后,系统认定数据确实发生了合理变化(如设备参数被人工调整),自动接受该值并清零计数器。
**典型场景**:温度设定值从 50°C 调整到 200°C,首次变化幅度超限被拦截,但如果连续 3 次都是 200°C,则说明确实发生了变化,应当接受。
#### 配置属性
| 属性名 | 数据库列 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
| `ContinuousFilterEnable` | `连续异常过滤使能` | `bool` | `false` | 是否启用连续异常过滤 |
| `MaxContinuousCount` | `连续异常最大次数` | `int` | `3` | 连续异常值达到此次数后接受该值 |
#### 判定逻辑
```
当值被判定为异常时:
如果 连续异常过滤已启用:
连续异常计数器++
如果 计数器 >= MaxContinuousCount:
→ 计数器清零,接受该值(返回 true)
否则:
→ 拒绝该值(返回 false)
否则:
→ 直接拒绝该值(返回 false)
当值正常(未被判定为异常)时:
→ 计数器清零
```
> **重要**:连续异常计数器仅在值实际变化(`changed == true`)时才递增,重复采集到相同的值不会增加计数。
---
## 3. 整体执行流程
```
┌─────────────────────────────────────────────────────┐
│ Set() 方法 │
│ │
│ 1. 计算值是否变化 (changed) │
│ 2. 检查条件: (changed || setChanged) && _isOnline │
│ ├─ 条件不满足 → 跳过过滤,直接赋值 │
│ └─ 条件满足 → 调用 ApplyOutlierFilter(data) │
│ │
├─────────────────────────────────────────────────────┤
│ ApplyOutlierFilter(data) │
│ │
│ 1. 前置检查 │
│ ├─ 无任何过滤启用 → 直接通过 │
│ └─ 数据无法转为数值 → 直接通过(不过滤非数值类型) │
│ │
│ 2. 合理范围过滤 │
│ └─ 新值不在 [Min, Max] → 标记异常 │
│ │
│ 3. 绝对幅度过滤(MaxAmplitude > 0 时) │
│ └─ |新值 - 旧值| > MaxAmplitude → 标记异常 │
│ │
│ 4. 百分比幅度过滤(MaxAmplitudePercent > 0 且旧值≠0) │
│ └─ 变化% > MaxAmplitudePercent → 标记异常 │
│ │
│ 5. 异常结果处理 │
│ ├─ 未异常 → 计数器清零,返回 true(接受) │
│ └─ 已异常 │
│ ├─ 连续过滤启用 │
│ │ ├─ 计数 >= 上限 → 清零,返回 true(接受) │
│ │ └─ 计数 < 上限 → 返回 false(拒绝) │
│ └─ 连续过滤未启用 → 返回 false(拒绝) │
│ │
├─────────────────────────────────────────────────────┤
│ 返回到 Set() │
│ │
│ ├─ ApplyOutlierFilter 返回 true → 正常赋值流程 │
│ └─ ApplyOutlierFilter 返回 false │
│ → 触发 VariableCollectChange(更新采集时间) │
│ → Set() 返回 false │
│ → SetValue() 返回错误信息 │
│ → 变量值不更新,保持上一次的有效值 │
└─────────────────────────────────────────────────────┘
```
---
## 4. 涉及文件清单
### 4.1 后端代码
| 文件路径 | 变更说明 |
|---|---|
| `ThingsGateway.Gateway.Application/Entity/Variable.cs` | 新增 8 个数据库持久化属性(`[OrmColumn]`),分别对应三种过滤模式的配置 |
| `ThingsGateway.Gateway.Application/Model/VariableRunTime.cs` | `Set()` 方法返回类型改为 `bool`;新增 `ApplyOutlierFilter()` 过滤逻辑方法;新增 `TryConvertToDecimal()` 辅助方法;新增 `_continuousAnomalyCount` 计数器字段 |
### 4.2 前端 UI
| 文件路径 | 变更说明 |
|---|---|
| `ThingsGateway.Gateway.Razor/Pages/.../VariableEditComponent.razor` | 变量编辑弹窗新增第三个 Tab 页 **"异常值过滤"**,包含三组过滤配置表单 |
| `ThingsGateway.Management.Razor/Pages/.../VariableEditComponent.razor` | 管理端同步新增相同的 Tab 页 |
### 4.3 本地化
| 文件路径 | 新增 Key |
|---|---|
| `ThingsGateway.Gateway.Application/Locales/zh-CN.json` | `RangeFilterEnable`(合理范围过滤使能)、`RangeFilterMinValue`(范围最小值)、`RangeFilterMaxValue`(范围最大值)、`AmplitudeFilterEnable`(变化幅度过滤使能)、`MaxAmplitude`(最大变化幅度)、`MaxAmplitudePercent`(最大变化幅度%)、`ContinuousFilterEnable`(连续异常过滤使能)、`MaxContinuousCount`(连续异常最大次数) |
| `ThingsGateway.Gateway.Application/Locales/en-US.json` | 同上(英文 Key) |
| `ThingsGateway.Gateway.Razor/Locales/zh-CN.json` | `OutlierFilterInformation`(异常值过滤 — Tab 标题) |
| `ThingsGateway.Gateway.Razor/Locales/en-US.json` | `OutlierFilterInformation`(OutlierFilter — Tab title) |
---
## 5. 数据库变更
在 `variable` 表中新增以下 8 个列(SqlSugar ORM 自动迁移,`IsNullable = true`):
| 列名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `RangeFilterEnable` | `bit` | `false` | 合理范围过滤使能 |
| `RangeFilterMinValue` | `decimal` | `0` | 范围最小值 |
| `RangeFilterMaxValue` | `decimal` | `100` | 范围最大值 |
| `AmplitudeFilterEnable` | `bit` | `false` | 变化幅度过滤使能 |
| `MaxAmplitude` | `decimal` | `100` | 最大变化幅度(绝对值),> 0 时生效 |
| `MaxAmplitudePercent` | `decimal` | `100` | 最大变化幅度(百分比,%),> 0 时生效 |
| `ContinuousFilterEnable` | `bit` | `false` | 连续异常过滤使能 |
| `MaxContinuousCount` | `int` | `3` | 连续异常最大次数 |
---
## 6. UI 界面布局
变量编辑弹窗包含三个 Tab 页:
```
┌──────────────┬──────────────┬──────────────┬──────────────┐
│ 变量信息 │ 报警信息 │ 异常值过滤 │ 插件信息 │
└──────────────┴──────────────┴──────────────┴──────────────┘
```
**异常值过滤** Tab 页采用 3 列布局(`ItemsPerRow=3`),分三组展示:
```
┌─────────────────────────────────────────────────────────────┐
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 合理范围过滤使能 │ │ 范围最小值 │ │ 范围最大值 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│─────────────────────────────────────────────────────────────│
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 变化幅度过滤使能 │ │ 最大变化幅度 │ │ 最大变化幅度% │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│─────────────────────────────────────────────────────────────│
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 连续异常过滤使能 │ │ 连续异常最大次数 │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
---
## 7. 日志输出
过滤过程中的关键事件会通过 `LogMessage` 输出日志:
| 日志级别 | 事件 | 示例 |
|---|---|---|
| `Trace` | 范围过滤触发 | `变量A Outlier filter: value 250 out of range [0, 200]` |
| `Trace` | 绝对幅度过滤触发 | `变量A Outlier filter: amplitude 150 exceeds max 100` |
| `Trace` | 百分比幅度过滤触发 | `变量A Outlier filter: amplitude 85.50% exceeds max 50%` |
| `Information` | 连续异常达上限,接受值 | `变量A Outlier filter: continuous anomaly count reached 3, accepting value 250` |
| `Warning` | 值被拒绝(由 `SetValue` 输出) | `变量A Outlier filter rejected value` |
---
## 8. 注意事项
1. **非数值类型不过滤**:如果变量值无法转换为 `decimal`(如字符串、布尔值),则跳过所有过滤,直接接受该值。
2. **首次赋值不过滤幅度**:变量的 `Value` 为 `null` 时(首次采集),幅度过滤不会执行(无旧值可比较),但范围过滤仍然生效。
3. **设备离线时清零计数器**:设备离线时,`SetValue` 会清零连续异常计数器,确保重新上线后不会携带历史状态。
4. **三种过滤独立启用**:每种过滤模式可独立开启或关闭,互不影响。
5. **默认全部关闭**:所有过滤使能默认为 `false`,不影响现有变量的行为。
6. **幅度阈值为 0 时跳过**:`MaxAmplitude` 或 `MaxAmplitudePercent` 设为 `0` 时,对应的检查项将被跳过,不会触发拦截。可以利用此特性只启用其中一种幅度检查方式。
@@ -240,6 +240,65 @@ public class Variable : PrimaryKeyEntity, IValidatableObject
[AutoGenerateColumn(Ignore = true)]
public AlarmPropertys? AlarmPropertys { get; set; }
#region 异常值过滤
/// <summary>
/// 合理范围过滤使能
/// </summary>
[OrmColumn(ColumnDescription = "合理范围过滤使能", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual bool RangeFilterEnable { get; set; }
/// <summary>
/// 最小值
/// </summary>
[OrmColumn(ColumnDescription = "最小值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual decimal RangeFilterMinValue { get; set; }
/// <summary>
/// 最大值
/// </summary>
[OrmColumn(ColumnDescription = "最大值", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual decimal RangeFilterMaxValue { get; set; } = 100;
/// <summary>
/// 变化幅度过滤使能
/// </summary>
[OrmColumn(ColumnDescription = "变化幅度过滤使能", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual bool AmplitudeFilterEnable { get; set; }
/// <summary>
/// 最大变化幅度(与上次值的绝对差值上限)
/// </summary>
[OrmColumn(ColumnDescription = "最大变化幅度", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual decimal MaxAmplitude { get; set; } = 100;
/// <summary>
/// 最大变化幅度百分比(与上次值的变化比例上限,单位%)
/// </summary>
[OrmColumn(ColumnDescription = "最大变化幅度%", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual decimal MaxAmplitudePercent { get; set; } = 100;
/// <summary>
/// 连续异常过滤使能
/// </summary>
[OrmColumn(ColumnDescription = "连续异常过滤使能", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual bool ContinuousFilterEnable { get; set; }
/// <summary>
/// 连续异常最大次数,超过此次数后接受该值
/// </summary>
[OrmColumn(ColumnDescription = "连续异常最大次数", IsNullable = true)]
[AutoGenerateColumn(Visible = false, Filterable = true, Sortable = true)]
public virtual int MaxContinuousCount { get; set; } = 3;
#endregion 异常值过滤
#region 备用字段
@@ -571,7 +571,15 @@
"SaveVariable": "Add/Modify Variable",
"Unit": "Unit",
"VariableNotNull": "Variable name does not exist",
"WriteExpressions": "WriteExpressions"
"WriteExpressions": "WriteExpressions",
"RangeFilterEnable": "RangeFilterEnable",
"RangeFilterMinValue": "RangeFilterMinValue",
"RangeFilterMaxValue": "RangeFilterMaxValue",
"AmplitudeFilterEnable": "AmplitudeFilterEnable",
"MaxAmplitude": "MaxAmplitude",
"MaxAmplitudePercent": "MaxAmplitude%",
"ContinuousFilterEnable": "ContinuousFilterEnable",
"MaxContinuousCount": "MaxContinuousCount"
},
"ThingsGateway.Gateway.Application.AlarmPropertys": {
@@ -597,7 +597,15 @@
"SaveVariable": "添加/修改变量",
"Unit": "单位",
"VariableNotNull": "变量名称不存在",
"WriteExpressions": "写入表达式"
"WriteExpressions": "写入表达式",
"RangeFilterEnable": "合理范围过滤使能",
"RangeFilterMinValue": "范围最小值",
"RangeFilterMaxValue": "范围最大值",
"AmplitudeFilterEnable": "变化幅度过滤使能",
"MaxAmplitude": "最大变化幅度",
"MaxAmplitudePercent": "最大变化幅度%",
"ContinuousFilterEnable": "连续异常过滤使能",
"MaxContinuousCount": "连续异常最大次数"
},
"ThingsGateway.Gateway.Application.AlarmPropertys": {
@@ -269,6 +269,11 @@ public partial class VariableRuntime : Variable
#pragma warning disable CS0649
protected string _lastErrorMessage;
/// <summary>
/// 连续异常计数器
/// </summary>
private int _continuousAnomalyCount;
/// <summary>
/// 设置变量值与时间/质量戳
@@ -279,6 +284,7 @@ public partial class VariableRuntime : Variable
RawValue = value;
if (IsOnline == false)
{
_continuousAnomalyCount = 0;
Set(value, dateTime, setChanged);
return new();
}
@@ -287,7 +293,17 @@ public partial class VariableRuntime : Variable
try
{
var data = ReadExpressions.GetExpressionsResult(RawValue, LogMessage);
Set(data, dateTime, setChanged);
if (!Set(data, dateTime, setChanged))
{
var oldMessage = _lastErrorMessage;
_lastErrorMessage = $"{Name} Outlier filter rejected value";
if (oldMessage != _lastErrorMessage)
{
LogMessage?.LogWarning(_lastErrorMessage);
}
return new($"{Name} Outlier filter rejected value");
}
}
catch (Exception ex)
{
@@ -312,11 +328,108 @@ public partial class VariableRuntime : Variable
}
else
{
Set(value, dateTime, setChanged);
if (!Set(value, dateTime, setChanged))
{
return new($"{Name} Outlier filter rejected value");
}
}
return new();
}
/// <summary>
/// 应用异常值过滤,返回true表示值通过过滤,返回false表示值被拒绝
/// </summary>
private bool ApplyOutlierFilter(object data)
{
bool anyEnabled = RangeFilterEnable || AmplitudeFilterEnable || ContinuousFilterEnable;
if (!anyEnabled || data == null)
{
_continuousAnomalyCount = 0;
return true;
}
if (!TryConvertToDecimal(data, out var numericValue))
{
_continuousAnomalyCount = 0;
return true;
}
bool isAnomaly = false;
// 合理范围过滤
if (RangeFilterEnable)
{
if (numericValue < RangeFilterMinValue || numericValue > RangeFilterMaxValue)
{
isAnomaly = true;
LogMessage?.LogTrace($"{Name} Outlier filter: value {numericValue} out of range [{RangeFilterMinValue}, {RangeFilterMaxValue}]");
}
}
// 变化幅度过滤
if (!isAnomaly && AmplitudeFilterEnable && Value != null)
{
if (TryConvertToDecimal(Value, out var lastNumeric))
{
var absoluteAmplitude = Math.Abs(numericValue - lastNumeric);
if (MaxAmplitude > 0 && absoluteAmplitude > MaxAmplitude)
{
isAnomaly = true;
LogMessage?.LogTrace($"{Name} Outlier filter: amplitude {absoluteAmplitude} exceeds max {MaxAmplitude}");
}
if (!isAnomaly && lastNumeric != 0 && MaxAmplitudePercent > 0)
{
var amplitudePercent = absoluteAmplitude / Math.Abs(lastNumeric) * 100;
if (amplitudePercent > MaxAmplitudePercent)
{
isAnomaly = true;
LogMessage?.LogTrace($"{Name} Outlier filter: amplitude {amplitudePercent:F2}% exceeds max {MaxAmplitudePercent}%");
}
}
}
}
if (!isAnomaly)
{
_continuousAnomalyCount = 0;
return true;
}
// 连续异常过滤
if (ContinuousFilterEnable)
{
_continuousAnomalyCount++;
if (_continuousAnomalyCount >= MaxContinuousCount)
{
// 连续异常次数已达上限,接受该值
_continuousAnomalyCount = 0;
LogMessage?.LogInformation($"{Name} Outlier filter: continuous anomaly count reached {MaxContinuousCount}, accepting value {data.ToSystemTextJsonString()}");
return true;
}
}
return false;
}
/// <summary>
/// 尝试将对象转换为decimal
/// </summary>
private static bool TryConvertToDecimal(object value, out decimal result)
{
result = 0;
if (value == null) return false;
try
{
result = Convert.ToDecimal(value);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 设置变量值与时间/质量戳
/// </summary>
@@ -335,7 +448,7 @@ public partial class VariableRuntime : Variable
}
}
protected void Set(object data, DateTime dateTime, bool setChanged)
protected bool Set(object data, DateTime dateTime, bool setChanged)
{
DateTime time = dateTime != default ? dateTime : DateTime.Now;
CollectTime = time;
@@ -378,6 +491,14 @@ public partial class VariableRuntime : Variable
changed = false;
}
}
// 异常值过滤:仅在值变化且在线时检查
if ((changed || setChanged) && _isOnline && !ApplyOutlierFilter(data))
{
GlobalData.VariableCollectChange(this);
return false;
}
if (changed || _isOnlineChanged == true || setChanged)
{
ChangeTime = time;
@@ -394,6 +515,7 @@ public partial class VariableRuntime : Variable
GlobalData.VariableValueChange(this);
}
GlobalData.VariableCollectChange(this);
return true;
}
public void Init(DeviceRuntime deviceRuntime)
@@ -165,7 +165,11 @@
"Unknown": "Unknown",
"UpdateChannel": "Edit Channel",
"UpdateDevice": "Edit Device",
"VariableInformation": "Variable Information"
"VariableInformation": "Variable Information",
"OutlierFilterInformation": "Outlier Filter",
"RangeFilter": "Range Filter",
"AmplitudeFilter": "Amplitude Filter",
"ContinuousFilter": "Continuous Anomaly Filter"
},
"ThingsGateway.Gateway.Razor.BackendLogPage": {
"BackendLog": "Gateway Backend Log",
@@ -164,7 +164,11 @@
"Unknown": "未知",
"UpdateChannel": "编辑通道",
"UpdateDevice": "编辑设备",
"VariableInformation": "变量信息"
"VariableInformation": "变量信息",
"OutlierFilterInformation": "异常值过滤",
"RangeFilter": "合理范围过滤",
"AmplitudeFilter": "变化幅度过滤",
"ContinuousFilter": "连续异常过滤"
},
"ThingsGateway.Gateway.Razor.BackendLogPage": {
"BackendLog": "网关后台日志",
@@ -1,4 +1,4 @@
@namespace ThingsGateway.Gateway.Razor
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Foundation
@@ -191,6 +191,43 @@
</ValidateForm>
</TabItem>
<TabItem Text=@GatewayLocalizer["OutlierFilterInformation"]>
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=3 LabelWidth=250 Model="Model">
<FieldItems>
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Name">
<EditTemplate Context="value">
<div class="col-12">
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.RangeFilterEnable" />
<EditorItem @bind-Field="@context.RangeFilterMinValue" />
<EditorItem @bind-Field="@context.RangeFilterMaxValue" />
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Name">
<EditTemplate Context="value">
<div class="col-12">
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.AmplitudeFilterEnable" />
<EditorItem @bind-Field="@context.MaxAmplitude" />
<EditorItem @bind-Field="@context.MaxAmplitudePercent" />
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Name">
<EditTemplate Context="value">
<div class="col-12">
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.ContinuousFilterEnable" />
<EditorItem @bind-Field="@context.MaxContinuousCount" />
</FieldItems>
</EditorForm>
</TabItem>
@if (!BatchEditEnable)
{
<TabItem Text=@GatewayLocalizer["PluginInformation"]>
@@ -571,7 +571,15 @@
"SaveVariable": "Add/Modify Variable",
"Unit": "Unit",
"VariableNotNull": "Variable name does not exist",
"WriteExpressions": "WriteExpressions"
"WriteExpressions": "WriteExpressions",
"RangeFilterEnable": "RangeFilterEnable",
"RangeFilterMinValue": "RangeFilterMinValue",
"RangeFilterMaxValue": "RangeFilterMaxValue",
"AmplitudeFilterEnable": "AmplitudeFilterEnable",
"MaxAmplitude": "MaxAmplitude",
"MaxAmplitudePercent": "MaxAmplitude%",
"ContinuousFilterEnable": "ContinuousFilterEnable",
"MaxContinuousCount": "MaxContinuousCount"
},
"ThingsGateway.Gateway.Application.AlarmPropertys": {
@@ -597,7 +597,15 @@
"SaveVariable": "添加/修改变量",
"Unit": "单位",
"VariableNotNull": "变量名称不存在",
"WriteExpressions": "写入表达式"
"WriteExpressions": "写入表达式",
"RangeFilterEnable": "合理范围过滤使能",
"RangeFilterMinValue": "范围最小值",
"RangeFilterMaxValue": "范围最大值",
"AmplitudeFilterEnable": "变化幅度过滤使能",
"MaxAmplitude": "最大变化幅度",
"MaxAmplitudePercent": "最大变化幅度%",
"ContinuousFilterEnable": "连续异常过滤使能",
"MaxContinuousCount": "连续异常最大次数"
},
"ThingsGateway.Gateway.Application.AlarmPropertys": {
@@ -165,7 +165,11 @@
"Unknown": "Unknown",
"UpdateChannel": "Edit Channel",
"UpdateDevice": "Edit Device",
"VariableInformation": "Variable Information"
"VariableInformation": "Variable Information",
"OutlierFilterInformation": "Outlier Filter",
"RangeFilter": "Range Filter",
"AmplitudeFilter": "Amplitude Filter",
"ContinuousFilter": "Continuous Anomaly Filter"
},
"ThingsGateway.Gateway.Razor.BackendLogPage": {
"BackendLog": "Gateway Backend Log",
@@ -164,7 +164,11 @@
"Unknown": "未知",
"UpdateChannel": "编辑通道",
"UpdateDevice": "编辑设备",
"VariableInformation": "变量信息"
"VariableInformation": "变量信息",
"OutlierFilterInformation": "异常值过滤",
"RangeFilter": "合理范围过滤",
"AmplitudeFilter": "变化幅度过滤",
"ContinuousFilter": "连续异常过滤"
},
"ThingsGateway.Gateway.Razor.BackendLogPage": {
"BackendLog": "网关后台日志",
@@ -1,4 +1,4 @@
@namespace ThingsGateway.Gateway.Razor
@namespace ThingsGateway.Gateway.Razor
@using ThingsGateway.Admin.Application
@using ThingsGateway.Admin.Razor
@using ThingsGateway.Foundation
@@ -191,6 +191,43 @@
</ValidateForm>
</TabItem>
<TabItem Text=@GatewayLocalizer["OutlierFilterInformation"]>
<EditorForm class="p-2" AutoGenerateAllItem="false" RowType=RowType.Inline ItemsPerRow=3 LabelWidth=250 Model="Model">
<FieldItems>
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Name">
<EditTemplate Context="value">
<div class="col-12">
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.RangeFilterEnable" />
<EditorItem @bind-Field="@context.RangeFilterMinValue" />
<EditorItem @bind-Field="@context.RangeFilterMaxValue" />
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Name">
<EditTemplate Context="value">
<div class="col-12">
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.AmplitudeFilterEnable" />
<EditorItem @bind-Field="@context.MaxAmplitude" />
<EditorItem @bind-Field="@context.MaxAmplitudePercent" />
<EditorItem TValue="string" TModel="Variable" @bind-Field="@context.Name">
<EditTemplate Context="value">
<div class="col-12">
</div>
</EditTemplate>
</EditorItem>
<EditorItem @bind-Field="@context.ContinuousFilterEnable" />
<EditorItem @bind-Field="@context.MaxContinuousCount" />
</FieldItems>
</EditorForm>
</TabItem>
@if (!BatchEditEnable)
{
<TabItem Text=@GatewayLocalizer["PluginInformation"]>