diff --git a/FatAgent b/FatAgent new file mode 100755 index 0000000..4830a68 Binary files /dev/null and b/FatAgent differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..88969c5 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module git.mmeiblog.cn/mei/FatAgent + +go 1.23.4 + +require ( + github.com/goburrow/modbus v0.1.0 + github.com/robfig/cron/v3 v3.0.1 +) + +require github.com/goburrow/serial v0.1.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7599848 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/goburrow/modbus v0.1.0 h1:DejRZY73nEM6+bt5JSP6IsFolJ9dVcqxsYbpLbeW/ro= +github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg= +github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA= +github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= diff --git a/internal/report.go b/internal/report.go new file mode 100644 index 0000000..5bf0569 --- /dev/null +++ b/internal/report.go @@ -0,0 +1 @@ +package internal diff --git a/main.go b/main.go new file mode 100644 index 0000000..cb9d3b5 --- /dev/null +++ b/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "git.mmeiblog.cn/mei/FatAgent/pkg" + "github.com/robfig/cron/v3" +) + +func main() { + c := cron.New() + + c.AddFunc("@every 1m", func() { + pkg.NewACController("/dev/tty1") + }) + + c.Start() + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGTERM) + <-sig // 阻塞直到收到信号 + fmt.Printf("Hava a good Day!") + c.Stop() // 关闭 cron +} diff --git a/pkg/airConditioner.go b/pkg/airConditioner.go new file mode 100644 index 0000000..c89b34a --- /dev/null +++ b/pkg/airConditioner.go @@ -0,0 +1,181 @@ +package pkg + +import ( + "encoding/binary" + "fmt" + "time" + + "github.com/goburrow/modbus" +) + +// Modbus 寄存器地址常量 +const ( + coilStartAddress = 1 + coilCount = 32 + registerStartAddress = 3 + registerCount = 3 + temperatureRegister = 6 // 40007 + humidityRegister = 7 // 40008 +) + +type ACStatus struct { + // 开关量 (BOOL) + FanProtect bool `json:"fanProtect"` // 送风机保护 + FanPressureSwitch bool `json:"fanPressureSwitch"` // 送风压差开关 + PhaseProtect bool `json:"phaseProtect"` // 相许保护 + FireLinkage bool `json:"fireLinkage"` // 防火连锁开关 + HeaterOverheat bool `json:"heaterOverheat"` // 电加热过热 + RemoteSwitch bool `json:"remoteSwitch"` // 远程开关 + HumidifierFault bool `json:"humidifierFault"` // 加湿器故障 + Comp1HighPressure bool `json:"comp1HighPressure"` // 1#压机高压 + Comp1LowPressure bool `json:"comp1LowPressure"` // 1#压机低压 + Comp1Overload bool `json:"comp1Overload"` // 1#压机过载 + Cond1Overload bool `json:"cond1Overload"` // 1#冷凝风机过载 + Comp2HighPressure bool `json:"comp2HighPressure"` // 2#压机高压 + Comp2LowPressure bool `json:"comp2LowPressure"` // 2#压机低压 + Comp2Overload bool `json:"comp2Overload"` // 2#压机过载 + Cond2Overload bool `json:"cond2Overload"` // 2#冷凝风机过载 + RunSignal bool `json:"runSignal"` // 运行信号 + FaultSignal bool `json:"faultSignal"` // 故障信号 + Valve bool `json:"valve"` // 风阀 + SupplyFan bool `json:"supplyFan"` // 送风机 + Comp1 bool `json:"comp1"` // 1#压机 + Cond1 bool `json:"cond1"` // 1#冷凝风机 + Comp2 bool `json:"comp2"` // 2#压机 + Cond2 bool `json:"cond2"` // 2#冷凝风机 + Heater1 bool `json:"heater1"` // 1#电加热 + Heater2 bool `json:"heater2"` // 2#电加热 + + // 模拟量 (WORD) + SupplyTemp uint16 // 送风温度 + IndoorTemp uint16 // 室内温度 + IndoorHumidity uint16 // 室内湿度 +} + +// 控制器 +type ACController struct { + handler *modbus.RTUClientHandler + client modbus.Client +} + +func NewACController(port string) (*ACController, error) { + handler := modbus.NewRTUClientHandler(port) + handler.BaudRate = 9600 + handler.DataBits = 8 + handler.Parity = "N" + handler.StopBits = 1 + handler.SlaveId = 1 + handler.Timeout = 2 * time.Second + + if err := handler.Connect(); err != nil { + handler.Close() // 避免资源泄漏 + return nil, fmt.Errorf("连接Modbus设备失败: %v", err) + } + + client := modbus.NewClient(handler) + + return &ACController{ + handler: handler, + client: client, + }, nil +} + +// 启动 +func (ac *ACController) Start() error { + _, err := ac.client.WriteSingleCoil(0, 0xFF00) // 启动 -> 40001 + if err != nil { + return fmt.Errorf("启动设备失败: %v", err) + } + return nil +} + +// 停止 +func (ac *ACController) Stop() error { + _, err := ac.client.WriteSingleCoil(1, 0xFF00) // 停止 -> 40002 + if err != nil { + return fmt.Errorf("停止设备失败: %v", err) + } + return nil +} + +// 设置温度 +func (ac *ACController) SetTemperature(value uint16) error { + _, err := ac.client.WriteSingleRegister(temperatureRegister, value) // 40007 + if err != nil { + return fmt.Errorf("设置温度失败: %v", err) + } + return nil +} + +// 设置湿度 +func (ac *ACController) SetHumidity(value uint16) error { + _, err := ac.client.WriteSingleRegister(humidityRegister, value) // 40008 + if err != nil { + return fmt.Errorf("设置湿度失败: %v", err) + } + return nil +} + +func (ac *ACController) GetAllStatus() (*ACStatus, error) { + status := &ACStatus{} + + // 1. 读取布尔量 + bools, err := ac.client.ReadCoils(coilStartAddress, coilCount) // offset=1 -> 40002 + if err != nil { + return nil, fmt.Errorf("读取布尔量失败: %v", err) + } + if len(bools)*8 < 25 { + return nil, fmt.Errorf("布尔量数据不足,缺少必要位") + } + status.FanProtect = getBool(bools, 0) + status.FanPressureSwitch = getBool(bools, 1) + status.PhaseProtect = getBool(bools, 2) + status.FireLinkage = getBool(bools, 3) + status.HeaterOverheat = getBool(bools, 4) + status.RemoteSwitch = getBool(bools, 5) + status.HumidifierFault = getBool(bools, 6) + status.Comp1HighPressure = getBool(bools, 7) + status.Comp1LowPressure = getBool(bools, 8) + status.Comp1Overload = getBool(bools, 9) + status.Cond1Overload = getBool(bools, 10) + status.Comp2HighPressure = getBool(bools, 11) + status.Comp2LowPressure = getBool(bools, 12) + status.Comp2Overload = getBool(bools, 13) + status.Cond2Overload = getBool(bools, 14) + status.RunSignal = getBool(bools, 15) + status.FaultSignal = getBool(bools, 16) + status.Valve = getBool(bools, 17) + status.SupplyFan = getBool(bools, 18) + status.Comp1 = getBool(bools, 19) + status.Cond1 = getBool(bools, 20) + status.Comp2 = getBool(bools, 21) + status.Cond2 = getBool(bools, 22) + status.Heater1 = getBool(bools, 23) + status.Heater2 = getBool(bools, 24) + + // 2. 读取测量量:送风温度、室内温度、室内湿度 + // 寄存器:40004, 40005, 40006 → offset=3, count=3 + words, err := ac.client.ReadHoldingRegisters(registerStartAddress, registerCount) + if err != nil { + return nil, fmt.Errorf("读取模拟量失败: %v", err) + } + if len(words) < 6 { + return nil, fmt.Errorf("模拟量数据不足,期望6字节,实际%d字节", len(words)) + } + + status.SupplyTemp = binary.BigEndian.Uint16(words[0:2]) + status.IndoorTemp = binary.BigEndian.Uint16(words[2:4]) + status.IndoorHumidity = binary.BigEndian.Uint16(words[4:6]) + + return status, nil +} + +// 解析单个 bit +func getBool(data []byte, index int) bool { + byteIndex := index / 8 + bitIndex := index % 8 + if byteIndex >= len(data) { + return false + } + return (data[byteIndex] & (1 << bitIndex)) != 0 +}