博客 / 詳情

返回

用golang的channel特性,來做簡易分揀機的中控部分

先介紹一下項目的背景,之前單位有一個做小型快遞分揀機的需求,針對小型包裹智能分揀到不通的出口。大致的物理傳送帶如下方圖所示,原諒我不會畫圖。此文章的目的,只是給大家展示一下golang channel的用處。

9.png

如上圖所示,傳送帶分了幾個部分,頭部區域,分揀工作區域,硬件設備(傳感器和臂手)。
頭部區域主要有攝像頭和掃碼槍,主要是識別包裹,查詢出包裹對應的區域地址。
頭部區域和分揀工作區域邊界,會有一個紅外線傳感器,來確定包裹進入了分揀工作區域。
在傳送帶的齒輪上會有一個速度傳感器,來實時接受信號,計算傳送帶轉動的距離。
分揀工作區域每隔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)
    }
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.