From bd1bb4a03eff53164bcc67ed365f208b17e29f9a Mon Sep 17 00:00:00 2001 From: huangsimin Date: Wed, 6 Nov 2019 17:51:00 +0800 Subject: [PATCH] init --- .gitignore | 2 + flow.go | 97 +++++++++++++++++++++++++++++++++++++ go.mod | 11 +++++ go.sum | 21 ++++++++ my.cfg | 4 ++ operator.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++ sensor.go | 63 ++++++++++++++++++++++++ tests/cfg_test.go | 58 ++++++++++++++++++++++ 8 files changed, 375 insertions(+) create mode 100644 .gitignore create mode 100644 flow.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 my.cfg create mode 100644 operator.go create mode 100644 sensor.go create mode 100644 tests/cfg_test.go 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") +}