用golang的channel特性,来做简易分拣机的中控部分

先介绍一下项目的背景,之前单位有一个做小型快递分拣机的需求,针对小型包裹智能分拣到不通的出口。大致的物理传送带如下方图所示,原谅我不会画图。此文章的目的,只是给大家展示一下golang channel的用处。

如上图所示,传送带分了几个部分,头部区域,分拣工作区域,硬件设备(传感器和臂手)。
头部区域主要有摄像头和扫码枪,主要是识别包裹,查询出包裹对应的区域地址。
头部区域和分拣工作区域边界,会有一个红外线传感器,来确定包裹进入了分拣工作区域。
在传送带的齿轮上会有一个速度传感器,来实时接受信号,计算传送带转动的距离。
分拣工作区域每隔30cm会有臂手(这里我们会有led灯做模拟,其实就是一个GPIO)

根据以上的简述,我们用golang代码来简单实现这个逻辑

1. 功能分析

头部区域涉及到扫描枪和摄像头的AI识别,我们就暂时用一个scanPacket函数来模拟代替,

// 模拟 每隔两秒钟会有一个包裹

func ScanPacket() {
  ticker := time.NewTicker(2 * time.Second)
    for {
        <-ticker.C
        fmt.Println("scan a packet")
    } 
}

当识别到一个包裹后我们就要确定它要在哪个led灯附近,所以我们先要把led的配置初始化好

// Light 灯
type Light struct {
    Id       int    // 灯编号
    State    string // on/off
    Distance int64  // 距离入口红外线传感器的位置(就是距分拣工作区域起始位置的长度) 单位 mm
    SwitchCh chan struct{} // 用来通知该灯亮起
}

var lights = map[int]*Light{
    1: {
        Id:       1,
        State:    "off",
        Distance: 300,
        SwitchCh: make(chan struct{}, 10),
    },
    2: {
        Id:       2,
        State:    "off",
        Distance: 600,
        SwitchCh: make(chan struct{}, 10),
    },
    3: {
        Id:       3,
        State:    "off",
        Distance: 900,
        SwitchCh: make(chan struct{}, 10),
    },
    4: {
        Id:       4,
        State:    "off",
        Distance: 1200,
        SwitchCh: make(chan struct{}, 10),
    },
}

// 此处模拟了4个led灯和对应的传送带的位置

定义包裹的结构体

// Packet 包裹
type Packet struct {
    Id          int64         // 包裹id
    BelongLight *Light        // 所属led灯的位置
    Distance    int64         // 这个包裹对应的分拣工作区的位置(就是灯的位置)
    SensorCh    chan struct{} // 传感器的channel
}

由于速度传感器的io频率很高,如果把所有的packet都维护到一个数组里面,后面计算每个包裹的距离时,锁的并发会很大,我这边就利用了goroutine的优势,对每个包裹启动了一个goroutine,包裹的状态和距离都是单协程计算,不存在数据冲突。

func packetWorker(packet *Packet) {
    //fmt.Printf("packet %d scan\n", packet.Id)
    defer func() {
        close(packet.SensorCh)
    }()
    // 1. 注册包裹的channel
    packetChanRegisterSets.Register(packet)
    // 2. 开始监控速度传感器的信号
    for {
        <-packet.SensorCh
        packet.Distance = packet.Distance - 8
        //fmt.Printf("packet %d distance is %d\n", packet.Id, packet.Distance)
        if packet.Distance >= -16 && packet.Distance <= 16 {
            // 3. 通知对应的led灯亮起/(臂手拨动)
            packet.BelongLight.SwitchCh <- struct{}{}
            // 4. 取消注册
            packetChanRegisterSets.UnRegister(packet)
            return
        }
    }
}

每个包裹实例都有一个channel, 把包裹所有的channel都注册到一个集合里面,当接受速度传感器信号时,只需要把集合内的所有channel发一个信号(广播),就能通知所有的包裹重新计算所到的位置。整个系统的并发全部集中到PacketChanRegisterSet,大大的缩小了并发的范围。大部分的并发也只是读

type PacketChanRegisterSet struct {
    Set sync.Map
}

func (s *PacketChanRegisterSet) Register(packet *Packet) {
    s.Set.Store(packet.Id, packet.SensorCh)
}

