模型ID: 149
模型中文名: 机组偏航工作异常
模型英文名:Model_149_Alarm_GenYawWorkAbnormal
当前版本: Model_149_AlarmRT0_GenAccNoiseInYaw_V6.3.100.0_IPHM
模板版本:R_WT_v6.2.0_20220301
首发日期:2024-11-28
最近一次更新:2024-12-04
设备类型:风机
设备类型配置:GW1500,GW2000,GW2500,GW3000,GW3S,GW4S,GW4H,GW4000,GW5H,GW6H等金风系列兆瓦机型
模型测点:
应用项目:IPHM
版本控制:
{
"business_type": "风机数据预警",
"platform": "GW",
"corn": "0 0 1 1/1 * ? *",
"status": "启用",
"priority": "D2",
"is_SDK": "否",
"SDK_version": "",
"aircraft_model": "GW1500,GW1650,GW2000,GW2300,GW2500,GW3000,GW3S,GW4H,GWH,GW5H,GW6H,GW4000,GW4200,GW4500,GW5000,GW6000",
"model_type": "单个",
"deve_temp": "",
"release_time": "2024-12-03",
"code_lan": "R",
"run_info": "风机",
"scenario_type": "中心端",
"model_id": "149",
"model_version": "V6.3.300.0.200.0",
"encrypted_code_files": "AlarmResult.R,Config.R,DataReader.R,DataReaderAWS.R,DataService.R,DataWrite.R,ENumType.R,GetUserPara.R,Model.R,ModelUtil.R,SystemConfig.R,Util.R",
"not_encryted_file": "code_config.yaml,Main.R,ModelConfig.yaml,Readme.html"
}
主要监测以下几个方面的常见问题:
1.偏航过程中振动大;
2.偏航过程中机头异常摆动、刹车不稳、机组晃动大、偏航异响等;
3.偏航闸片质量问题或发电机轴承间隙大等问题在机组偏航过程中表现出偏航晃动大;
机组偏航振动大的原因较多,有设计问题、制造问题、机组运行问题、部件安装工艺问题等,比如叶片弯曲角度过大过小及叶片质量或载荷不均匀等导致的偏航振动、塔架刚度不足引起的偏航振动、偏航刹车失效、偏航断齿、偏航轴承齿间隙大等问题都可能引起偏航振动大或偏航异响。
本次模型和早期的版本的差异:
1)测点完全不同,本次模型主要采用了更多的状态量和布尔量,详细测点参考本模型的测点和老模型的测试报告。
2)算法不同,老算法用振动在特定转速段的偏航速度不为0的情况下的在不同转速下的振动比较和振动分布面积大小进行比较。如果偏航的数据数量较少,则波动较大,算法不够准确。
3)老算法由于长久无人优化,已经2021年3月SPHM的反馈准确率为42.55%而下线,同年6月朱诗佳优化后测试准确率为18.46%未上线,2020年江容专门针对海装 H93_2000机组开发,适用于海装 H93_2000机组,适配的是辽宁大唐项目的H93_2000机组,测试报告中盲测无告警记录,无准确率的记录《模型测试报告-H93_2000机组偏航振动异常预警模型测试报告.docx》。
模型的算法说明:
1.偏航频繁异常:理论依据:工作条件下,时间频次出现异常,主要用于监测偏航系统的对风问题,预防偏航不停的在偏航,偏航找不到风向位置,为了找风而频繁动作,可能是风向标问题或偏航控制问题。偏航频繁逻辑参考:偏航动作信号变换低于5分钟动作一次的情况;
2..偏航状态下机组振动异常:偏航异响、磨损、偏航时振动大,偏航状态条件下,机组的振动加速度超限或濒临超限;
3.偏航时间过长:连续偏航超过2个小时的情况,可以用偏航电机工作时间来确定;连续2个小时内超过1.5小时都在偏航的情况;
4.机组长时不偏航:偏航信号有变化或偏航启动信号使能信号有触发,但偏航位置没有变化的情况或信号时间长度和偏航电机工作时间异常。出现的异常,偏航电机断齿或故障,监控无法识别或无法判别这个问题。这个在华电出现过案例(这个问题已兼容到227模型内,这里不再重复兼容);
2024-10-25:
2024-11-12:2024-11-14
偏航过程中振动大故障模型测试报警示例图:
pjmResult$ScadaWTID = '620915082'
pjmResult$trigger_time = '2023-06-22 23:03:24'
2024-12-02:
library(magrittr)
library(yaml)
# 获取配置文件内容
config <- read_yaml("ModelConfig.yaml")
config$get_excel_ieccn$model_iec =
c('xacce','yacce','accavg','rpm','windspeed','yawspeed','yawposi','untwist','lyawflag','yyawflag','rectime')
config$get_excel_ieccn$possible_list =
c('WTUR_Acce_Ra_F32_x,WNAC_Acce_Ra_F32_Itilt,WTUR_Acce_Ra_F32_X,WTRM_Acce_Ra_F32_x,WTOW_Acce_Ra_F32_x,WTUR_Other_Rw_F32_ZD,WNAC_Other_Ra_F32_x,WNAC.Other.Ra.F32.VibrationX' ,
'WTUR_Acce_Ra_F32_y,WNAC_Acce_Ra_F32_Iroll,WTUR_Acce_Ra_F32_Y,WTRM_Acce_Ra_F32_y,WTOW_Acce_Ra_F32_y,WTUR_Other_Rw_F32_NOZD,WNAC_Other_Ra_F32_y,WNAC.Other.Ra.F32.VibrationY',
'WTUR_Acce_Ra_F32',
'WGEN_Spd_Ra_F32',
'WTUR_WSpd_Ra_F32',
'WYAW_Spd_Ra_F32_Yaw',
'WYAW_Posi_Ra_F32',
'WYAW_Bool_Rd_b0_UnTwist',
'WYAW_Bool_Rd_b0_Lyaw',
'WYAW_Bool_Rd_b0_Ryaw',
'rectime,WMAN.TM,rectime,WTUR_Tm_Rw_Dt'
)
config$get_excel_ieccn$cn_name =
c('x方向加速度','y方向加速度','加速度有效值','发电机转速','风速','偏航速度','偏航位置','解缆标志位','左偏航标志位','右偏航标志位','时间')
# 获取模型配置的测点参数
get_excel_ieccn = config$get_excel_ieccn %>% data.frame()
# 在已有的yaml文件中追加写yaml文件
yaml_obj = NULL
# 加载已存在的YAML文件
yaml_obj$get_excel_ieccn = get_excel_ieccn
# 在加载的YAML对象上进行修改
yaml_obj$intervalHours <- 24
yaml_obj$projectName = '149'
yaml_obj$projectType = 'alarm'
yaml_obj$logFileName = 'a.txt' # initial log file,required
yaml_obj$drawPicIf = F
yaml_obj$user_config_enable = F # 是否启用用户参数配置:默认没有该配置,除非特定项目要求
yaml_obj$IS_TRANSLATE = F # 是否启用国际化翻译:默认没有该配置,除非特定项目要求
# 再次写入文件
write_yaml(yaml_obj, "model_config.yaml")
library(compiler)
options(encoding = 'UTF-8')
file_proj = basename(getwd())
if(!exists(file_proj)) {dir.create(file_proj)}
cmpfile(infile = 'Config.R' ,outfile = file.path('.',file_proj,'Config.Rc'))
cmpfile(infile = 'DataService.R' ,outfile = file.path('.',file_proj,'DataService.Rc'))
cmpfile(infile = 'DataReader.R' ,outfile = file.path('.',file_proj,'DataReader.Rc'))
cmpfile(infile = 'ENumType.R' ,outfile = file.path('.',file_proj,'ENumType.Rc'))
cmpfile(infile = 'Util.R' ,outfile = file.path('.',file_proj,'Util.Rc'))
cmpfile(infile = 'AlarmResult.R' ,outfile = file.path('.',file_proj,'AlarmResult.Rc'))
cmpfile(infile = 'DataWrite.R' ,outfile = file.path('.',file_proj,'DataWrite.Rc'))
cmpfile(infile = 'Model.R' ,outfile = file.path('.',file_proj,'Model.Rc'))
cmpfile(infile = 'ModelUtil.R' ,outfile = file.path('.',file_proj,'ModelUtil.Rc') )
cmpfile(infile = 'DataReaderAWS.R' ,outfile = file.path('.',file_proj,'DataReaderAWS.Rc'))
cmpfile(infile = 'ModelUtil.R' ,outfile = file.path('.',file_proj,'ModelUtil.Rc') )
cmpfile(infile = 'SystemConfig.R' ,outfile = file.path('.',file_proj,'SystemConfig.Rc'))
cmpfile(infile = 'GetUserPara.R' ,outfile = file.path('.',file_proj,'GetUserPara.Rc'))
# 将其他文件不需要加密的文件copy到工程文件夹中,然后打压缩包
file.copy(from = 'Main.R',to = file.path('.',file_proj,'Main.R'),overwrite = T)
file.copy(from = 'MainLocal.R',to = file.path('.',file_proj,'MainLocal.R'),overwrite = T)
file.copy(from = 'ModelConfig.yaml',to = file.path('.',file_proj,'ModelConfig.yaml'),overwrite = T)
file.copy(from = list.files(pattern = '.Rproj')[1],to = file.path('.',file_proj,list.files(pattern = '.Rproj')[1]),overwrite = T)
file.copy(from = 'code_config.yaml',to = file.path('.',file_proj,'code_config.yaml'),overwrite = T)
# 将加密的文件批量上传更新到SVN发版位置
files = list.files(file.path(getwd(),file_proj),full.names = T)
target_dir = 'D:\\SVN\\WiTur_box\\异常告警\\发版版本\\MODEL_NEW'
target_dir2 = file.path(target_dir,file_proj)
if (!dir.exists(target_dir2)) {
dir.create(target_dir2, recursive = TRUE)
}
file.copy(files, target_dir2, overwrite = TRUE)
owd::owd(target_dir)
# 删除本地的文件夹及测试的日志文件,只保留加密的最新版本
unlink(file_proj, recursive = TRUE)
logfilename = list.files(pattern = '.log')
file.remove(logfilename) # 删除日志文件
library(magrittr)
library(yaml)
code_config = read_yaml('code_config.yaml')
encrypted_files = code_config$encrypted_code_files %>% strsplit(split = ',') %>% unlist()
not_encryted_files = code_config$not_encryted_file %>% strsplit(split = ',') %>% unlist()
library(compiler)
options(encoding = 'UTF-8')
file_proj = basename(getwd())
if(!exists(file_proj)) {dir.create(file_proj)}
# 将文件进行加密
purrr::map(seq_along(encrypted_files),function(x){
cmpfile(infile = encrypted_files[x] ,outfile = file.path('.',file_proj, paste0(encrypted_files[x],'c')))
})
# 将不加密的文件copy到目标文件路径
purrr::map(seq_along(not_encryted_files),function(y){
message('源文件:',file.path(getwd(),not_encryted_files[y]),',移动到:',file.path('.',file_proj))
file.copy(from = file.path(getwd(),not_encryted_files[y]), to = file.path('.',file_proj), overwrite = TRUE)
})
综合测试精确率:
精确率 = (TP+TN)/(TP+TN+FP+FN) = (122+1173+1125)/(1125+1173+553) = 84.88% ;
召回率 =TP/(TP+FN) = 122/(122+431) = 22.06%;
案例集样例示例
报警的案例机组信息及可视化基本信息
可视化文件解析与复现
library(svglite)
library(ggplot2)
library(scales) # 用于格式化日期时间
library(lubridate)
library(magrittr)
library(dplyr)
vis_path = "D:\\SVN\\WiTur_box\\异常告警\\tag源码\\Model_143_168_216_253_Alarm_SafetyVibrationRisk\\Model_149_AlarmRT0_GenAccNoiseInYaw_V6.3.100.0_IPHM\\可视化数据文件"
visfile_info1 = list.files(vis_path,pattern = '.vis',full.names = F,recursive = T)
visfile_info2 = purrr::map(seq_along(visfile_info1),function(x){
a = strsplit(basename(visfile_info1[x]),split = '_') ;
list(modelid = a[[1]][4],
wfid = a[[1]][2],
wtid = a[[1]][3],
date = substr(a[[1]][5],1,10),
path = visfile_info1[x])
}) %>% dplyr::bind_rows() # 可视化文件信息解析成数据框
all_farminf = yyg.template::get_all_wind_farm_info(); dim(all_farminf)
visfile_info2 = merge(all_farminf,visfile_info2,by.x = 'wfScadaId',by.y = 'wfid',all.y = T,all.x = F)
visfile_info2 |> count(modelid) |> arrange(desc(n))
# 已有图片监测
# pic_paths = readline()
# D:\SVN\WiTur_box\异常告警\准确率核对\哈密国华
#
# pic_files = list.files(pic_paths,pattern = '.png|.jpg|.jpeg',recursive = T)
#
# purrr::map(seq_along(pic_files),function(x){
# a = strsplit(basename(pic_files[x]),split = '_') ;
# list(modelid = a[[1]][4],
# wfid = a[[1]][2],
# wtid = a[[1]][3],
# date = substr(a[[1]][5],1,10),
# path = pic_files[x])
# }) %>% dplyr::bind_rows() # 可视化文件信息解析成数据框
model_id = '149'
model_vis = visfile_info2 %>% dplyr::filter(modelid == model_id)
model_vis$path %>% tail()
visfiles = file.path(vis_path,model_vis$path)
visfiles
# 解析可视化文件数据
for(filenum in 1:length(visfiles)) {
message( '解析可视化文件:',filenum)
vie_dat = jsonlite::fromJSON((visfiles[filenum]))
vie_dat$picture$`0`$picture$data |> names() # 可视化数据的名字
# 创建一个SVG文件
titlename = gsub('.vis','',basename(model_vis$path[filenum]))
if (length(vie_dat$picture$`0`$picture$data$rectime)>0){
lengths <- sapply(vie_dat$picture$`0`$picture$data, length)
# 先按最长的数据进行绘制图
data_all = vie_dat$picture$`0`$picture$data
data0 = data_all[which(lengths == length(vie_dat$picture$`0`$picture$data$rectime))] %>% as.data.frame()
rectime = as.POSIXct(data0$rectime / 1000, origin = "1970-01-01", tz = "UTC") %>% as.character()
break_hous = ifelse(length(rectime)*6/3600/7 <1,1,length(rectime)*6/3600/7)
# 获取第一个图的绘图参数
purrr::map(seq_along(names(vie_dat$picture$`0`$picture$figure1)[grep('plot',names(vie_dat$picture$`0`$picture$figure1))]),function(ii){
plot_ii = vie_dat$picture$`0`$picture$figure1[[names(vie_dat$picture$`0`$picture$figure1)[grep('plot',names(vie_dat$picture$`0`$picture$figure1))][ii]]]
dplyr::bind_rows(plot_ii)[1,]
}) %>% dplyr::bind_rows() -> plot_pardf
# 获取左侧坐标轴的参数
plot_pardf_left = plot_pardf %>% dplyr::filter(vertical_axis == 'left' & x == 'rectime')
plot_pardf_right = plot_pardf %>% dplyr::filter(vertical_axis == 'right' & x == 'rectime')
# 获取绘图的数据并根据标签对数据进行整理
data_0 = dplyr::select(data0,unique(plot_pardf_left$x),unique(plot_pardf_left$y))
names(data_0) = c(unique(plot_pardf_left$x),plot_pardf_left$label)
# 选择时间轴的xy数据
data_0$rectime = anytime::anytime(rectime) %>% as.POSIXct()
data_long <- data_0 %>% tidyr::pivot_longer(cols = -rectime, names_to = "variable", values_to = "value") ;head(data_long)
# 其他纵轴方面
data_other = data.frame(rectime = anytime::anytime(as.POSIXct(data_all$data1/ 1000, origin = "1970-01-01", tz = "UTC") %>% as.character()) %>% as.POSIXct(), 偏航状态 = data_all$data3 )
data_long_other <- data_other %>% tidyr::pivot_longer(cols = -rectime, names_to = "variable", values_to = "value") ;head(data_long_other)
data_long = bind_rows(data_long,data_long_other) %>% distinct()
# 生成本地的动态图======
if (F) {
library(ggplot2)
library(gganimate)
library(scales) # 用于日期格式
# 确保数据按时间排序
data_long <- data_long[order(data_long$rectime), ]
# 创建动态图
p <- ggplot(data_long, aes(x = rectime, y = value, color = variable)) +
geom_line() +
scale_x_datetime(labels = date_format("%Y-%m-%d %H:%M:%S"),
breaks = date_breaks(paste0(break_hous, " hours"))) +
scale_y_continuous(breaks = pretty_breaks(n = 8)) +
xlab(vie_dat$picture$`0`$picture$figure1$x_label) +
ylab(vie_dat$picture$`0`$picture$figure1$y_label1[1]) +
ggtitle(titlename) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
transition_reveal(rectime) # 添加动态效果
# 保存动图
anim <- animate(p, nframes = 100, fps = 10, duration = 3,width = 500, height = 360) # 你可以根据需要调整帧数和帧速率
anim_save(paste0(titlename, ".gif"), anim) # 保存为GIF文件
}
# 生成本地的音频+视频的动态图 ========
if (F){
# 加载必要的库
library(ggplot2)
library(gganimate)
library(scales)
library(av)
# 确保数据按时间排序
data_long <- data_long[order(data_long$rectime), ]
# 生成声音文件
# 假设 data_long_other 是一个包含声音数据的数据框
# 这里我们使用 value 的平方生成声音
time_long = 15
obj <- sonify(data_long_other$value^2, duration = time_long, play = FALSE)
# 保存声音文件
audio_file <- "sound.wav"
writeWave(obj, audio_file)
# 创建动态图
p <- ggplot(data_long, aes(x = rectime, y = value, color = variable)) +
geom_line() +
scale_x_datetime(labels = date_format("%Y-%m-%d %H:%M:%S"),
breaks = date_breaks(paste0(break_hous, " hours"))) +
scale_y_continuous(breaks = pretty_breaks(n = 8)) +
xlab(vie_dat$picture$`0`$picture$figure1$x_label) +
ylab(vie_dat$picture$`0`$picture$figure1$y_label1[1]) +
ggtitle(titlename) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
transition_reveal(rectime)
# 保存动态图,设置最后停留的时间为2秒
anim_file <- paste0(titlename, ".gif")
anim <- animate(p, nframes = 100, fps = 10, duration = time_long, width = 500, height = 360, end_pause = 20)
anim_save(anim_file, anim) # 保存为GIF文件
# 将GIF转换为MP4视频
video_file <- paste0(titlename,".mp4")
av::av_encode_video(anim_file, audio = audio_file, output = video_file)
# 清理临时文件(可选)
file.remove(anim_file) # 删除GIF文件
file.remove(audio_file) # 删除声音文件
}
# 生成本地的静态图 ======
png(paste0(titlename,".png"), width = 700, height = 500)
# 通过ggplot绘图
p <- ggplot(data_long, aes(x = rectime, y = value, color = variable)) +
geom_line() +
scale_x_datetime(labels = date_format("%Y-%m-%d %H:%M:%S"), breaks = date_breaks(paste0(break_hous ," hours"))) +
scale_y_continuous(breaks = pretty_breaks(n = 8)) + # 设置Y轴显示至少8个间隔标签
xlab(vie_dat$picture$`0`$picture$figure1$x_label) +
ylab(vie_dat$picture$`0`$picture$figure1$y_label1[1]) +
ggtitle(titlename) +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) # 旋转x轴标签为45度
print(p)
# 如果存在右侧坐标添加双坐标轴
if (nrow(plot_pardf_right)>0){
x_dat = data_all[which(names(data_all) == plot_pardf_right$x)]
y_dat = data_all[which(names(data_all) == plot_pardf_right$y)]
min_length <- min(c(length(unlist(x_dat)),length(unlist(y_dat))))
data0 <- lapply(data_all, function(x) {
if(length(x) > min_length) {
x <- x[1:min_length]
} else if(length(x) < min_length) {
x <- c(x, rep(NA, min_length - length(x)))
}
return(x)
}) %>% as.data.frame()
data_1 = dplyr::select(data0,unique(plot_pardf_right$x),unique(plot_pardf_right$y))
names(data_1) = c(unique(plot_pardf_right$x),plot_pardf_right$label)
data_1$rectime = as.POSIXct(data_1$rectime / 1000, origin = "1970-01-01", tz = "UTC") %>% as.character()%>% anytime::anytime() %>% as.POSIXct()
data_long_1 <- data_1 %>% tidyr::pivot_longer(cols = -rectime, names_to = "variable", values_to = "value2")
# 获取value的数值范围和value2的数值范围
value_range <- range(data_long$value)
value2_range <- range(data_long_1$value2)
p2 <- p + geom_line(data = data_long_1, aes(x = rectime, y = value2, color = variable)) +
scale_y_continuous(breaks = pretty_breaks(n = 8), limits = value_range) +
scale_y_continuous(sec.axis = sec_axis(~ .*mean(value2_range/value_range,na.rm = T) , name = plot_pardf_right$label,
breaks = unique(round(seq(value2_range[1],value2_range[2], length.out = 6)))))
print(p2)
}
dev.off()
}
}
owd::owd()
本地测试是引用本地的案例进行测试,也可以通过Model文件进行个例测试或通过Main文件进行测试。
if (file.exists('Util.R')){
source('Util.R',encoding = 'UTF-8')
} else {
compiler::loadcmp('Util.Rc')
}
# 故障测试 =========
load("偏航过程中加速度超限.RData") # 案例集
RfileChoose('AlarmResult')
RfileChoose('Model')
runmode = 0
aResultList = ''
pjmResult = PJMResult
# which(rpm_fault$风机编号 == '622127098')
rpm_fault = ff
purrr::map(1:nrow(rpm_fault),function(i){
message('第',i,'个案例测试...')
result <- try({
pjmResult$ScadaWTID = rpm_fault$风机编号[i]
pjmResult$ScadaWFID = rpm_fault$风场编号[i]
pjmResult$trigger_time = as.POSIXct(rpm_fault$故障截至时间[i]) + lubridate::duration(hour = 24)
pjmResult$RunDate = gsub('-|:| ','',pjmResult$trigger_time) %>% substring(first = 1,last = 8)
aResultList = runModel(pjmResult = pjmResult,aResultList = aResultList)
aResultList_DF = aResultList %>% bind_rows()
write.table(aResultList_DF,file = '加速度超限7.xls',append = T,row.names = F,sep = ';',fileEncoding = 'GBK')
print(aResultList_DF)
# # 写数据库,模型结果写到数据库卡夫卡
# RfileChoose('DataWrite')
# Warning_ResultInfo1 = aResultList
# lapply(1:length(Warning_ResultInfo1), function(x){Warning_ResultInfo1[[x]]$jobRecordID <<- pjmResult$jobRecordID})
# Warning_ResultInfo1[[1]]$jobRecordID <- pjmResult$jobRecordID
# Warning_ComentList = lapply(1:length(Warning_ResultInfo1), function(x){paste0('Model',x,':',Warning_ResultInfo1[[x]]$Comment)}) %>% unlist %>% paste(collapse = '。')
# Warning_ErrorMessages = lapply(1:length(Warning_ResultInfo1), function(x){paste0(Warning_ResultInfo1[[x]]$ErrorMessage)}) %>% unlist %>% paste(collapse = ' ')
# Warning_StateList = lapply(1:length(Warning_ResultInfo1), function(x){paste0('Model',x,':',Warning_ResultInfo1[[x]]$ModelStatus)}) %>% unlist %>% paste(collapse = ' ')
# pjmResult$WTStatus = ifelse(length(grep('error|Error',c(Warning_ErrorMessages))) == 0,PJMStatus$PROJECT_STATUS_STATE_FINISHED,PJMStatus$PROJECT_STATUS_STATE_ERROR)
# # 将结果推送到库里
# todymodbST = writeBatchEarlyWarningResult(Warning_ResultInfo1) %>% unlist()
},silent = T)
if (inherits(result, "try-error")) {
cat(i,"获取数据时发生了一个错误",'跳过错误继续运行...')
}
})