314 lines
8.4 KiB
Go
314 lines
8.4 KiB
Go
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
|
||
}
|