-
Notifications
You must be signed in to change notification settings - Fork 0
/
plc.go
119 lines (107 loc) · 3.8 KB
/
plc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package plc
import (
"context"
"fmt"
"strconv"
"strings"
"time"
plc4go "github.com/apache/plc4x/plc4go/pkg/api"
"github.com/c0dered273/automation-remote-controller/internal/remote-control-client/configs"
"github.com/c0dered273/automation-remote-controller/pkg/model"
"github.com/rs/zerolog"
)
var (
plcReadDelay = 100 * time.Millisecond
)
// NewPlcConn возвращает настроенный пул соединений с контроллером
func NewPlcConn(driverManager plc4go.PlcDriverManager, config *configs.RClientConfig) (*ConnPool, error) {
conn, err := NewConnPool(driverManager, config.PLCUri)
if err != nil {
return nil, fmt.Errorf("failed to init plc connection pool: %w", err)
}
conn.SetMaxOpenConns(4)
conn.SetConnTimeout(3 * time.Second)
return conn, nil
}
// PollService сервис организует прием и передачу событий на контроллер используя пул соединений
// события приходят из других компонентов приложения через приемный и передающий каналы
type PollService struct {
ctx context.Context
conn *ConnPool
sendChan chan model.NotifyEvent
receiveChan chan model.ActionEvent
logger zerolog.Logger
}
func (s *PollService) continuousRead(config *configs.RClientConfig) {
riseTrig := make(map[string]bool)
for {
for _, n := range config.Notifications {
tag := strings.Split(n.TagAddress, "/")
tagAddress := tag[0]
bit, err := strconv.Atoi(tag[1])
if err != nil {
s.logger.Error().Err(err).Msgf("plc polling: failed to parse bit number: %s", tag[1])
continue
}
resp, err := s.conn.ReadTagAddress(s.ctx, "read", tagAddress)
if err != nil {
s.logger.Error().Err(err).Msgf("plc polling: failed to read tag: %s", tagAddress)
continue
}
value := resp.GetValue("read").GetBoolArray()
// Rise trigger нужен для исключения повторной отправки сообщения, если тег в контроллере все еще активен,
// отправка события происходит по переднему фронту сигнала
_, ok := riseTrig[n.TagAddress]
if !ok {
riseTrig[n.TagAddress] = false
}
if value[bit] && !riseTrig[n.TagAddress] {
s.sendChan <- model.NotifyEvent{
Text: n.Text[strconv.FormatBool(value[bit])],
}
riseTrig[n.TagAddress] = true
}
riseTrig[n.TagAddress] = value[bit]
time.Sleep(plcReadDelay)
}
}
}
func (s *PollService) continuousWrite(config *configs.RClientConfig) {
for {
a := <-s.receiveChan
for _, cfg := range config.Devices {
if strings.EqualFold(a.DeviceID, cfg.DeviceID) {
value, ok := cfg.Values[strings.ToLower(a.Action.String())]
if !ok {
s.logger.Error().Msgf("plc polling: action %s not found", a.Action.String())
}
_, err := s.conn.WriteTagAddress(s.ctx, "write", cfg.TagAddress, value)
if err != nil {
s.logger.Error().Err(err).Msgf("plc polling: failed to writing plc tag: %s, value %s", cfg.TagAddress, value)
continue
}
}
}
}
}
// Polling запускает циклический опрос событий с контроллера и запись данных в теги контроллера
func (s *PollService) Polling(config *configs.RClientConfig) {
go s.continuousRead(config)
go s.continuousWrite(config)
}
// NewPLCPollService возвращает настроенный сервис опроса ПЛК
func NewPLCPollService(
ctx context.Context,
conn *ConnPool,
sendChan chan model.NotifyEvent,
receiveChan chan model.ActionEvent,
logger zerolog.Logger,
) *PollService {
return &PollService{
ctx: ctx,
conn: conn,
sendChan: sendChan,
receiveChan: receiveChan,
logger: logger,
}
}