Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the supports for Aqara Gateways #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/rs/zerolog/log"
"io"
"os"
"os/exec"
"runtime"
"strings"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -79,9 +84,17 @@ 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"
ModelG2H = "lumi.camera.gwagl02"
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{}
Expand All @@ -97,3 +110,33 @@ func getKey(b []byte, sub string) string {
}
return string(b)
}

func IsAiot() bool {
switch Model {
case ModelM2, ModelM1S, ModelG2H, 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")
}
}
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
}

57 changes: 57 additions & 0 deletions internal/app/serial.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
}

Expand Down Expand Up @@ -47,6 +50,60 @@ 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) >= 9 {
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 ModelG2H:
counters := readSerial("/proc/tty/driver/ms_uart")
if len(counters) >= 9 {
s.stat.ZigbeeTX = counters[6]
s.stat.ZigbeeRX = counters[7]
s.stat.ZigbeeOE = counters[8]
}
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) >= 12 {
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)
Expand Down
10 changes: 9 additions & 1 deletion internal/miio/cloud.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package miio

import (
"github.com/AlexxIT/openmiio_agent/internal/app"
"bytes"
"net"
"time"
Expand All @@ -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
Expand Down
21 changes: 17 additions & 4 deletions internal/miio/local.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -35,6 +41,7 @@ func localWorker() {

func localClientWorker(conn net.Conn) {
var from int
var len int

b := make([]byte, 4096)
for {
Expand All @@ -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
Expand Down
66 changes: 57 additions & 9 deletions internal/miio/miio.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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")

Expand All @@ -62,15 +83,17 @@ func Init() {

go rpc.MarksWorker()

go cloudWorker()
if app.Model != app.ModelM3 {
go cloudWorker()
}
go localWorker()
}

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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
20 changes: 19 additions & 1 deletion internal/miio/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"], &params); 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
Expand Down
Loading