feat(airConditioner): 添加空调的串口逻辑(未经测试)

This commit is contained in:
mei 2025-08-19 22:40:22 +08:00
parent 7da66aaaeb
commit 0519fbe216
6 changed files with 225 additions and 0 deletions

BIN
FatAgent Executable file

Binary file not shown.

10
go.mod Normal file
View File

@ -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

6
go.sum Normal file
View File

@ -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=

1
internal/report.go Normal file
View File

@ -0,0 +1 @@
package internal

27
main.go Normal file
View File

@ -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
}

181
pkg/airConditioner.go Normal file
View File

@ -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
}