From 089ccb467a59f418bce7d2bcbf4eeab5e591c7b6 Mon Sep 17 00:00:00 2001 From: niceboy Date: Sun, 24 Mar 2024 11:33:13 +0000 Subject: [PATCH] Add the supports for Aqara Gateways --- internal/app/app.go | 45 +++++++++++++++++++++++++- internal/app/serial.go | 50 +++++++++++++++++++++++++++++ internal/miio/cloud.go | 10 +++++- internal/miio/local.go | 21 ++++++++++--- internal/miio/miio.go | 66 +++++++++++++++++++++++++++++++++------ internal/miio/report.go | 20 +++++++++++- internal/zigbee/zigbee.go | 12 ++++++- 7 files changed, 207 insertions(+), 17 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 1302152..5319b83 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog/log" "io" "os" + "os/exec" "runtime" "strings" ) @@ -39,8 +40,12 @@ func Init() { // get device model and firmware version if b, err := os.ReadFile("/etc/build.prop"); err == nil { - Firmware = getKey(b, "ro.sys.mi_fw_ver=") + "_" + getKey(b, "ro.sys.mi_build_num=") Model = getKey(b, "ro.sys.model=") + if IsAiot() { + Firmware = getKey(b, "ro.sys.fw_ver=") + "_" + getKey(b, "ro.sys.build_num=") + } else { + Firmware = getKey(b, "ro.sys.mi_fw_ver=") + "_" + getKey(b, "ro.sys.mi_build_num=") + } } else if b, err = os.ReadFile("/etc/rootfs_fw_info"); err == nil { Firmware = getKey(b, "version=") Model = ModelMGW @@ -79,9 +84,16 @@ const ( ModelMGW = "lumi.gateway.mgl03" ModelE1 = "lumi.gateway.aqcn02" ModelMGW2 = "lumi.gateway.mcn001" + ModelM1S = "lumi.gateway.acn01" ModelM1S22 = "lumi.gateway.acn004" + ModelM2 = "lumi.gateway.iragl5" + ModelG2HPro= "lumi.camera.agl001" + ModelG3 = "lumi.camera.gwpagl01" + ModelM2PoE = "lumi.gateway.iragl8" + ModelM3 = "lumi.gateway.acn012" ) +var Cloud string var Firmware string var Model string var Args = map[string]string{} @@ -97,3 +109,34 @@ func getKey(b []byte, sub string) string { } return string(b) } + +func IsAiot() bool { + switch Model { + case ModelM2, ModelM1S, ModelM1S22, ModelG2HPro, ModelM2PoE, ModelG3, ModelM3: + if Model == ModelM1S || Model == ModelM1S22 { + if Cloud == "" { + if b, err := exec.Command("agetprop", "persist.sys.cloud").Output(); err == nil { + Cloud = strings.TrimRight(string(b), "\n") + log.Info().Msgf("b %s", string(b)) + } + } + if Cloud == "miot" { + return false + } + } + Cloud = "aiot" + return true + case ModelE1: + if Cloud == "" { + if b, err := exec.Command("agetprop", "persist.sys.cloud").Output(); err == nil { + Cloud = strings.TrimRight(string(b), "\n") + } + } + if Cloud == "aiot" { + return true + } + } + Cloud = "miot" + return false +} + diff --git a/internal/app/serial.go b/internal/app/serial.go index 8a7aa45..8639a92 100644 --- a/internal/app/serial.go +++ b/internal/app/serial.go @@ -15,6 +15,9 @@ type SerialStats struct { ZigbeeRX uint32 `json:"zigbee_rx,omitempty"` ZigbeeTX uint32 `json:"zigbee_tx,omitempty"` ZigbeeOE uint32 `json:"zigbee_oe,omitempty"` + IrdaRX uint32 `json:"irda_rx,omitempty"` + IrdaTX uint32 `json:"irda_tx,omitempty"` + IrdaOE uint32 `json:"irda_oe,omitempty"` } } @@ -47,6 +50,53 @@ func (s *SerialStats) MarshalJSON() ([]byte, error) { s.stat.ZigbeeRX = counters[4] s.stat.ZigbeeOE = counters[5] } + case ModelM1S: + counters := readSerial("/proc/tty/driver/serial") + if len(counters) >= 6 { + s.stat.ZigbeeTX = counters[3] + s.stat.ZigbeeRX = counters[4] + s.stat.ZigbeeOE = counters[5] + } + case ModelM2: + counters := readSerial("/proc/tty/driver/ms_uart") + if len(counters) >= 6 { + s.stat.ZigbeeTX = counters[3] + s.stat.ZigbeeRX = counters[4] + s.stat.ZigbeeOE = counters[5] + s.stat.IrdaTX = counters[6] + s.stat.IrdaRX = counters[7] + s.stat.IrdaOE = counters[8] + } + case ModelM1S22, ModelG2HPro: + counters := readSerial("/proc/tty/driver/ms_uart") + if len(counters) >= 6 { + s.stat.ZigbeeTX = counters[3] + s.stat.ZigbeeRX = counters[4] + s.stat.ZigbeeOE = counters[5] + } + case ModelG3: + counters := readSerial("/proc/tty/driver/ms_uart") + if len(counters) >= 9 { + s.stat.IrdaTX = counters[6] + s.stat.IrdaRX = counters[7] + s.stat.IrdaOE = counters[8] + s.stat.ZigbeeTX = counters[9] + s.stat.ZigbeeRX = counters[10] + s.stat.ZigbeeOE = counters[11] + } + case ModelM2PoE, ModelM3: + counters := readSerial("/proc/tty/driver/ms_uart") + if len(counters) >= 9 { + s.stat.ZigbeeTX = counters[3] + s.stat.ZigbeeRX = counters[4] + s.stat.ZigbeeOE = counters[5] + s.stat.BluetoothTX = counters[6] + s.stat.BluetoothRX = counters[7] + s.stat.BluetoothOE = counters[8] + s.stat.IrdaTX = counters[9] + s.stat.IrdaRX = counters[10] + s.stat.IrdaOE = counters[11] + } } return json.Marshal(s.stat) diff --git a/internal/miio/cloud.go b/internal/miio/cloud.go index eac5a85..1a3864d 100644 --- a/internal/miio/cloud.go +++ b/internal/miio/cloud.go @@ -1,6 +1,7 @@ package miio import ( + "github.com/AlexxIT/openmiio_agent/internal/app" "bytes" "net" "time" @@ -11,11 +12,18 @@ var conn0 net.Conn func cloudWorker() { var err error var n int + var connection string sep := []byte(`}{`) for { - conn0, err = net.Dial("tcp", "localhost:54322") + if app.IsAiot() { + connection = "127.0.0.1:21397" + } else { + connection = "localhost:54322" + } + + conn0, err = net.Dial("tcp", connection) if err != nil { time.Sleep(time.Second * 10) continue diff --git a/internal/miio/local.go b/internal/miio/local.go index 302c245..962b313 100644 --- a/internal/miio/local.go +++ b/internal/miio/local.go @@ -1,20 +1,26 @@ package miio import ( + "github.com/AlexxIT/openmiio_agent/internal/app" "github.com/AlexxIT/openmiio_agent/pkg/rpc" "net" "os" "os/exec" "sync" "time" + "bytes" ) func localWorker() { _ = os.Remove("/tmp/miio_agent.socket") - _ = exec.Command("killall", "miio_agent").Run() + if app.IsAiot() { + _ = exec.Command("killall", "ha_agent").Run() + } else { + _ = exec.Command("killall", "miio_agent").Run() - // fix basic_gw (Multimode Gateway) bug with instant reconnection - time.Sleep(time.Millisecond * 500) + // fix basic_gw (Multimode Gateway) bug with instant reconnection + time.Sleep(time.Millisecond * 500) + } sock, err := net.Listen("unixpacket", "/tmp/miio_agent.socket") if err != nil { @@ -35,6 +41,7 @@ func localWorker() { func localClientWorker(conn net.Conn) { var from int + var len int b := make([]byte, 4096) for { @@ -43,7 +50,13 @@ func localClientWorker(conn net.Conn) { break } - msg, err := rpc.NewMessage(b[:n]) + index := bytes.IndexByte(b, 0) + if (index > n) { + len = n + } else { + len = index + } + msg, err := rpc.NewMessage(b[:len]) if err != nil { log.Warn().Err(err).Caller().Send() continue diff --git a/internal/miio/miio.go b/internal/miio/miio.go index 7a4dc2f..651267a 100644 --- a/internal/miio/miio.go +++ b/internal/miio/miio.go @@ -17,7 +17,14 @@ const ( AddrGateway = int(32) // basic_app AddrCentral = int(128) // central_service_lite AddrCloud = int(0) // miio_client - AddrMQQT = int(-1) // mosquitto + AddrMQTT = int(-1) // mosquitto + AddrAiotAuto = int(256) + AddrRecorder = int(1024) + AddrPPCS = int(2048) + AddrAI = int(4096) + AddrMatter = int(65536) + AddrAiotEvent = int(262144) + AddrAiotLan = int(524288) ) func appname(addr int) string { @@ -38,8 +45,22 @@ func appname(addr int) string { return "central" case AddrCloud: return "cloud" - case AddrMQQT: + case AddrMQTT: return "mqtt" + case AddrAiotAuto: + return "aiot_automation" + case AddrRecorder: + return "recorder" + case AddrPPCS: + return "ppcs" + case AddrAI: + return "ai" + case AddrMatter: + return "matter" + case AddrAiotEvent: + return "aiot_event" + case AddrAiotLan: + return "aiot_lanbox" } return strconv.Itoa(addr) } @@ -53,7 +74,7 @@ func Init() { mqtt.Subscribe(func(topic string, payload []byte) { if topic == "miio/command" { - miioRequestRaw(AddrMQQT, payload) + miioRequestRaw(AddrMQTT, payload) } }, "miio/command") @@ -62,7 +83,9 @@ func Init() { go rpc.MarksWorker() - go cloudWorker() + if app.Model != app.ModelM3 { + go cloudWorker() + } go localWorker() } @@ -70,7 +93,7 @@ func Send(to int, b []byte) { switch to { case AddrCloud: sendToCloud(b) - case AddrMQQT: + case AddrMQTT: mqtt.Publish("miio/command_ack", b, false) default: sendToUnicast(to, b) @@ -101,7 +124,26 @@ func miioRequest(from int, msg rpc.Message) { return // skip basic_gw bug } - msg.SetInt("_from", from) + if app.IsAiot() { + //msg0, to0 := rpc.FindMessage(msg) + //log.Info().Msgf("1111 %s %d", msg0, to0) + if msg0, to0 := rpc.FindMessage(msg); msg0 != nil { + log.Trace().Msgf("[miio] %s res from=%d to=%d", msg, from, to0) + + mqtt.Publish("miio/report_ack", msg, false) + miioResponse(to0, msg0, msg) + return + } else { + log.Trace().Msgf("[miio] %s req from=%d to=%d", msg, from, to) + if from > 0 { + mqtt.Publish("miio/report", msg, false) + } + msg.SetInt("_from", from) + } + } else { + msg.SetInt("_from", from) + + } } else if msg0, to0 := rpc.FindMessage(msg); msg0 != nil { // 2. Response from any to any (msg with original ID) log.Trace().Msgf("[miio] %s res from=%d to=%d", msg, from, to0) @@ -138,9 +180,15 @@ func miioRequest(from int, msg rpc.Message) { return } - if to == AddrCloud { - // swap message ID, so we can catch response on this request - rpc.MarkMessage(msg, from) + if app.IsAiot() { + if to == AddrZigbee || to == AddrAiotAuto || to == AddrAiotLan { + rpc.MarkMessage(msg, from) + } + } else { + if to == AddrCloud { + // swap message ID, so we can catch response on this request + rpc.MarkMessage(msg, from) + } } b, err := msg.Marshal() diff --git a/internal/miio/report.go b/internal/miio/report.go index 2ddc682..88f4efd 100644 --- a/internal/miio/report.go +++ b/internal/miio/report.go @@ -13,9 +13,10 @@ var report struct { } var cloudState string +type Params map[string]json.RawMessage func miioReport(to int, req rpc.Message, res *rpc.Message) bool { - if string(req["method"]) == `"local.query_status"` { + if string(req["method"]) == `"local.query_status"` || string(req["method"]) == `"basis.network"` { if state := string((*res)["params"]); state != cloudState { cloudState = state @@ -29,6 +30,23 @@ func miioReport(to int, req rpc.Message, res *rpc.Message) bool { report.CloudState = nil } } + if app.IsAiot() { + var params Params + if err := json.Unmarshal(req["params"], ¶ms); err == nil { + if state := string(params["name"]); state != cloudState { + cloudState = state + // params is bytes slice with quotes + report.CloudState = params["name"] + + if state == `"network_signal"` { + report.CloudStarts++ + report.CloudUptime = app.NewUptime() + } else { + report.CloudState = nil + } + } + } + } } return false // because we don't change response diff --git a/internal/zigbee/zigbee.go b/internal/zigbee/zigbee.go index 7fd6bfe..61ae96a 100644 --- a/internal/zigbee/zigbee.go +++ b/internal/zigbee/zigbee.go @@ -41,6 +41,9 @@ func Init() { case app.ModelM1S22: log.Warn().Msgf("[zigb] M1S 2022 unsupported") return + case app.ModelM2, app.ModelM1S, app.ModelM2PoE, app.ModelG3, app.ModelM3: + preventRestart("Z3GatewayHost_MQTT") + _ = exec.Command("killall", "Z3GatewayHost_MQTT").Run() default: return } @@ -55,6 +58,13 @@ func Init() { go z3Worker("mZ3GatewayHost_MQTT", "-p", "/dev/ttyS1", "-d", "/data/") case app.ModelMGW2: go z3Worker("mZ3GatewayHost_MQTT", "-p", "/dev/ttyS1", "-d", "/data/zigbee_host/", "-r", "c") + case app.ModelM2PoE, app.ModelG2HPro, app.ModelM3: + go z3Worker("Z3GatewayHost_MQTT", "-p", "/dev/ttyS1", "-d", "/data/", "-r", "c") + case app.ModelG3: + go z3Worker("Z3GatewayHost_MQTT", "-p", "/dev/ttyS3", "-d", "/data/", "-r", "c") + case app.ModelM2, app.ModelM1S, app.ModelM1S22: + log.Warn().Msgf("[zigb] M2 M1S unsupported") + return } } @@ -67,7 +77,7 @@ func Init() { switch app.Model { case app.ModelMGW: go tcpWorker(tcp, "/dev/ttyS2", false) - case app.ModelE1, app.ModelMGW2: + case app.ModelE1, app.ModelMGW2, app.ModelG2HPro, app.ModelM2PoE, app.ModelG3, app.ModelM3: go tcpWorker(tcp, "/dev/ttyS1", true) } }