rain_monitor/modbus/modbus.go
fengyarnom 94308d81a0 init
2025-06-27 18:09:37 +08:00

314 lines
8.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package modbus
import (
"encoding/hex"
"fmt"
"log"
"net"
"rain_monitor/db"
"rain_monitor/models"
"sync"
"time"
)
const (
DeviceWeatherStation = 1 // 气象站
DeviceRainGauge = 2 // 雨量计
)
const (
WeatherStationAddr = 0x01
RainGaugeAddr = 0x02
)
const (
FuncReadHoldingRegisters = 0x03
)
var (
WeatherStationCmd = []byte{0x01, 0x03, 0x01, 0xf4, 0x00, 0x10, 0x04, 0x08} // 气象站查询命令
RainGaugeCmd = []byte{0x02, 0x03, 0x00, 0x00, 0x00, 0x0a, 0xc5, 0xfe} // 雨量计查询命令
)
var (
connectedClients map[string]net.Conn
clientsMutex sync.RWMutex
latestWeatherData *models.WeatherData
latestRainData *models.RainGaugeData
dataMutex sync.RWMutex
)
func init() {
connectedClients = make(map[string]net.Conn)
}
// StartTCPServer 启动TCP服务器
func StartTCPServer() {
listener, err := net.Listen("tcp", ":10004")
if err != nil {
log.Fatalf("无法启动TCP服务器: %v", err)
}
defer listener.Close()
log.Println("TCP服务器已启动监听端口 10004")
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("接受连接失败: %v", err)
continue
}
clientAddr := conn.RemoteAddr().String()
log.Printf("新客户端连接: %s", clientAddr)
clientsMutex.Lock()
connectedClients[clientAddr] = conn
clientsMutex.Unlock()
go handleConnection(conn)
}
}
// HandleConnection 处理客户端连接
func handleConnection(conn net.Conn) {
defer func() {
conn.Close()
clientAddr := conn.RemoteAddr().String()
clientsMutex.Lock()
delete(connectedClients, clientAddr)
clientsMutex.Unlock()
log.Printf("客户端断开连接: %s", clientAddr)
}()
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
log.Printf("读取数据失败: %v", err)
break
}
if n > 0 {
data := buffer[:n]
log.Printf("收到数据: %s", hex.EncodeToString(data))
processModbusData(data)
}
}
}
// ProcessModbusData 解析ModBus数据
func processModbusData(data []byte) {
if len(data) < 3 {
log.Println("数据长度不足")
return
}
deviceAddr := data[0]
functionCode := data[1]
if functionCode != FuncReadHoldingRegisters {
log.Printf("不支持的功能码: %02X", functionCode)
return
}
switch deviceAddr {
case WeatherStationAddr:
processWeatherStationData(data)
case RainGaugeAddr:
processRainGaugeData(data)
default:
log.Printf("未知设备地址: %02X", deviceAddr)
}
}
// ProcessWeatherStationData 处理气象站数据
func processWeatherStationData(data []byte) {
if len(data) < 35 {
log.Println("气象站数据长度不足")
return
}
byteCount := int(data[2])
if len(data) < 3+byteCount+2 {
log.Println("气象站数据长度与字节数不匹配")
return
}
dataSection := data[3 : 3+byteCount]
weather := &models.WeatherData{
Timestamp: time.Now(),
}
if len(dataSection) >= 32 {
weather.WindSpeed = float64(uint16(dataSection[0])<<8|uint16(dataSection[1])) / 100.0
weather.WindForce = int(uint16(dataSection[2])<<8 | uint16(dataSection[3]))
weather.WindDirection8 = int(uint16(dataSection[4])<<8 | uint16(dataSection[5]))
weather.WindDirection360 = int(uint16(dataSection[6])<<8 | uint16(dataSection[7]))
weather.Humidity = float64(uint16(dataSection[8])<<8|uint16(dataSection[9])) / 10.0
weather.Temperature = float64(uint16(dataSection[10])<<8|uint16(dataSection[11])) / 10.0
weather.Noise = float64(uint16(dataSection[12])<<8|uint16(dataSection[13])) / 10.0
weather.PM25 = int(uint16(dataSection[14])<<8 | uint16(dataSection[15]))
weather.PM10 = int(uint16(dataSection[16])<<8 | uint16(dataSection[17]))
weather.AtmPressure = float64(uint16(dataSection[18])<<8|uint16(dataSection[19])) / 10.0
weather.LuxHigh = int(uint16(dataSection[20])<<8 | uint16(dataSection[21]))
weather.LuxLow = int(uint16(dataSection[22])<<8 | uint16(dataSection[23]))
weather.LightIntensity = int(uint16(dataSection[24])<<8 | uint16(dataSection[25]))
weather.Rainfall = float64(uint16(dataSection[26])<<8|uint16(dataSection[27])) / 10.0
weather.CompassAngle = float64(uint16(dataSection[28])<<8|uint16(dataSection[29])) / 100.0
weather.SolarRadiation = int(uint16(dataSection[30])<<8 | uint16(dataSection[31]))
dataMutex.Lock()
latestWeatherData = weather
dataMutex.Unlock()
log.Printf("气象站数据更新: 温度=%.1f℃, 湿度=%.1f%%, 风速=%.2fm/s, 风向=%d°, 大气压力=%.1fhPa, PM2.5=%dμg/m³, PM10=%dμg/m³, 降雨量=%.1fmm, 光照强度=%dlux",
weather.Temperature, weather.Humidity, weather.WindSpeed, weather.WindDirection360, weather.AtmPressure,
weather.PM25, weather.PM10, weather.Rainfall, weather.LightIntensity)
// 保存到数据库
_, err := db.SaveWeatherData(weather)
if err != nil {
log.Printf("保存气象站数据失败: %v", err)
} else {
log.Println("气象站数据已保存到数据库")
}
}
}
// ProcessRainGaugeData 处理雨量计数据
func processRainGaugeData(data []byte) {
if len(data) < 25 {
log.Println("雨量计数据长度不足")
return
}
byteCount := int(data[2])
if len(data) < 3+byteCount+2 {
log.Println("雨量计数据长度与字节数不匹配")
return
}
dataSection := data[3 : 3+byteCount]
rainData := &models.RainGaugeData{
Timestamp: time.Now(),
}
if len(dataSection) >= 20 {
rainData.DailyRainfall = float64(uint16(dataSection[0])<<8|uint16(dataSection[1])) / 10.0
rainData.InstantRainfall = float64(uint16(dataSection[2])<<8|uint16(dataSection[3])) / 10.0
rainData.YesterdayRainfall = float64(uint16(dataSection[4])<<8|uint16(dataSection[5])) / 10.0
rainData.TotalRainfall = float64(uint16(dataSection[6])<<8|uint16(dataSection[7])) / 10.0
rainData.HourlyRainfall = float64(uint16(dataSection[8])<<8|uint16(dataSection[9])) / 10.0
rainData.LastHourRainfall = float64(uint16(dataSection[10])<<8|uint16(dataSection[11])) / 10.0
rainData.Max24hRainfall = float64(uint16(dataSection[12])<<8|uint16(dataSection[13])) / 10.0
rainData.Max24hRainfallPeriod = int(uint16(dataSection[14])<<8 | uint16(dataSection[15]))
rainData.Min24hRainfall = float64(uint16(dataSection[16])<<8|uint16(dataSection[17])) / 10.0
rainData.Min24hRainfallPeriod = int(uint16(dataSection[18])<<8 | uint16(dataSection[19]))
dataMutex.Lock()
latestRainData = rainData
dataMutex.Unlock()
log.Printf("雨量计数据更新: 当天降雨量=%.1fmm, 瞬时降雨量=%.1fmm, 总降雨量=%.1fmm, 昨日降雨量=%.1fmm, 小时降雨量=%.1fmm, 上一小时降雨量=%.1fmm",
rainData.DailyRainfall, rainData.InstantRainfall, rainData.TotalRainfall,
rainData.YesterdayRainfall, rainData.HourlyRainfall, rainData.LastHourRainfall)
// 保存到数据库
_, err := db.SaveRainGaugeData(rainData)
if err != nil {
log.Printf("保存雨量计数据失败: %v", err)
} else {
log.Println("雨量计数据已保存到数据库")
}
}
}
// QueryDevice 向设备发送查询命令
func QueryDevice(deviceType int) error {
var cmd []byte
switch deviceType {
case DeviceWeatherStation:
cmd = WeatherStationCmd
case DeviceRainGauge:
cmd = RainGaugeCmd
default:
return fmt.Errorf("未知设备类型: %d", deviceType)
}
clientsMutex.RLock()
defer clientsMutex.RUnlock()
if len(connectedClients) == 0 {
return fmt.Errorf("没有连接的客户端")
}
for addr, conn := range connectedClients {
_, err := conn.Write(cmd)
if err != nil {
log.Printf("向客户端 %s 发送命令失败: %v", addr, err)
continue
}
log.Printf("向客户端 %s 发送命令: %s", addr, hex.EncodeToString(cmd))
}
return nil
}
// GetConnectionStatus 获取连接状态
func GetConnectionStatus() models.ConnectionStatus {
clientsMutex.RLock()
defer clientsMutex.RUnlock()
status := models.ConnectionStatus{
Connected: len(connectedClients) > 0,
Count: len(connectedClients),
}
if len(connectedClients) > 0 {
for addr := range connectedClients {
host, _, _ := net.SplitHostPort(addr)
status.IP = host
status.Port = 10004
break
}
}
return status
}
// GetLatestWeatherData 获取最新气象站数据
func GetLatestWeatherData() *models.WeatherData {
dataMutex.RLock()
defer dataMutex.RUnlock()
if latestWeatherData == nil {
return nil
}
// 返回一个副本
data := *latestWeatherData
return &data
}
// GetLatestRainData 获取最新雨量计数据
func GetLatestRainData() *models.RainGaugeData {
dataMutex.RLock()
defer dataMutex.RUnlock()
if latestRainData == nil {
return nil
}
// 返回一个副本
data := *latestRainData
return &data
}