rain_monitor/modbus/modbus.go
2025-07-12 17:41:49 +08:00

301 lines
7.8 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)
}
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)
}
}
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)
}
}
}
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)
}
}
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("气象站数据已保存到数据库")
}
}
}
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("雨量计数据已保存到数据库")
}
}
}
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
}
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
}
func GetLatestWeatherData() *models.WeatherData {
dataMutex.RLock()
defer dataMutex.RUnlock()
if latestWeatherData == nil {
return nil
}
data := *latestWeatherData
return &data
}
func GetLatestRainData() *models.RainGaugeData {
dataMutex.RLock()
defer dataMutex.RUnlock()
if latestRainData == nil {
return nil
}
data := *latestRainData
return &data
}