智慧水务系统中,水质监测传感器通过NB-IoT网络将pH、浊度、溶解氧等指标回传至平台。R语言凭借其丰富的时序分析与可视化包,成为中小型水务项目后端分析的实用选择。本文围绕传感器数据从NB-IoT到达后的解析、清洗与建模展开,给出一套可直接落地的处理思路。

NB-IoT数据帧结构与R语言解析方法
水质监测终端通常采用CoAP或MQTT over NB-IoT上报数据, payload可能是二进制紧凑格式,也可能是带设备ID的JSON。二进制帧一般包含起始符、传感器类型码、四字节浮点值和CRC8校验。若直接用字符函数读取,会因大小端差异得到错误数值,因此要在R中按字节拆包。
下面这段R代码模拟接收一个十六进制字符串并解析为温度与pH。我们使用readBin配合rawConnection处理原始字节,比正则提取更可靠。注意实际设备可能使用小端序,endian="little"不能省略。
# 模拟NB-IoT设备上行十六进制帧: 头部AA 传感器码01 温度(float) pH(float) CRC
raw_hex <- "AA0100004840000000421D"
raw_bytes <- as.raw(strtoi(substring(raw_hex, seq(1, nchar(raw_hex), 2),
seq(2, nchar(raw_hex), 2)), 16))
con <- rawConnection(raw_bytes, open="rb")
header <- readBin(con, "integer", size=1, signed=FALSE)
sensor_type <- readBin(con, "integer", size=1, signed=FALSE)
temperature <- readBin(con, "double", size=4, endian="little")
ph_value <- readBin(con, "double", size=4, endian="little")
close(con)
cat(sprintf("温度: %.2f, pH: %.2fn", temperature, ph_value))
当平台采用JSON上报时,R的jsonlite包可直接将报文转为数据框。但现场常遇见字段缺失,例如某批次固件不上传电导率,此时用fromJSON后需用coalesce补默认值,否则后续merge会因长度不等报错。建议在解析层统一输出宽表,列名遵循device_id, ts, ph, turb, do规范。
数据清洗与异常检测的R实现
NB-IoT链路在地下井盖或桥梁阴影区易出现丢包与重传,造成时间戳乱序和数值跳变。若把突变当作真实污染,会误触发告警。我们可用zoo包的滑动中位数过滤毛刺,再用相邻差值法标记离群点。
以下脚本读取设备日数据,对浊度做窗口为5的中值滤波,并标出超过三倍标准差的样本。实践中发现,雨季井盖震动会让浊度瞬时冲高,仅用均值滤波会平滑掉真实泥石流信号,所以中位数的窗口不宜过大,一般3到7即可。
library(zoo)
library(dplyr)
# 模拟某传感器时序
df <- data.frame(
ts = seq.POSIXt(as.POSIXct("2023-01-01 00:00"), by="15 min", length.out=100),
turb = c(rnorm(90, 5, 0.5), rnorm(10, 50, 2))
)
df %>%
mutate(turb_med = rollmedian(turb, k=5, fill=NA)) %>%
mutate(sd = sd(turb, na.rm=TRUE)) %>%
mutate(is_outlier = abs(turb - turb_med) > 3 * sd) -> df_clean
print(table(df_clean$is_outlier))
除了数值滤波,还需处理时区问题。NB-IoT模块若无GPS,时间来自基站,可能比标准UTC快八小时。R里务必用with_tz显式转换,并在写入InfluxDB前统一为UTC,避免 Grafana 跨设备对比时曲线错位。清洗后的数据建议按天分片存储,方便R用read_csv惰性加载,降低内存压力。
基于R的时序可视化与简易告警模型
完成解析与清洗后,可用ggplot2绘制多参数趋势图,辅助巡检人员判断污染来源。比如pH持续下降伴随溶解氧走低,多为有机废水排入;而浊度单指标尖峰通常是泥沙扰动。图形比原始表更直观,也便于上报市政平台。
下面代码将清洗结果画成双轴图,左轴浊度、右轴pH,并标红异常段。在真实项目中,我们把该图渲染为PNG定时邮件发送。要注意scale_x_datetime的时区必须和数据一致,否则午夜峰值会偏移到白天。
library(ggplot2)
ggplot(df_clean, aes(x=ts)) +
geom_line(aes(y=turb, color="浊度")) +
geom_line(aes(y=ph * 10, color="pHx10")) +
geom_point(data=subset(df_clean, is_outlier), aes(y=turb), color="red", size=2) +
scale_color_manual(values=c("浊度"="blue", "pHx10"="green")) +
labs(title="水质监测时序", x="时间", y="数值") +
theme_minimal()
告警方面,不必一开始就上机器学习。用R写一条规则引擎:当连续三点pH低于6.5且浊度高于20 NTU,就通过mailR发信。若后续节点超五百,再把历史数据喂给forecast包做ARIMA预测,提前两小时预估溢流风险。这种从规则到模型的渐进路径,在县城水司的预算内完全可行,也利用了R在统计上的成熟生态。