Files
2026-07-12 19:18:26 +08:00

104 lines
2.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package protocol
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"regexp"
"sync"
)
type DataType string
const (
Bool DataType = "BOOL"
Int DataType = "INT"
Real DataType = "REAL"
)
type DeviceConnectionConfig struct {
DeviceID uuid.UUID
Host string
Port int
ConnectTimeoutSeconds int
ProtocolConfig map[string]any
}
type Connection interface {
Read(context.Context, string, DataType) (float64, error)
Write(context.Context, string, DataType, float64) error
Close() error
}
type Factory interface {
ProtocolType() string
ValidateConfig(map[string]any) error
ValidateAddress(string, DataType, bool) error
ConfigSchema() map[string]any
NewConnection(context.Context, DeviceConnectionConfig) (Connection, error)
}
type Registry struct {
mu sync.RWMutex
items map[string]Factory
}
func NewRegistry() *Registry { return &Registry{items: map[string]Factory{}} }
func (r *Registry) Register(f Factory) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.items[f.ProtocolType()]; ok {
return fmt.Errorf("协议已注册: %s", f.ProtocolType())
}
r.items[f.ProtocolType()] = f
return nil
}
func (r *Registry) Get(t string) (Factory, error) {
r.mu.RLock()
defer r.mu.RUnlock()
f, ok := r.items[t]
if !ok {
return nil, errors.New("不支持的协议")
}
return f, nil
}
func (r *Registry) Metadata() []map[string]any {
r.mu.RLock()
defer r.mu.RUnlock()
out := []map[string]any{}
for _, f := range r.items {
port := 502
if f.ProtocolType() == "S7" {
port = 102
}
out = append(out, map[string]any{"protocol_type": f.ProtocolType(), "default_port": port, "config_schema": f.ConfigSchema()})
}
return out
}
var s7DB = regexp.MustCompile(`^DB\d+\.\d+(?:\.[0-7])?$`)
var s7Standard = regexp.MustCompile(`^[MIQ]\d+(?:\.[0-7])?$`)
var s7Word = regexp.MustCompile(`^[MQ][WD]\d+$`)
func ValidateS7Address(a string, t DataType, w bool) error {
if !s7DB.MatchString(a) && !s7Standard.MatchString(a) && !s7Word.MatchString(a) {
return errors.New("S7 地址格式无效")
}
hasBit := regexp.MustCompile(`\.[0-7]$`).MatchString(a)
numericDBZeroSuffix := t != Bool && regexp.MustCompile(`^DB\d+\.\d+\.0$`).MatchString(a)
if t == Bool && !hasBit {
return errors.New("BOOL 地址必须包含 bit")
}
if t != Bool && hasBit && !numericDBZeroSuffix {
return errors.New("INT/REAL 地址不能包含 bit(仅兼容 DB 数值地址末尾 .0")
}
if t == Int && regexp.MustCompile(`^[MQ]D`).MatchString(a) {
return errors.New("INT 应使用 MW/QW 地址")
}
if t == Real && regexp.MustCompile(`^[MQ]W`).MatchString(a) {
return errors.New("REAL 应使用 MD/QD 地址")
}
if w && len(a) > 0 && a[0] == 'I' {
return errors.New("I 区只读")
}
return nil
}