commit bd1bb4a03eff53164bcc67ed365f208b17e29f9a Author: huangsimin Date: Wed Nov 6 17:51:00 2019 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f2390d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +my_test.cfg +*.db diff --git a/flow.go b/flow.go new file mode 100644 index 0000000..18ac77a --- /dev/null +++ b/flow.go @@ -0,0 +1,97 @@ +package flow + +import ( + "log" + "time" +) + +// FlowContext 流程的上下文, 用于传递每个流程之间的参数 +type FlowContext struct { + RootFlow *Flow + CurrentFlow *FlowNode + + CurrentWrite []byte // 8 byte + CurrentRead []byte // 14 byte +} + +// FlowNode 流程节点 +type FlowNode struct { + Name string + Path string + + prev *FlowNode + next *FlowNode + + Task func(cxt *FlowContext) int +} + +// Write 该写入函数包含了 自动封装日志格式 +func (cxt *FlowContext) Write(flag OperatorFlag) { + + operator.portWriterLock.Lock() + wbuf := OperatorOption(flag) + operator.port.Write(wbuf) + operator.port.Flush() + cxt.CurrentWrite = wbuf + operator.portWriterLock.Unlock() + + if len(wbuf) == 8 { + flowpath := cxt.CurrentFlow.Path + ">" + cxt.CurrentFlow.Name // 路径 + _, err := operator.oplog.Exec("insert into operator(ts , rootflow, flowpath, writelog, readlog) values(?,?,?,?,?)", time.Now().Unix(), cxt.RootFlow.Name, flowpath, cxt.CurrentWrite, cxt.CurrentRead) + if err != nil { + log.Println("日志写入错误: ", err) + } + } else { + log.Println("write buffer len is not equal to 8, now is", len(wbuf), "buf: ", wbuf) + } + +} + +// Sensor 返回传感器数值, 每次调用都是最新的. +func (cxt *FlowContext) Sensor() *Sensor { + buf := make([]byte, 14) + + operator.portReaderLock.Lock() + n, err := operator.port.Read(buf) + cxt.CurrentRead = buf + operator.portReaderLock.Unlock() + if err != nil { + log.Println("read bufferr is error:", err) + } else { + + if n == 14 { + return NewSensor(buf) + } + //TODO: 断包, 沾包 处理 + log.Println("读取长度不等于14, len = ", n) + } + + return nil +} + +// Flow 流程 +type Flow struct { + Name string + + Context *FlowContext // 执行流程时候的上下文 + + Head *FlowNode + Tail *FlowNode +} + +// Add 添加 +func (flow *Flow) Add(node *FlowNode) { + + if flow.Head == nil { + flow.Head = node + flow.Tail = node + return + } + + flow.Tail.next = node + flow.Tail = node + + if node.next != nil { + panic("tail next is not nil") + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..037f83a --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module flow + +go 1.13 + +require ( + github.com/mattn/go-sqlite3 v1.11.0 + github.com/smartystreets/goconvey v1.6.4 // indirect + github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 + golang.org/x/sys v0.0.0-20191104094858-e8c54fb511f6 // indirect + gopkg.in/ini.v1 v1.50.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..192565b --- /dev/null +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191104094858-e8c54fb511f6 h1:ZJUmhYTp8GbGC0ViZRc2U+MIYQ8xx9MscsdXnclfIhw= +golang.org/x/sys v0.0.0-20191104094858-e8c54fb511f6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/ini.v1 v1.50.0 h1:c/4YI/GUgB7d2yOkxdsQyYDhW67nWrTl6Zyd9vagYmg= +gopkg.in/ini.v1 v1.50.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/my.cfg b/my.cfg new file mode 100644 index 0000000..af338fa --- /dev/null +++ b/my.cfg @@ -0,0 +1,4 @@ +[config] +portid = /dev/pts3 +baud = 9600 +readtimeout = 5 \ No newline at end of file diff --git a/operator.go b/operator.go new file mode 100644 index 0000000..71323d7 --- /dev/null +++ b/operator.go @@ -0,0 +1,119 @@ +package flow + +import ( + "database/sql" + "encoding/binary" + "log" + "os" + "sync" + "time" + + serial "github.com/tarm/serial" + "gopkg.in/ini.v1" +) + +// GetOperatorDB 获取 operator 日志 +func GetOperatorDB() *sql.DB { + db, err := sql.Open("sqlite3", "./log.db") + if err != nil { + panic(err) + } + _, err = db.Exec("create table if not EXISTS operator ( ts INT, rootflow varchar(255), flowpath text, writelog char(8), readlog char(14))") + if err != nil { + panic(err) + } + return db +} + +// OperatorFlag 操作位 +type OperatorFlag uint16 + +const ( + UltrasonicPower OperatorFlag = 0b1000000000000000 // UltrasonicPower bit15 超声波电源开关 1开,0关 + // CYZ-A-X + CirculatingIrrigation OperatorFlag = 0b0100000000000000 // CirculatingIrrigation bit14 循环灌洗水泵 1开,0关 + UFRecoil OperatorFlag = 0b0010000000000000 // bit13 UF 超滤膜反冲进水阀 1开,0关 + UFPositive OperatorFlag = 0b0001000000000000 // bit12 UF 超滤膜正冲进水阀 1开,0关 + // YM-01-X-03 + UFTreatedWater OperatorFlag = 0b0000100000000000 // bit11 UF 超滤膜净水出水阀 1开,0关 + UFRawWater OperatorFlag = 0b0000010000000000 // bit10 UF超滤膜原水进水阀 1开,0关 + // CirculatingTankWashWater YM-01-X-01 + CirculatingTankWashWater OperatorFlag = 0b0000001000000000 // bit9 循环罐洗进水电动球阀 1开,0关 + UFPositiveFlushingWaterOutlet OperatorFlag = 0b0000000100000000 // bit8 UF超滤膜正冲浓水出口电磁阀 1开,0关 + // CleaningTankExhaust YV-02-02-1-X-06 + CleaningTankExhaust OperatorFlag = 0b0000000010000000 // bit7 清洗罐排气电磁阀 1开,0关 + DPFCompactCylinderControlB OperatorFlag = 0b0000000001000000 // bit6 DPF压紧气缸控制电磁阀B 1开,0关 + DPFCompactCylinderControlA OperatorFlag = 0b0000000000100000 // bit5 DPF压紧气缸控制电磁阀A 1开,0关 + // YV-02-05-1-X-04 + CleaningTankDrainingWater OperatorFlag = 0b0000000000010000 // bit4 清洗罐放水阀控制电磁阀 1开,0关 + GasExplosion OperatorFlag = 0b0000000000001000 // bit3 气爆阀控制电磁阀 1开,0关 + // YV-02-02-1-X-02 + CleaningTankInflation OperatorFlag = 0b0000000000000100 // bit2 清洗罐充气电磁阀 1开,0关 + CleaningTankSealB OperatorFlag = 0b0000000000000010 // bit1 清洗罐密封圈充气电磁阀B 1开,0关 + CleaningTankSealA OperatorFlag = 0b0000000000000001 // bit0 清洗罐密封圈充气电磁阀A 1开,0关 +) + +// OperatorOption 操作位设置 +func OperatorOption(flag OperatorFlag) []byte { + var buf []byte = make([]byte, 8, 8) + buf[0] = byte(0xaa) + buf[1] = byte(0x55) + binary.BigEndian.PutUint16(buf[2:], uint16(flag)) + check := byte(0) + for _, b := range buf { + check += b + } + buf[7] = check + return buf +} + +// Operator 日志系统 +type Operator struct { + port *serial.Port + + portReaderLock *sync.Mutex + portWriterLock *sync.Mutex + + oplog *sql.DB +} + +var operator *Operator + +func init() { + op := &Operator{} + op.portReaderLock = &sync.Mutex{} + op.portWriterLock = &sync.Mutex{} + + op.oplog = GetOperatorDB() + + var portid string + var baud int + var rtimeout time.Duration + + var cfg *ini.File + cfg, err := ini.Load("my_test.cfg") + if err != nil { + log.Println(err) + log.Println("加载配置my.cfg失败, 将使用默认值") + f, err := os.OpenFile("./my_test.cfg", os.O_CREATE|os.O_WRONLY, 0666) + if err != nil { + panic(err) + } + f.WriteString("[config]\nportid = COM1\nbaud = 9600\nreadtimeout = 5") + cfg, err = ini.Load("my_test.cfg") + if err != nil { + panic(err) + } + } + + section := cfg.Section("config") + portid = section.Key("portid").MustString("COM1") + baud = section.Key("baud").MustInt(9600) + rtimeout = time.Second * time.Duration(section.Key("readtimeout").MustInt64(5)) + + port, err := serial.OpenPort(&serial.Config{Name: portid, Baud: baud, ReadTimeout: rtimeout}) + if err != nil { + panic(err) + } + op.port = port +} diff --git a/sensor.go b/sensor.go new file mode 100644 index 0000000..0336fe6 --- /dev/null +++ b/sensor.go @@ -0,0 +1,63 @@ +package flow + +import ( + "encoding/binary" + "fmt" + "log" +) + +// Sensor 传感器 +type Sensor struct { + SP01 uint16 // 清洗罐气压传感器 SP-01 罐体排空,值≤0.5(暂定) + SP02 uint16 // 清洗液水箱水位传感器 SP-02 模拟量传感器 + LT01 uint16 // 清洗罐满水位传感器 (非接触式水位传感器) LT-01 模拟量传感器 + LT02 uint16 // 清洗罐排水传感器 (非接触式水位传感器) LT-02 模拟量传感器 + LT03 uint8 // 清洗液水箱满水位传感器 (浮子式水位传感器) LT-03 值为1,清洗液水箱满水位 +} + +func (sensor *Sensor) String() string { + return fmt.Sprintf("SP01=%d SP02=%d LT01=%d LT02=%d LT03=%d", sensor.SP01, sensor.SP02, sensor.LT01, sensor.LT02, sensor.LT03) +} + +func NewSensor(buf []byte) *Sensor { + + if len(buf) == 14 { + + if buf[0] == byte(0xaa) && buf[1] == byte(0x55) { + + sensor := &Sensor{} + + sensor.SP01 = binary.BigEndian.Uint16(buf[2:4]) + sensor.SP02 = binary.BigEndian.Uint16(buf[4:6]) + sensor.LT01 = binary.BigEndian.Uint16(buf[6:8]) + sensor.LT02 = binary.BigEndian.Uint16(buf[8:10]) + sensor.LT03 = uint8(buf[10]) + + return sensor + } + + } + + log.Println(buf, "非标准传感器字节流") + + return nil +} + +// BytesFromSensor 生成Buf从Sensor +func BytesFromSensor(sensor *Sensor) []byte { + buf := make([]byte, 14) + buf[0] = byte(0xaa) + buf[1] = byte(0x55) + + binary.BigEndian.PutUint16(buf[2:4], sensor.SP01) + binary.BigEndian.PutUint16(buf[4:6], sensor.SP02) + binary.BigEndian.PutUint16(buf[6:8], sensor.LT01) + binary.BigEndian.PutUint16(buf[8:10], sensor.LT02) + buf[10] = sensor.LT03 + + for _, b := range buf[0:13] { + buf[13] += b + } + + return buf +} diff --git a/tests/cfg_test.go b/tests/cfg_test.go new file mode 100644 index 0000000..073d9c1 --- /dev/null +++ b/tests/cfg_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "log" + "os" + "testing" + "time" + + "gopkg.in/ini.v1" +) + +func TestIntCreate(t *testing.T) { + + os.Remove("./my_test.cfg") + time.Sleep(time.Second * 1) + + for i := 0; i < 3; i++ { + + var portid string + var baud int + var rtimeout time.Duration + + var cfg *ini.File + cfg, err := ini.Load("my_test.cfg") + if err != nil { + log.Println(err) + log.Println("加载配置my.cfg失败, 将使用默认值") + f, err := os.OpenFile("./my_test.cfg", os.O_CREATE|os.O_WRONLY, 0666) + if err != nil { + panic(err) + } + f.WriteString("[config]\nportid = COM1\nbaud = 9600\nreadtimeout = 5") + cfg, err = ini.Load("my_test.cfg") + if err != nil { + panic(err) + } + } + + section := cfg.Section("config") + portid = section.Key("portid").MustString("COM1") + baud = section.Key("baud").MustInt(9600) + rtimeout = time.Second * time.Duration(section.Key("readtimeout").MustInt64(5)) + + if portid != "COM1" { + t.Error(`portid != "COM1"`) + } + + if baud != 9600 { + t.Error(`baud != 9600`) + } + + if rtimeout != time.Second*5 { + t.Error(`rtimeout != time.Second * 5`) + } + } + + os.Remove("./my_test.cfg") +}