// Broadcast 广播
func (s *PacketChanRegisterSet) Broadcast() {
    s.Set.Range(func(key, value any) bool {
        c := value.(chan struct{})
        c <- struct{}{}
        return true
    })
}

func (s *PacketChanRegisterSet) UnRegister(packet *Packet) {
    s.Set.Delete(packet.Id)
}

当led灯(或者臂手)接受到channel信号的时候就亮起

func lightWorker(light *Light) {
    for {
        <-light.SwitchCh
        fmt.Printf("light %d is turn on\n", light.Id)
    }
}

2. 代码实现

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

var lights = map[int]*Light{
    1: {
        Id:       1,
        State:    "off",
        Distance: 300,
        SwitchCh: make(chan struct{}, 10),
    },
    2: {
        Id:       2,
        State:    "off",
        Distance: 600,
        SwitchCh: make(chan struct{}, 10),
    },
    3: {
        Id:       3,
        State:    "off",
        Distance: 900,
        SwitchCh: make(chan struct{}, 10),
    },
    4: {
        Id:       4,
        State:    "off",
        Distance: 1200,
        SwitchCh: make(chan struct{}, 10),
    },
}

type PacketChanRegisterSet struct {
    Set sync.Map
}

func (s *PacketChanRegisterSet) Register(packet *Packet) {
    s.Set.Store(packet.Id, packet.SensorCh)
}

// Broadcast 广播
func (s *PacketChanRegisterSet) Broadcast() {
    s.Set.Range(func(key, value any) bool {
        c := value.(chan struct{})
        c <- struct{}{}
        return true
    })
}

func (s *PacketChanRegisterSet) UnRegister(packet *Packet) {
    s.Set.Delete(packet.Id)
}

var packetChanRegisterSets = PacketChanRegisterSet{
    Set: sync.Map{},
}

func main() {

    // 启动灯
    for _, light := range lights {
        go lightWorker(light)
    }
    // 启动扫描包裹程序
    go ScanPacket()

    // 模拟传感器
    ticker := time.NewTicker(200 * time.Millisecond)

    for {
        <-ticker.C
        packetChanRegisterSets.Broadcast()
    }

}

// Packet 包裹
type Packet struct {
    Id          int64         // 包裹id
    BelongLight *Light        // 所属led灯的位置
    Distance    int64         // 这个包裹对应的分拣工作区的位置(就是灯的位置)
    SensorCh    chan struct{} // 传感器的channel
}

// Light 灯
type Light struct {
    Id       int    // 灯编号
    State    string // on/off
    Distance int64  // 距离 单位 mm
    SwitchCh chan struct{}
}

// ScanPacket 扫描包裹
func ScanPacket() {
    ticker := time.NewTicker(2 * time.Second)
    for {
        <-ticker.C
        fmt.Println("scan a packet")
        id := rand.Intn(4)
        light := lights[id+1]
        packet := Packet{
            Id:          time.Now().Unix(),
            BelongLight: light,
            Distance:    light.Distance,
            SensorCh:    make(chan struct{}, 100),
        }
        go packetWorker(&packet)
    }
}

func packetWorker(packet *Packet) {
    //fmt.Printf("packet %d scan\n", packet.Id)
    defer func() {
        close(packet.SensorCh)
    }()
    // 1. 注册包裹的channel
    packetChanRegisterSets.Register(packet)
    // 2. 开始监控速度传感器的信号
    for {
        <-packet.SensorCh
        packet.Distance = packet.Distance - 8
        //fmt.Printf("packet %d distance is %d\n", packet.Id, packet.Distance)
        if packet.Distance >= -16 && packet.Distance <= 16 {
            // 3. 通知对应的led灯亮起/(臂手拨动)
            packet.BelongLight.SwitchCh <- struct{}{}
            // 4. 取消注册
            packetChanRegisterSets.UnRegister(packet)
            return
        }
    }
}

func lightWorker(light *Light) {
    for {
        <-light.SwitchCh
        fmt.Printf("light %d is turn on\n", light.Id)
    }
}

你可能感兴趣的:(用golang的channel特性,来做简易分拣机的中控部分)