This is an automated email from the ASF dual-hosted git repository.
cdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new c76b28e feature(plc4go): simulated driver, connection pool
c76b28e is described below
commit c76b28ee125970b611ae608e5884121ef790facc
Author: cdutz <[email protected]>
AuthorDate: Thu Jan 6 17:37:10 2022 +0100
feature(plc4go): simulated driver, connection pool
---
Jenkinsfile | 2 +-
plc4go/go.mod | 6 +-
plc4go/go.sum | 70 +-
plc4go/internal/plc4go/knxnetip/FieldHandler.go | 15 -
plc4go/internal/plc4go/s7/Driver.go | 2 +-
plc4go/internal/plc4go/simulated/Connection.go | 162 +++++
.../internal/plc4go/simulated/Connection_test.go | 758 +++++++++++++++++++++
plc4go/internal/plc4go/simulated/Device.go | 76 +++
plc4go/internal/plc4go/simulated/Device_test.go | 288 ++++++++
plc4go/internal/plc4go/simulated/Driver.go | 79 +++
plc4go/internal/plc4go/simulated/Driver_test.go | 326 +++++++++
plc4go/internal/plc4go/simulated/Field.go | 71 ++
plc4go/internal/plc4go/simulated/FieldHandler.go | 103 +++
.../internal/plc4go/simulated/FieldHandler_test.go | 169 +++++
plc4go/internal/plc4go/simulated/Field_test.go | 291 ++++++++
plc4go/internal/plc4go/simulated/Reader.go | 84 +++
plc4go/internal/plc4go/simulated/Reader_test.go | 202 ++++++
plc4go/internal/plc4go/simulated/ValueHandler.go | 37 +
plc4go/internal/plc4go/simulated/Writer.go | 76 +++
plc4go/internal/plc4go/simulated/Writer_test.go | 214 ++++++
.../plc4go/utils/pool/PlcConnectionPool.go | 158 +++++
.../plc4go/utils/pool/PlcConnectionPool_test.go | 88 +++
.../plc4go/utils/pool/PooledPlcConnection.go | 188 +++++
plc4go/pkg/plc4go/pool/plc_connection_pool.go | 20 +
24 files changed, 3407 insertions(+), 78 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 92e83ea..a49f848 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -47,7 +47,7 @@ pipeline {
}
options {
- // Kill this job after one hour.
+ // Kill this job after one day.
timeout(time: 24, unit: 'HOURS')
// When we have test-fails e.g. we don't need to run the remaining
steps
skipStagesAfterUnstable()
diff --git a/plc4go/go.mod b/plc4go/go.mod
index b783d8e..dcb2101 100644
--- a/plc4go/go.mod
+++ b/plc4go/go.mod
@@ -23,7 +23,6 @@ go 1.16
require (
github.com/ajankovic/xdiff v0.0.1
- github.com/elastic/go-licenser v0.4.0 // indirect
github.com/google/gopacket v1.1.19
github.com/icza/bitio v1.0.0
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4
@@ -31,9 +30,8 @@ require (
github.com/rs/zerolog v1.20.0
github.com/snksoft/crc v1.1.0
github.com/subchen/go-xmldom v1.1.2
- github.com/tebeka/go2xunit v1.4.10 // indirect
+ github.com/viney-shih/go-lock v1.1.1
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
+ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
- golang.org/x/tools v0.1.8 // indirect
- gotest.tools/gotestsum v1.7.0 // indirect
)
diff --git a/plc4go/go.sum b/plc4go/go.sum
index cf5a1e6..d3915ac 100644
--- a/plc4go/go.sum
+++ b/plc4go/go.sum
@@ -3,103 +3,59 @@ github.com/ajankovic/xdiff v0.0.1/go.mod
h1:SUmEZ67uB97I0zkiuQ+lb+LOms9ipn8X+p+2
github.com/antchfx/xpath v0.0.0-20170515025933-1f3266e77307
h1:C735MoY/X+UOx6SECmHk5pVOj51h839Ph13pEoY8UmU=
github.com/antchfx/xpath v0.0.0-20170515025933-1f3266e77307/go.mod
h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod
h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk=
-github.com/dnephin/pflag v1.0.7/go.mod
h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE=
-github.com/elastic/go-licenser v0.4.0
h1:jLq6A5SilDS/Iz1ABRkO6BHy91B9jBora8FwGRsDqUI=
-github.com/elastic/go-licenser v0.4.0/go.mod
h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
-github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
-github.com/fatih/color v1.10.0/go.mod
h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
-github.com/fsnotify/fsnotify v1.4.9
h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
-github.com/fsnotify/fsnotify v1.4.9/go.mod
h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/google/go-cmp v0.4.0/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/davecgh/go-spew v1.1.0
h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gopacket v1.1.19
h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod
h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
-github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
-github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod
h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8=
github.com/icza/bitio v1.0.0/go.mod
h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6
h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod
h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4
h1:G2ztCwXov8mRvP0ZfjE6nAlaCX2XbykaeHdbT6KwDz0=
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod
h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs=
-github.com/jonboulle/clockwork v0.2.2
h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
-github.com/jonboulle/clockwork v0.2.2/go.mod
h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
-github.com/mattn/go-colorable v0.1.8
h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
-github.com/mattn/go-colorable v0.1.8/go.mod
h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-isatty v0.0.12
h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
-github.com/mattn/go-isatty v0.0.12/go.mod
h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pkg/errors v0.8.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod
h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48=
github.com/snksoft/crc v1.1.0/go.mod
h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A=
-github.com/spf13/pflag v1.0.3/go.mod
h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/objx v0.1.0/go.mod
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1
h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod
h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subchen/go-xmldom v1.1.2
h1:7evI2YqfYYOnuj+PBwyaOZZYjl3iWq35P6KfBUw9jeU=
github.com/subchen/go-xmldom v1.1.2/go.mod
h1:6Pg/HuX5/T4Jlj0IPJF1sRxKVoI/rrKP6LIMge9d5/8=
-github.com/tebeka/go2xunit v1.4.10
h1:0UO+9YoLpXTZ0DL9XbTmIIibgmKBGiwroo8uhFMSyR0=
-github.com/tebeka/go2xunit v1.4.10/go.mod
h1:wmc9jKT7KlU4QLU6DNTaIXNnYNOjKKNlp6mjOS0UrqY=
-github.com/yuin/goldmark v1.2.1/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.0/go.mod
h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.1/go.mod
h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/viney-shih/go-lock v1.1.1
h1:SwzDPPAiHpcwGCr5k8xD15d2gQSo8d4roRYd7TDV2eI=
+github.com/viney-shih/go-lock v1.1.1/go.mod
h1:Yijm78Ljteb3kRiJrbLAxVntkUukGu5uzSxq/xV7OO8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod
h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod
h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod
h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod
h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267
h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod
h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod
h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.1.0/go.mod
h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.7/go.mod
h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
-golang.org/x/tools v0.1.8/go.mod
h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gotest.tools/gotestsum v1.7.0 h1:RwpqwwFKBAa2h+F6pMEGpE707Edld0etUD3GhqqhDNc=
-gotest.tools/gotestsum v1.7.0/go.mod
h1:V1m4Jw3eBerhI/A6qCxUE07RnCg7ACkKj9BYcAm09V8=
-gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/plc4go/internal/plc4go/knxnetip/FieldHandler.go
b/plc4go/internal/plc4go/knxnetip/FieldHandler.go
index 5dd11db..c33f7bb 100644
--- a/plc4go/internal/plc4go/knxnetip/FieldHandler.go
+++ b/plc4go/internal/plc4go/knxnetip/FieldHandler.go
@@ -29,21 +29,6 @@ import (
"strconv"
)
-type FieldType uint8
-
-const (
- FieldCoil FieldType = 0x00
-)
-
-func (m FieldType) GetName() string {
- switch m {
- case FieldCoil:
- // TODO: these looked like copy paste from modbus before change
this name might be therefore completly wrong
- return "KNXNETIPFIELDCOIL"
- }
- return ""
-}
-
type FieldHandler struct {
groupAddress3Level *regexp.Regexp
groupAddress2Level *regexp.Regexp
diff --git a/plc4go/internal/plc4go/s7/Driver.go
b/plc4go/internal/plc4go/s7/Driver.go
index 4ab02bb..b6d24c0 100644
--- a/plc4go/internal/plc4go/s7/Driver.go
+++ b/plc4go/internal/plc4go/s7/Driver.go
@@ -47,7 +47,7 @@ func NewDriver() plc4go.PlcDriver {
func (m *Driver) GetConnection(transportUrl url.URL, transports
map[string]transports.Transport, options map[string][]string) <-chan
plc4go.PlcConnectionConnectResult {
log.Debug().Stringer("transportUrl", &transportUrl).Msgf("Get
connection for transport url with %d transport(s) and %d option(s)",
len(transports), len(options))
- // Get an the transport specified in the url
+ // Get the transport specified in the url
transport, ok := transports[transportUrl.Scheme]
if !ok {
log.Error().Stringer("transportUrl", &transportUrl).Msgf("We
couldn't find a transport for scheme %s", transportUrl.Scheme)
diff --git a/plc4go/internal/plc4go/simulated/Connection.go
b/plc4go/internal/plc4go/simulated/Connection.go
new file mode 100644
index 0000000..74d63b4
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Connection.go
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ _default "github.com/apache/plc4x/plc4go/internal/plc4go/spi/default"
+ internalModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
+ "strconv"
+ "time"
+)
+
+type Connection struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+}
+
+func NewConnection(device *Device, fieldHandler spi.PlcFieldHandler,
valueHandler spi.PlcValueHandler, options map[string][]string) *Connection {
+ connection := &Connection{
+ device: device,
+ fieldHandler: fieldHandler,
+ valueHandler: valueHandler,
+ options: options,
+ connected: false,
+ }
+ return connection
+}
+
+func (c *Connection) Connect() <-chan plc4go.PlcConnectionConnectResult {
+ ch := make(chan plc4go.PlcConnectionConnectResult)
+ go func() {
+ // Check if the connection was already connected
+ if c.connected {
+ // Return an error to the user.
+ ch <- _default.NewDefaultPlcConnectionConnectResult(c,
errors.New("already connected"))
+ }
+ if delayString, ok := c.options["connectionDelay"]; ok {
+ // This is the length of the array, not the string
+ if len(delayString) == 1 {
+ delay, err := strconv.Atoi(delayString[0])
+ if err == nil {
+ time.Sleep(time.Duration(delay) *
time.Millisecond)
+ }
+ }
+ }
+ // Mark the connection as "connected"
+ c.connected = true
+ // Return the connection in a connected state to the user.
+ ch <- _default.NewDefaultPlcConnectionConnectResult(c, nil)
+ }()
+ return ch
+}
+
+func (c *Connection) BlockingClose() {
+ <-c.Close()
+}
+
+func (c *Connection) Close() <-chan plc4go.PlcConnectionCloseResult {
+ ch := make(chan plc4go.PlcConnectionCloseResult)
+ go func() {
+ // Check if the connection is connected
+ if !c.connected {
+ // Return an error to the user.
+ ch <- _default.NewDefaultPlcConnectionCloseResult(c,
errors.New("not connected"))
+ }
+ if delayString, ok := c.options["closingDelay"]; ok {
+ // This is the length of the array, not the string
+ if len(delayString) == 1 {
+ delay, err := strconv.Atoi(delayString[0])
+ if err == nil {
+ time.Sleep(time.Duration(delay) *
time.Millisecond)
+ }
+ }
+ }
+ // Mark the connection as "disconnected"
+ c.connected = false
+ // Return a new connection to the user.
+ ch <- _default.NewDefaultPlcConnectionCloseResult(c, nil)
+ }()
+ return ch
+}
+
+func (c *Connection) IsConnected() bool {
+ return c.connected
+}
+
+func (c *Connection) Ping() <-chan plc4go.PlcConnectionPingResult {
+ ch := make(chan plc4go.PlcConnectionPingResult)
+ go func() {
+ if delayString, ok := c.options["pingDelay"]; ok {
+ // This is the length of the array, not the string
+ if len(delayString) == 1 {
+ delay, err := strconv.Atoi(delayString[0])
+ if err == nil {
+ time.Sleep(time.Duration(delay) *
time.Millisecond)
+ }
+ }
+ }
+ // Return a new connection to the user.
+ ch <- _default.NewDefaultPlcConnectionPingResult(nil)
+ }()
+ return ch
+}
+
+func (c *Connection) GetMetadata() model.PlcConnectionMetadata {
+ return _default.DefaultConnectionMetadata{
+ ConnectionAttributes: map[string]string{
+ "connectionDelay": "Delay applied when connecting",
+ "closingDelay": "Delay applied when closing the
connection",
+ "pingDelay": "Delay applied when executing a ping
operation",
+ "readDelay": "Delay applied when executing a read
operation",
+ "writeDelay": "Delay applied when executing a
write operation",
+ },
+ ProvidesReading: true,
+ ProvidesWriting: true,
+ ProvidesSubscribing: false,
+ ProvidesBrowsing: false,
+ }
+}
+
+func (c *Connection) ReadRequestBuilder() model.PlcReadRequestBuilder {
+ return internalModel.NewDefaultPlcReadRequestBuilder(c.fieldHandler,
NewReader(c.device, c.options))
+}
+
+func (c *Connection) WriteRequestBuilder() model.PlcWriteRequestBuilder {
+ return internalModel.NewDefaultPlcWriteRequestBuilder(c.fieldHandler,
c.valueHandler, NewWriter(c.device, c.options))
+}
+
+func (c *Connection) SubscriptionRequestBuilder()
model.PlcSubscriptionRequestBuilder {
+ panic("not implemented")
+}
+
+func (c *Connection) UnsubscriptionRequestBuilder()
model.PlcUnsubscriptionRequestBuilder {
+ panic("not implemented")
+}
+
+func (c *Connection) BrowseRequestBuilder() model.PlcBrowseRequestBuilder {
+ panic("not implemented")
+}
diff --git a/plc4go/internal/plc4go/simulated/Connection_test.go
b/plc4go/internal/plc4go/simulated/Connection_test.go
new file mode 100644
index 0000000..6a74b56
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Connection_test.go
@@ -0,0 +1,758 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+ _default "github.com/apache/plc4x/plc4go/internal/plc4go/spi/default"
+ internalModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestConnection_Connect(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want plc4go.PlcConnectionConnectResult
+ delayAtLeast time.Duration
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: false,
+ },
+ want:
_default.NewDefaultPlcConnectionConnectResult(&Connection{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ }, nil),
+ delayAtLeast: 0,
+ wantErr: false,
+ },
+ // If the connection was already connected, the
+ // connection should fail with an error.
+ {
+ name: "already connected",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ want:
_default.NewDefaultPlcConnectionConnectResult(&Connection{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ }, nil),
+ delayAtLeast: 0,
+ wantErr: true,
+ },
+ // If the connection should simulate a delay, make sure it
doesn't
+ // return immediately.
+ {
+ name: "delayed connected",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{
+ "connectionDelay": {"1000"},
+ },
+ connected: false,
+ },
+ want:
_default.NewDefaultPlcConnectionConnectResult(&Connection{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{
+ "connectionDelay": {"1000"},
+ },
+ connected: true,
+ }, nil),
+ delayAtLeast: time.Second * 1,
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ timeBeforeConnect := time.Now()
+ connectionChan := c.Connect()
+ select {
+ case connectResult := <-connectionChan:
+ timeAfterConnect := time.Now()
+ // If an expected delay was defined, check if
connecting
+ // took at least this long.
+ if tt.delayAtLeast > 0 {
+ connectionTime :=
timeAfterConnect.Sub(timeBeforeConnect)
+ if connectionTime < tt.delayAtLeast {
+
t.Errorf("TestConnection.Connect() connected too fast. Expected at least %v but
connected after %v", tt.delayAtLeast, connectionTime)
+ }
+ }
+ // If we wanted an error, but didn't get one or
the other way around.
+ if tt.wantErr != (connectResult.GetErr() !=
nil) {
+ t.Errorf("TestConnection.Connect()
hasErr= %v, wantErr %v", connectResult.GetErr() != nil, tt.wantErr)
+ } else if !tt.wantErr {
+ // Check if we're connected.
+ if !reflect.DeepEqual(connectResult,
tt.want) {
+
t.Errorf("TestConnection.Connect() = %v, want %v", connectResult, tt.want)
+ }
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("TestConnection.Connect() got timeout")
+ }
+ })
+ }
+}
+
+func TestConnection_Close(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want plc4go.PlcConnectionCloseResult
+ delayAtLeast time.Duration
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ want:
_default.NewDefaultPlcConnectionCloseResult(&Connection{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: false,
+ }, nil),
+ delayAtLeast: 0,
+ wantErr: false,
+ },
+ {
+ name: "not connected",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: false,
+ },
+ want:
_default.NewDefaultPlcConnectionCloseResult(&Connection{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: false,
+ }, nil),
+ delayAtLeast: 0,
+ wantErr: true,
+ },
+ {
+ name: "delayed close",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{
+ "closingDelay": {"1000"},
+ },
+ connected: true,
+ },
+ want:
_default.NewDefaultPlcConnectionCloseResult(&Connection{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{
+ "closingDelay": {"1000"},
+ },
+ connected: false,
+ }, nil),
+ delayAtLeast: 1000,
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ timeBeforeClose := time.Now()
+ closeChan := c.Close()
+ select {
+ case closeResult := <-closeChan:
+ timeAfterClose := time.Now()
+ // If an expected delay was defined, check if
closing
+ // took at least this long.
+ if tt.delayAtLeast > 0 {
+ connectionTime :=
timeAfterClose.Sub(timeBeforeClose)
+ if connectionTime < tt.delayAtLeast {
+
t.Errorf("TestConnection.Close() connected too fast. Expected at least %v but
connected after %v", tt.delayAtLeast, connectionTime)
+ }
+ }
+ // If we wanted an error, but didn't get one or
the other way around.
+ if tt.wantErr != (closeResult.GetErr() != nil) {
+ t.Errorf("TestConnection.Close()
hasErr= %v, wantErr %v", closeResult.GetErr() != nil, tt.wantErr)
+ } else if !tt.wantErr {
+ if !reflect.DeepEqual(closeResult,
tt.want) {
+
t.Errorf("TestConnection.Close() = %v, want %v", closeResult, tt.want)
+ }
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("TestConnection.Close() got timeout")
+ }
+ })
+ }
+}
+
+func TestConnection_BlockingClose(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ delayAtLeast time.Duration
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ delayAtLeast: 0,
+ },
+ {
+ name: "not connected",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: false,
+ },
+ delayAtLeast: 0,
+ },
+ {
+ name: "delayed close",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{
+ "closingDelay": {"1000"},
+ },
+ connected: true,
+ },
+ delayAtLeast: 1000,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ timeBeforeClose := time.Now()
+ executor := func() <-chan bool {
+ ch := make(chan bool)
+ go func() {
+ c.BlockingClose()
+ ch <- true
+ }()
+ return ch
+ }
+ select {
+ case <-executor():
+ timeAfterClose := time.Now()
+ // If an expected delay was defined, check if
closing
+ // took at least this long.
+ if tt.delayAtLeast > 0 {
+ connectionTime :=
timeAfterClose.Sub(timeBeforeClose)
+ if connectionTime < tt.delayAtLeast {
+
t.Errorf("TestConnection.Close() connected too fast. Expected at least %v but
connected after %v", tt.delayAtLeast, connectionTime)
+ }
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("TestConnection.Close() got timeout")
+ }
+ })
+ }
+}
+
+func TestConnection_GetMetadata(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want model.PlcConnectionMetadata
+ }{
+ {
+ name: "simple",
+ fields: fields{},
+ want: _default.DefaultConnectionMetadata{
+ ConnectionAttributes: map[string]string{
+ "connectionDelay": "Delay applied when
connecting",
+ "closingDelay": "Delay applied when
closing the connection",
+ "pingDelay": "Delay applied when
executing a ping operation",
+ "readDelay": "Delay applied when
executing a read operation",
+ "writeDelay": "Delay applied when
executing a write operation",
+ },
+ ProvidesReading: true,
+ ProvidesWriting: true,
+ ProvidesSubscribing: false,
+ ProvidesBrowsing: false,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ if got := c.GetMetadata(); !reflect.DeepEqual(got,
tt.want) {
+ t.Errorf("GetMetadata() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func TestConnection_IsConnected(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ want: true,
+ },
+ {
+ name: "not connected",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: false,
+ },
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ if got := c.IsConnected(); got != tt.want {
+ t.Errorf("IsConnected() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func TestConnection_Ping(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want plc4go.PlcConnectionPingResult
+ delayAtLeast time.Duration
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ want:
_default.NewDefaultPlcConnectionPingResult(nil),
+ delayAtLeast: 0,
+ },
+ {
+ name: "delayed ping",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{
+ "pingDelay": {"1000"},
+ },
+ connected: true,
+ },
+ want:
_default.NewDefaultPlcConnectionPingResult(nil),
+ delayAtLeast: 1000,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ timeBeforePing := time.Now()
+ pingChan := c.Ping()
+ select {
+ case pingResult := <-pingChan:
+ timeAfterPing := time.Now()
+ // If an expected delay was defined, check if
closing
+ // took at least this long.
+ if tt.delayAtLeast > 0 {
+ pingTime :=
timeAfterPing.Sub(timeBeforePing)
+ if pingTime < tt.delayAtLeast {
+ t.Errorf("TestConnection.Ping()
completed too fast. Expected at least %v but returned after %v",
tt.delayAtLeast, pingTime)
+ }
+ }
+ if !reflect.DeepEqual(pingResult, tt.want) {
+ t.Errorf("TestConnection.Ping() = %v,
want %v", pingResult, tt.want)
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("TestConnection.Ping() got timeout")
+ }
+ })
+ }
+}
+
+func TestConnection_BrowseRequestBuilder(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ defer func() {
+ if r := recover(); tt.wantErr && r == nil {
+ t.Errorf("The code did not panic")
+ }
+ }()
+ c.BrowseRequestBuilder()
+ })
+ }
+}
+
+func TestConnection_ReadRequestBuilder(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want model.PlcReadRequestBuilder
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ want:
internalModel.NewDefaultPlcReadRequestBuilder(NewFieldHandler(),
NewReader(NewDevice("hurz"), map[string][]string{})),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ if got := c.ReadRequestBuilder();
!reflect.DeepEqual(got, tt.want) {
+ t.Errorf("ReadRequestBuilder() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestConnection_SubscriptionRequestBuilder(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ defer func() {
+ if r := recover(); tt.wantErr && r == nil {
+ t.Errorf("The code did not panic")
+ }
+ }()
+ c.SubscriptionRequestBuilder()
+ })
+ }
+}
+
+func TestConnection_UnsubscriptionRequestBuilder(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ defer func() {
+ if r := recover(); tt.wantErr && r == nil {
+ t.Errorf("The code did not panic")
+ }
+ }()
+ c.UnsubscriptionRequestBuilder()
+ })
+ }
+}
+
+func TestConnection_WriteRequestBuilder(t *testing.T) {
+ type fields struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ connected bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want model.PlcWriteRequestBuilder
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ device: NewDevice("hurz"),
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ options: map[string][]string{},
+ connected: true,
+ },
+ want:
internalModel.NewDefaultPlcWriteRequestBuilder(NewFieldHandler(),
NewValueHandler(), NewWriter(NewDevice("hurz"), map[string][]string{})),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c := &Connection{
+ device: tt.fields.device,
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ options: tt.fields.options,
+ connected: tt.fields.connected,
+ }
+ if got := c.WriteRequestBuilder();
!reflect.DeepEqual(got, tt.want) {
+ t.Errorf("WriteRequestBuilder() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestNewConnection(t *testing.T) {
+ type args struct {
+ device *Device
+ fieldHandler spi.PlcFieldHandler
+ valueHandler spi.PlcValueHandler
+ options map[string][]string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *Connection
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := NewConnection(tt.args.device,
tt.args.fieldHandler, tt.args.valueHandler, tt.args.options);
!reflect.DeepEqual(got, tt.want) {
+ t.Errorf("NewConnection() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/simulated/Device.go
b/plc4go/internal/plc4go/simulated/Device.go
new file mode 100644
index 0000000..53d5a30
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Device.go
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+ "github.com/rs/zerolog/log"
+ "math/rand"
+)
+
+type Device struct {
+ Name string
+ State map[SimulatedField]*values.PlcValue
+}
+
+func NewDevice(name string) *Device {
+ return &Device{
+ Name: name,
+ State: make(map[SimulatedField]*values.PlcValue),
+ }
+}
+
+func (t *Device) Get(field SimulatedField) *values.PlcValue {
+ switch field.FieldType {
+ case FieldState:
+ return t.State[field]
+ case FieldRandom:
+ return t.getRandomValue(field)
+ }
+ return nil
+}
+
+func (t *Device) Set(field SimulatedField, value *values.PlcValue) {
+ switch field.FieldType {
+ case FieldState:
+ t.State[field] = value
+ break
+ case FieldRandom:
+ // TODO: Doesn't really make any sense to write a random
+ break
+ case FieldStdOut:
+ log.Debug().Msgf("TEST PLC STDOUT [%s]: %s", field.Name,
(*value).GetString())
+ break
+ }
+}
+
+func (t *Device) getRandomValue(field SimulatedField) *values.PlcValue {
+ size := field.GetDataTypeSize().DataTypeSize()
+ data := make([]byte, uint16(size)*field.Quantity)
+ rand.Read(data)
+ readBuffer := utils.NewReadBufferByteBased(data)
+ plcValue, err := model.DataItemParse(readBuffer,
field.DataTypeSize.String(), field.Quantity)
+ if err != nil {
+ panic("Unable to parse random bytes")
+ }
+ return &plcValue
+}
diff --git a/plc4go/internal/plc4go/simulated/Device_test.go
b/plc4go/internal/plc4go/simulated/Device_test.go
new file mode 100644
index 0000000..870ece0
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Device_test.go
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ values2 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+ "github.com/rs/zerolog/log"
+ "reflect"
+ "testing"
+)
+
+func TestDevice_Get(t1 *testing.T) {
+ type fields struct {
+ Name string
+ State map[SimulatedField]*values.PlcValue
+ }
+ type args struct {
+ field SimulatedField
+ verifyOutput bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want *values.PlcValue
+ }{
+ {
+ name: "simple state",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ args: args{
+ field: NewSimulatedField(FieldState,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ verifyOutput: true,
+ },
+ want: ToReference(values2.NewPlcBOOL(true)),
+ },
+ {
+ name: "simple random",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{},
+ },
+ args: args{
+ field: NewSimulatedField(FieldRandom,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ verifyOutput: false,
+ },
+ want: ToReference(values2.NewPlcBOOL(true)),
+ },
+ {
+ name: "simple stdout",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{},
+ },
+ args: args{
+ field: NewSimulatedField(FieldStdOut,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ verifyOutput: false,
+ },
+ want: nil,
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := &Device{
+ Name: tt.fields.Name,
+ State: tt.fields.State,
+ }
+ got := t.Get(tt.args.field)
+ if got != nil {
+ log.Debug().Msgf("Result: %v", *got)
+ } else {
+ log.Debug().Msg("Result: nil")
+ }
+ if tt.args.verifyOutput && !reflect.DeepEqual(got,
tt.want) {
+ t1.Errorf("Get() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+/*
+ * When first playing around with random values I only got "false" values.
+ * So I added this test in order to verify I'm actually getting random values.
+ */
+func TestDevice_Random(t1 *testing.T) {
+ type fields struct {
+ Name string
+ State map[SimulatedField]*values.PlcValue
+ }
+ type args struct {
+ field SimulatedField
+ numRuns int
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want *values.PlcValue
+ }{
+ {
+ name: "simple random",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{},
+ },
+ args: args{
+ field: NewSimulatedField(FieldRandom,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ numRuns: 1000,
+ },
+ want: ToReference(values2.NewPlcBOOL(true)),
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := &Device{
+ Name: tt.fields.Name,
+ State: tt.fields.State,
+ }
+ numTrue := 0
+ numFalse := 0
+ for i := 0; i < tt.args.numRuns; i++ {
+ got := t.Get(tt.args.field)
+ boolValue := (*got).GetBool()
+ if boolValue {
+ numTrue++
+ } else {
+ numFalse++
+ }
+ }
+ if numTrue == 0 || numFalse == 0 {
+ t1.Errorf("Random doesn't seem to work. In %d
runs I got %d true and %d false values", tt.args.numRuns, numTrue, numFalse)
+ } else {
+ log.Info().Msgf("In %d runs I got %d true and
%d false values", tt.args.numRuns, numTrue, numFalse)
+ }
+ })
+ }
+}
+
+func TestDevice_Set(t1 *testing.T) {
+ type fields struct {
+ Name string
+ State map[SimulatedField]*values.PlcValue
+ }
+ type args struct {
+ field SimulatedField
+ value *values.PlcValue
+ shouldBeSaved bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ }{
+ {
+ name: "simple state",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{},
+ },
+ args: args{
+ field: NewSimulatedField(FieldState,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ value:
ToReference(values2.NewPlcBOOL(true)),
+ shouldBeSaved: true,
+ },
+ },
+ {
+ name: "simple random",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{},
+ },
+ args: args{
+ field: NewSimulatedField(FieldRandom,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ value:
ToReference(values2.NewPlcBOOL(true)),
+ shouldBeSaved: false,
+ },
+ },
+ {
+ name: "simple stdout",
+ fields: fields{
+ Name: "hurz",
+ State: map[SimulatedField]*values.PlcValue{},
+ },
+ args: args{
+ field: NewSimulatedField(FieldStdOut,
"boolField", model.SimulatedDataTypeSizes_BOOL, 1),
+ value:
ToReference(values2.NewPlcBOOL(true)),
+ shouldBeSaved: false,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := &Device{
+ Name: tt.fields.Name,
+ State: tt.fields.State,
+ }
+ // It shouldn't exist in the map before
+ if _, ok := tt.fields.State[tt.args.field]; ok {
+ t1.Errorf("Value for %v already present in
map", tt.args.field)
+ }
+ t.Set(tt.args.field, tt.args.value)
+ // It should exist in the map after
+ if _, ok := tt.fields.State[tt.args.field];
tt.args.shouldBeSaved != ok {
+ if tt.args.shouldBeSaved {
+ t1.Errorf("Value for %v not present in
map (it should)", tt.args.field)
+ } else {
+ t1.Errorf("Value for %v present in map
(is should not)", tt.args.field)
+ }
+ }
+ })
+ }
+}
+
+func TestDevice_getRandomValue(t1 *testing.T) {
+ type fields struct {
+ Name string
+ State map[SimulatedField]*values.PlcValue
+ }
+ type args struct {
+ field SimulatedField
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want *values.PlcValue
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := &Device{
+ Name: tt.fields.Name,
+ State: tt.fields.State,
+ }
+ if got := t.getRandomValue(tt.args.field);
!reflect.DeepEqual(got, tt.want) {
+ t1.Errorf("getRandomValue() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestNewDevice(t *testing.T) {
+ type args struct {
+ name string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *Device
+ }{
+ // TODO: Add test cases.
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := NewDevice(tt.args.name);
!reflect.DeepEqual(got, tt.want) {
+ t.Errorf("NewDevice() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func ToReference(value values.PlcValue) *values.PlcValue {
+ return &value
+}
diff --git a/plc4go/internal/plc4go/simulated/Driver.go
b/plc4go/internal/plc4go/simulated/Driver.go
new file mode 100644
index 0000000..80cf225
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Driver.go
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/options"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/pkg/errors"
+ "net/url"
+)
+
+type Driver struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+}
+
+func NewDriver() plc4go.PlcDriver {
+ return &Driver{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ }
+}
+
+// GetProtocolCode Get the short code used to identify this driver (As used in
the connection string)
+func (d *Driver) GetProtocolCode() string {
+ return "simulated"
+}
+
+// GetProtocolName Get a human-readable name for this driver
+func (d *Driver) GetProtocolName() string {
+ return "Simulated PLC4X Datasource"
+}
+
+// GetDefaultTransport If the driver has a default form of transport, provide
this and make
+// providing the transport code optional in the connection string
+func (d *Driver) GetDefaultTransport() string {
+ return "none"
+}
+
+// CheckQuery Have the driver parse the query string and provide feedback if
it's not a valid one
+func (d *Driver) CheckQuery(query string) error {
+ _, err := d.fieldHandler.ParseQuery(query)
+ return err
+}
+
+// GetConnection Establishes a connection to a given PLC using the information
in the connectionString
+func (d *Driver) GetConnection(_ url.URL, _ map[string]transports.Transport,
options map[string][]string) <-chan plc4go.PlcConnectionConnectResult {
+ connection := NewConnection(NewDevice("test"), d.fieldHandler,
d.valueHandler, options)
+ return connection.Connect()
+}
+
+// SupportsDiscovery returns true if this driver supports discovery
+// TODO: Actually the connection could support discovery to list up all fields
in the Device
+func (d *Driver) SupportsDiscovery() bool {
+ return false
+}
+
+func (d *Driver) Discover(_ func(event model.PlcDiscoveryEvent), _
...options.WithDiscoveryOption) error {
+ return errors.New("unsupported operation")
+}
diff --git a/plc4go/internal/plc4go/simulated/Driver_test.go
b/plc4go/internal/plc4go/simulated/Driver_test.go
new file mode 100644
index 0000000..ac92235
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Driver_test.go
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/options"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "net/url"
+ "testing"
+ "time"
+)
+
+func TestDriver_CheckQuery(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ type args struct {
+ query string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ wantErr bool
+ }{
+ {
+ name: "valid query",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ args: args{
+ query: "STATE/test:UINT[2]",
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ if err := d.CheckQuery(tt.args.query); (err != nil) !=
tt.wantErr {
+ t.Errorf("CheckQuery() error = %v, wantErr %v",
err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestDriver_Discover(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ type args struct {
+ callback func(event model.PlcDiscoveryEvent)
+ discoveryOptions []options.WithDiscoveryOption
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ wantErr bool
+ }{
+ {
+ name: "discovery fails",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ args: args{
+ // Can all be nil, as the call is expected to
fail
+ callback: nil,
+ discoveryOptions: nil,
+ },
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ if err := d.Discover(tt.args.callback,
tt.args.discoveryOptions...); (err != nil) != tt.wantErr {
+ t.Errorf("Discover() error = %v, wantErr %v",
err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestDriver_GetConnection(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ type args struct {
+ in0 url.URL
+ in1 map[string]transports.Transport
+ options map[string][]string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ wantErr bool
+ }{
+ {
+ name: "simple no options",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ // Input doesn't really matter, as the code simply
ignores most of it.
+ args: args{
+ in0: url.URL{},
+ in1: nil,
+ options: nil,
+ },
+ wantErr: false,
+ },
+ {
+ name: "simple with options",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ // Input doesn't really matter, as the code simply
ignores most of it.
+ args: args{
+ in0: url.URL{},
+ in1: nil,
+ options: map[string][]string{
+ "testOption": {"testValue"},
+ },
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ connectionChan := d.GetConnection(tt.args.in0,
tt.args.in1, tt.args.options)
+ select {
+ case connectResult := <-connectionChan:
+ if tt.wantErr && (connectResult.GetErr() ==
nil) {
+
t.Errorf("PlcConnectionPool.GetConnection() = %v, wantErr %v",
connectResult.GetErr(), tt.wantErr)
+ } else if connectResult.GetErr() != nil {
+
t.Errorf("PlcConnectionPool.GetConnection() error = %v, wantErr %v",
connectResult.GetErr(), tt.wantErr)
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("PlcConnectionPool.GetConnection() got
timeout")
+ }
+ })
+ }
+}
+
+func TestDriver_GetDefaultTransport(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ want: "none",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ if got := d.GetDefaultTransport(); got != tt.want {
+ t.Errorf("GetDefaultTransport() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestDriver_GetProtocolCode(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ want: "simulated",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ if got := d.GetProtocolCode(); got != tt.want {
+ t.Errorf("GetProtocolCode() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestDriver_GetProtocolName(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ want: "Simulated PLC4X Datasource",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ if got := d.GetProtocolName(); got != tt.want {
+ t.Errorf("GetProtocolName() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestDriver_SupportsDiscovery(t *testing.T) {
+ type fields struct {
+ fieldHandler FieldHandler
+ valueHandler ValueHandler
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want bool
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ fieldHandler: NewFieldHandler(),
+ valueHandler: NewValueHandler(),
+ },
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ d := &Driver{
+ fieldHandler: tt.fields.fieldHandler,
+ valueHandler: tt.fields.valueHandler,
+ }
+ if got := d.SupportsDiscovery(); got != tt.want {
+ t.Errorf("SupportsDiscovery() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestNewDriver(t *testing.T) {
+ tests := []struct {
+ name string
+ wantErr bool
+ }{
+ {
+ name: "simple",
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := NewDriver()
+ if got == nil && !tt.wantErr {
+ t.Errorf("NewDriver() error creating")
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/simulated/Field.go
b/plc4go/internal/plc4go/simulated/Field.go
new file mode 100644
index 0000000..133da1c
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Field.go
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "fmt"
+
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+)
+
+type Field interface {
+ GetFieldType() *FieldType
+ GetName() string
+ GetDataTypeSize() *model.SimulatedDataTypeSizes
+}
+
+type SimulatedField struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+}
+
+func NewSimulatedField(fieldType FieldType, name string, dataTypeSize
model.SimulatedDataTypeSizes, quantity uint16) SimulatedField {
+ return SimulatedField{
+ FieldType: fieldType,
+ Name: name,
+ DataTypeSize: dataTypeSize,
+ Quantity: quantity,
+ }
+}
+
+func (t SimulatedField) GetFieldType() FieldType {
+ return t.FieldType
+}
+
+func (t SimulatedField) GetName() string {
+ return t.Name
+}
+
+func (t SimulatedField) GetDataTypeSize() model.SimulatedDataTypeSizes {
+ return t.DataTypeSize
+}
+
+func (t SimulatedField) GetAddressString() string {
+ return fmt.Sprintf("%s/%s:%s[%d]", t.FieldType.Name(), t.Name,
t.DataTypeSize.String(), t.Quantity)
+}
+
+func (t SimulatedField) GetTypeName() string {
+ return t.DataTypeSize.String()
+}
+
+func (t SimulatedField) GetQuantity() uint16 {
+ return t.Quantity
+}
diff --git a/plc4go/internal/plc4go/simulated/FieldHandler.go
b/plc4go/internal/plc4go/simulated/FieldHandler.go
new file mode 100644
index 0000000..de0bfd3
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/FieldHandler.go
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "errors"
+
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+ apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "regexp"
+ "strconv"
+)
+
+type FieldType uint8
+
+const (
+ FieldRandom FieldType = iota
+ FieldState
+ FieldStdOut
+)
+
+func (e FieldType) Name() string {
+ switch e {
+ case FieldRandom:
+ return "RANDOM"
+ case FieldState:
+ return "STATE"
+ case FieldStdOut:
+ return "STDOUT"
+ default:
+ return "UNKNOWN"
+ }
+}
+
+type FieldHandler struct {
+ simulatedQuery *regexp.Regexp
+}
+
+func NewFieldHandler() FieldHandler {
+ return FieldHandler{
+ simulatedQuery:
regexp.MustCompile(`^(?P<type>\w+)/(?P<name>[a-zA-Z0-9_\\.]+):(?P<dataType>[a-zA-Z0-9]+)(\[(?P<numElements>\d+)])?$`),
+ }
+}
+
+func (m FieldHandler) ParseQuery(query string) (apiModel.PlcField, error) {
+ if match := utils.GetSubgroupMatches(m.simulatedQuery, query); match !=
nil {
+ fieldTypeName, ok := match["type"]
+ var fieldType FieldType
+ if ok {
+ switch fieldTypeName {
+ case "RANDOM":
+ fieldType = FieldRandom
+ break
+ case "STATE":
+ fieldType = FieldState
+ break
+ case "STDOUT":
+ fieldType = FieldStdOut
+ default:
+ return nil, errors.New("unknown field type '" +
fieldTypeName + "'")
+ }
+ }
+ fieldName, ok := match["name"]
+ fieldDataTypeName, ok := match["dataType"]
+ var fieldDataType model.SimulatedDataTypeSizes
+ if ok {
+ fieldDataType =
model.SimulatedDataTypeSizesByName(fieldDataTypeName)
+ if fieldDataType == 0 {
+ return nil, errors.New("unknown field data-type
'" + fieldDataTypeName + "'")
+ }
+ }
+ fieldNumElementsText, ok := match["numElements"]
+ var fieldNumElements uint16
+ if ok && len(fieldNumElementsText) > 0 {
+ num, err := strconv.Atoi(fieldNumElementsText)
+ if err != nil {
+ return nil, errors.New("invalid size '" +
fieldNumElementsText + "'")
+ }
+ fieldNumElements = uint16(num)
+ } else {
+ fieldNumElements = 1
+ }
+ return NewSimulatedField(fieldType, fieldName, fieldDataType,
fieldNumElements), nil
+ }
+ return nil, errors.New("Invalid address format for address '" + query +
"'")
+}
diff --git a/plc4go/internal/plc4go/simulated/FieldHandler_test.go
b/plc4go/internal/plc4go/simulated/FieldHandler_test.go
new file mode 100644
index 0000000..281147a
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/FieldHandler_test.go
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ model2
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "reflect"
+ "testing"
+)
+
+func TestFieldHandler_ParseQuery(t *testing.T) {
+ type args struct {
+ query string
+ }
+ tests := []struct {
+ name string
+ args args
+ want model.PlcField
+ wantErr bool
+ }{
+ {
+ name: "simple random",
+ args: args{
+ query: "RANDOM/test_random:BOOL",
+ },
+ want: NewSimulatedField(FieldRandom, "test_random",
model2.SimulatedDataTypeSizes_BOOL, 1),
+ wantErr: false,
+ },
+ {
+ name: "simple random array",
+ args: args{
+ query: "RANDOM/test_random:BOOL[10]",
+ },
+ want: NewSimulatedField(FieldRandom, "test_random",
model2.SimulatedDataTypeSizes_BOOL, 10),
+ wantErr: false,
+ },
+ {
+ name: "simple state",
+ args: args{
+ query: "STATE/test_state:BOOL",
+ },
+ want: NewSimulatedField(FieldState, "test_state",
model2.SimulatedDataTypeSizes_BOOL, 1),
+ wantErr: false,
+ },
+ {
+ name: "simple state array",
+ args: args{
+ query: "STATE/test_state:BOOL[42]",
+ },
+ want: NewSimulatedField(FieldState, "test_state",
model2.SimulatedDataTypeSizes_BOOL, 42),
+ wantErr: false,
+ },
+ {
+ name: "simple stdout",
+ args: args{
+ query: "STDOUT/test_stdout:BOOL",
+ },
+ want: NewSimulatedField(FieldStdOut, "test_stdout",
model2.SimulatedDataTypeSizes_BOOL, 1),
+ wantErr: false,
+ },
+ {
+ name: "simple stdout array",
+ args: args{
+ query: "STDOUT/test_stdout:BOOL[23]",
+ },
+ want: NewSimulatedField(FieldStdOut, "test_stdout",
model2.SimulatedDataTypeSizes_BOOL, 23),
+ wantErr: false,
+ },
+ {
+ name: "error invalid type",
+ args: args{
+ query: "HURZ/test_stdout:BOOL[23]",
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "error invalid name format",
+ args: args{
+ query: "RANDOM/test/stdout:BOOL[23]",
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "error invalid datatype",
+ args: args{
+ query: "RANDOM/test_stdout:HURZ[23]",
+ },
+ want: nil,
+ wantErr: true,
+ },
+ {
+ name: "error invalid array size",
+ args: args{
+ query:
"RANDOM/test_stdout:BOOL[999999999999999999999999999999999999]",
+ },
+ want: nil,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ m := NewFieldHandler()
+ got, err := m.ParseQuery(tt.args.query)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ParseQuery() error = %v, wantErr %v",
err, tt.wantErr)
+ return
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("ParseQuery() got = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func TestFieldType_Name(t *testing.T) {
+ tests := []struct {
+ name string
+ e FieldType
+ want string
+ }{
+ {
+ name: "simple random",
+ e: FieldRandom,
+ want: "RANDOM",
+ },
+ {
+ name: "simple state",
+ e: FieldState,
+ want: "STATE",
+ },
+ {
+ name: "simple stdout",
+ e: FieldStdOut,
+ want: "STDOUT",
+ },
+ {
+ name: "simple stdout",
+ e: 10,
+ want: "UNKNOWN",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := tt.e.Name(); got != tt.want {
+ t.Errorf("Name() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/simulated/Field_test.go
b/plc4go/internal/plc4go/simulated/Field_test.go
new file mode 100644
index 0000000..c4a961a
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Field_test.go
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ "reflect"
+ "testing"
+)
+
+func TestNewSimulatedField(t *testing.T) {
+ type args struct {
+ fieldType FieldType
+ name string
+ dataTypeSize model.SimulatedDataTypeSizes
+ quantity uint16
+ }
+ tests := []struct {
+ name string
+ args args
+ want SimulatedField
+ }{
+ {
+ name: "simple",
+ args: args{
+ fieldType: FieldRandom,
+ name: "test",
+ dataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ quantity: 1,
+ },
+ want: SimulatedField{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := NewSimulatedField(tt.args.fieldType,
tt.args.name, tt.args.dataTypeSize, tt.args.quantity); !reflect.DeepEqual(got,
tt.want) {
+ t.Errorf("NewSimulatedField() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestSimulatedField_GetAddressString(t1 *testing.T) {
+ type fields struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ want: "RANDOM/test:BOOL[1]",
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := SimulatedField{
+ FieldType: tt.fields.FieldType,
+ Name: tt.fields.Name,
+ DataTypeSize: tt.fields.DataTypeSize,
+ Quantity: tt.fields.Quantity,
+ }
+ if got := t.GetAddressString(); got != tt.want {
+ t1.Errorf("GetAddressString() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestSimulatedField_GetDataTypeSize(t1 *testing.T) {
+ type fields struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want model.SimulatedDataTypeSizes
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ want: model.SimulatedDataTypeSizes_BOOL,
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := SimulatedField{
+ FieldType: tt.fields.FieldType,
+ Name: tt.fields.Name,
+ DataTypeSize: tt.fields.DataTypeSize,
+ Quantity: tt.fields.Quantity,
+ }
+ if got := t.GetDataTypeSize(); got != tt.want {
+ t1.Errorf("GetDataTypeSize() = %v, want %v",
got, tt.want)
+ }
+ })
+ }
+}
+
+func TestSimulatedField_GetFieldType(t1 *testing.T) {
+ type fields struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want FieldType
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ want: FieldRandom,
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := SimulatedField{
+ FieldType: tt.fields.FieldType,
+ Name: tt.fields.Name,
+ DataTypeSize: tt.fields.DataTypeSize,
+ Quantity: tt.fields.Quantity,
+ }
+ if got := t.GetFieldType(); got != tt.want {
+ t1.Errorf("GetFieldType() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func TestSimulatedField_GetName(t1 *testing.T) {
+ type fields struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ want: "test",
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := SimulatedField{
+ FieldType: tt.fields.FieldType,
+ Name: tt.fields.Name,
+ DataTypeSize: tt.fields.DataTypeSize,
+ Quantity: tt.fields.Quantity,
+ }
+ if got := t.GetName(); got != tt.want {
+ t1.Errorf("GetName() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func TestSimulatedField_GetQuantity(t1 *testing.T) {
+ type fields struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want uint16
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ want: 1,
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := SimulatedField{
+ FieldType: tt.fields.FieldType,
+ Name: tt.fields.Name,
+ DataTypeSize: tt.fields.DataTypeSize,
+ Quantity: tt.fields.Quantity,
+ }
+ if got := t.GetQuantity(); got != tt.want {
+ t1.Errorf("GetQuantity() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
+
+func TestSimulatedField_GetTypeName(t1 *testing.T) {
+ type fields struct {
+ FieldType FieldType
+ Name string
+ DataTypeSize model.SimulatedDataTypeSizes
+ Quantity uint16
+ }
+ tests := []struct {
+ name string
+ fields fields
+ want string
+ }{
+ {
+ name: "simple",
+ fields: fields{
+ FieldType: FieldRandom,
+ Name: "test",
+ DataTypeSize: model.SimulatedDataTypeSizes_BOOL,
+ Quantity: 1,
+ },
+ want: "BOOL",
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := SimulatedField{
+ FieldType: tt.fields.FieldType,
+ Name: tt.fields.Name,
+ DataTypeSize: tt.fields.DataTypeSize,
+ Quantity: tt.fields.Quantity,
+ }
+ if got := t.GetTypeName(); got != tt.want {
+ t1.Errorf("GetTypeName() = %v, want %v", got,
tt.want)
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/simulated/Reader.go
b/plc4go/internal/plc4go/simulated/Reader.go
new file mode 100644
index 0000000..48d7ab0
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Reader.go
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ model2 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+ "strconv"
+ "time"
+)
+
+type Reader struct {
+ device *Device
+ options map[string][]string
+}
+
+func NewReader(device *Device, options map[string][]string) Reader {
+ return Reader{
+ device: device,
+ options: options,
+ }
+}
+
+func (r Reader) Read(readRequest model.PlcReadRequest) <-chan
model.PlcReadRequestResult {
+ ch := make(chan model.PlcReadRequestResult)
+ go func() {
+ // Possibly add a delay.
+ if delayString, ok := r.options["readDelay"]; ok {
+ if len(delayString) == 1 {
+ delay, err := strconv.Atoi(delayString[0])
+ if err == nil {
+ time.Sleep(time.Duration(delay) *
time.Millisecond)
+ }
+ }
+ }
+
+ // Process the request
+ responseCodes := make(map[string]model.PlcResponseCode)
+ responseValues := make(map[string]values.PlcValue)
+ for _, fieldName := range readRequest.GetFieldNames() {
+ field := readRequest.GetField(fieldName)
+ simulatedField, ok := field.(SimulatedField)
+ if !ok {
+ responseCodes[fieldName] =
model.PlcResponseCode_INVALID_ADDRESS
+ responseValues[fieldName] = nil
+ } else {
+ value := r.device.Get(simulatedField)
+ if value == nil {
+ responseCodes[fieldName] =
model.PlcResponseCode_NOT_FOUND
+ responseValues[fieldName] = nil
+ } else {
+ responseCodes[fieldName] =
model.PlcResponseCode_OK
+ responseValues[fieldName] = *value
+ }
+ }
+ }
+
+ // Emit the response
+ ch <- &model2.DefaultPlcReadRequestResult{
+ Request: readRequest,
+ Response: model2.NewDefaultPlcReadResponse(readRequest,
responseCodes, responseValues),
+ Err: nil,
+ }
+ }()
+ return ch
+}
diff --git a/plc4go/internal/plc4go/simulated/Reader_test.go
b/plc4go/internal/plc4go/simulated/Reader_test.go
new file mode 100644
index 0000000..1a84e63
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Reader_test.go
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/s7"
+ model4
"github.com/apache/plc4x/plc4go/internal/plc4go/s7/readwrite/model"
+ model2
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ model3 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ values2 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestReader_Read(t *testing.T) {
+ type fields struct {
+ device *Device
+ options map[string][]string
+ }
+ type args struct {
+ fields map[string]model.PlcField
+ fieldNames []string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want model.PlcReadResponse
+ delayAtLeast time.Duration
+ }{
+ {
+ name: "simple state",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{},
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcReadResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test": model.PlcResponseCode_OK,
+ },
+ map[string]values.PlcValue{
+ "test": values2.NewPlcBOOL(true),
+ }),
+ delayAtLeast: 0,
+ },
+ {
+ name: "simple state delayed",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{
+ "readDelay": {"1000"},
+ },
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcReadResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test": model.PlcResponseCode_OK,
+ },
+ map[string]values.PlcValue{
+ "test": values2.NewPlcBOOL(true),
+ }),
+ delayAtLeast: 1000,
+ },
+ {
+ name: "state not found",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{},
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": NewSimulatedField(FieldState,
"lalala", model2.SimulatedDataTypeSizes_BOOL, 1),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcReadResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test": model.PlcResponseCode_NOT_FOUND,
+ },
+ map[string]values.PlcValue{
+ "test": nil,
+ }),
+ delayAtLeast: 0,
+ },
+ // Passing in a completely wrong type of field.
+ {
+ name: "invalid field type",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{},
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": s7.PlcField{
+ FieldType: s7.S7Field,
+ MemoryArea:
model4.MemoryArea_DATA_BLOCKS,
+ BlockNumber: 1,
+ ByteOffset: 1,
+ BitOffset: 0,
+ NumElements: 1,
+ Datatype:
model4.TransportSize_BOOL,
+ },
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcReadResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test":
model.PlcResponseCode_INVALID_ADDRESS,
+ },
+ map[string]values.PlcValue{
+ "test": nil,
+ }),
+ delayAtLeast: 0,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := NewReader(tt.fields.device, tt.fields.options)
+ readRequest :=
model3.NewDefaultPlcReadRequest(tt.args.fields, tt.args.fieldNames, r, nil)
+ timeBeforeReadRequest := time.Now()
+ readResponseChannel := r.Read(readRequest)
+ select {
+ case readResponse := <-readResponseChannel:
+ timeAfterReadRequest := time.Now()
+ // If an expected delay was defined, check if
closing
+ // took at least this long.
+ if tt.delayAtLeast > 0 {
+ pingTime :=
timeAfterReadRequest.Sub(timeBeforeReadRequest)
+ if pingTime < tt.delayAtLeast {
+ t.Errorf("Reader.Read()
completed too fast. Expected at least %v but returned after %v",
tt.delayAtLeast, pingTime)
+ }
+ }
+ if
!reflect.DeepEqual(readResponse.GetRequest(), readRequest) {
+ t.Errorf("Reader.Read() ReadRequest =
%v, want %v", readResponse.GetRequest(), readRequest)
+ }
+ for _, fieldName := range
readRequest.GetFieldNames() {
+ if
!reflect.DeepEqual(readResponse.GetResponse().GetResponseCode(fieldName),
tt.want.GetResponseCode(fieldName)) {
+ t.Errorf("Reader.Read()
PlcResponse.ResponseCode = %v, want %v",
+
readResponse.GetResponse().GetResponseCode(fieldName),
tt.want.GetResponseCode(fieldName))
+ }
+ if
!reflect.DeepEqual(readResponse.GetResponse().GetValue(fieldName),
tt.want.GetValue(fieldName)) {
+ t.Errorf("Reader.Read()
PlcResponse.Value = %v, want %v",
+
readResponse.GetResponse().GetValue(fieldName), tt.want.GetValue(fieldName))
+ }
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("Reader.Read() got timeout")
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/simulated/ValueHandler.go
b/plc4go/internal/plc4go/simulated/ValueHandler.go
new file mode 100644
index 0000000..dc1e155
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/ValueHandler.go
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+)
+
+type ValueHandler struct {
+}
+
+func NewValueHandler() ValueHandler {
+ return ValueHandler{}
+}
+
+func (v ValueHandler) NewPlcValue(field model.PlcField, value interface{})
(values.PlcValue, error) {
+ //TODO implement me
+ panic("implement me")
+}
diff --git a/plc4go/internal/plc4go/simulated/Writer.go
b/plc4go/internal/plc4go/simulated/Writer.go
new file mode 100644
index 0000000..e830b7f
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Writer.go
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ model2 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "strconv"
+ "time"
+)
+
+type Writer struct {
+ device *Device
+ options map[string][]string
+}
+
+func NewWriter(device *Device, options map[string][]string) Writer {
+ return Writer{
+ device: device,
+ options: options,
+ }
+}
+
+func (w Writer) Write(writeRequest model.PlcWriteRequest) <-chan
model.PlcWriteRequestResult {
+ ch := make(chan model.PlcWriteRequestResult)
+ go func() {
+ // Possibly add a delay.
+ if delayString, ok := w.options["writeDelay"]; ok {
+ if len(delayString) == 1 {
+ delay, err := strconv.Atoi(delayString[0])
+ if err == nil {
+ time.Sleep(time.Duration(delay) *
time.Millisecond)
+ }
+ }
+ }
+
+ // Process the request
+ responseCodes := map[string]model.PlcResponseCode{}
+ for _, fieldName := range writeRequest.GetFieldNames() {
+ field := writeRequest.GetField(fieldName)
+ simulatedField, ok := field.(SimulatedField)
+ if !ok {
+ responseCodes[fieldName] =
model.PlcResponseCode_INVALID_ADDRESS
+ } else {
+ plcValue := writeRequest.GetValue(fieldName)
+ w.device.Set(simulatedField, &plcValue)
+ responseCodes[fieldName] =
model.PlcResponseCode_OK
+ }
+ }
+
+ // Emit the response
+ ch <- &model2.DefaultPlcWriteRequestResult{
+ Request: writeRequest,
+ Response:
model2.NewDefaultPlcWriteResponse(writeRequest, responseCodes),
+ Err: nil,
+ }
+ }()
+ return ch
+}
diff --git a/plc4go/internal/plc4go/simulated/Writer_test.go
b/plc4go/internal/plc4go/simulated/Writer_test.go
new file mode 100644
index 0000000..7312446
--- /dev/null
+++ b/plc4go/internal/plc4go/simulated/Writer_test.go
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package simulated
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/s7"
+ model4
"github.com/apache/plc4x/plc4go/internal/plc4go/s7/readwrite/model"
+ model2
"github.com/apache/plc4x/plc4go/internal/plc4go/simulated/readwrite/model"
+ model3 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+ values2 "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestWriter_Write(t *testing.T) {
+ type fields struct {
+ device *Device
+ options map[string][]string
+ }
+ type args struct {
+ fields map[string]model.PlcField
+ values map[string]values.PlcValue
+ fieldNames []string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want model.PlcWriteResponse
+ newState map[SimulatedField]*values.PlcValue
+ delayAtLeast time.Duration
+ }{
+ {
+ name: "simple state",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{},
+ },
+ options: map[string][]string{},
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1),
+ },
+ values: map[string]values.PlcValue{
+ "test": values2.NewPlcBOOL(true),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcWriteResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test": model.PlcResponseCode_OK,
+ }),
+ newState: map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState, "test",
model2.SimulatedDataTypeSizes_BOOL, 1): ToReference(values2.NewPlcBOOL(true)),
+ },
+ delayAtLeast: 0,
+ },
+ {
+ name: "simple state overwrite",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{},
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1),
+ },
+ values: map[string]values.PlcValue{
+ "test": values2.NewPlcBOOL(false),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcWriteResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test": model.PlcResponseCode_OK,
+ }),
+ newState: map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState, "test",
model2.SimulatedDataTypeSizes_BOOL, 1): ToReference(values2.NewPlcBOOL(false)),
+ },
+ delayAtLeast: 0,
+ },
+ {
+ name: "simple state delayed",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{
+ "writeDelay": {"1000"},
+ },
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1),
+ },
+ values: map[string]values.PlcValue{
+ "test": values2.NewPlcBOOL(false),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcWriteResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test": model.PlcResponseCode_OK,
+ }),
+ newState: map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState, "test",
model2.SimulatedDataTypeSizes_BOOL, 1): ToReference(values2.NewPlcBOOL(false)),
+ },
+ delayAtLeast: 1000,
+ },
+ // Passing in a completely wrong type of field.
+ {
+ name: "invalid field type",
+ fields: fields{
+ device: &Device{
+ Name: "hurz",
+ State:
map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState,
"test", model2.SimulatedDataTypeSizes_BOOL, 1):
ToReference(values2.NewPlcBOOL(true)),
+ },
+ },
+ options: map[string][]string{},
+ },
+ args: args{
+ fields: map[string]model.PlcField{
+ "test": s7.PlcField{
+ FieldType: s7.S7Field,
+ MemoryArea:
model4.MemoryArea_DATA_BLOCKS,
+ BlockNumber: 1,
+ ByteOffset: 1,
+ BitOffset: 0,
+ NumElements: 1,
+ Datatype:
model4.TransportSize_BOOL,
+ },
+ },
+ values: map[string]values.PlcValue{
+ "test": values2.NewPlcBOOL(false),
+ },
+ fieldNames: []string{"test"},
+ },
+ want: model3.NewDefaultPlcWriteResponse(nil,
+ map[string]model.PlcResponseCode{
+ "test":
model.PlcResponseCode_INVALID_ADDRESS,
+ }),
+ newState: map[SimulatedField]*values.PlcValue{
+ NewSimulatedField(FieldState, "test",
model2.SimulatedDataTypeSizes_BOOL, 1): ToReference(values2.NewPlcBOOL(true)),
+ },
+ delayAtLeast: 0,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ w := NewWriter(tt.fields.device, tt.fields.options)
+ writeRequest :=
model3.NewDefaultPlcWriteRequest(tt.args.fields, tt.args.fieldNames,
tt.args.values, w, nil)
+ timeBeforeWriteRequest := time.Now()
+ writeResponseChannel := w.Write(writeRequest)
+ select {
+ case writeResponse := <-writeResponseChannel:
+ timeAfterWriteRequest := time.Now()
+ // If an expected delay was defined, check if
closing
+ // took at least this long.
+ if tt.delayAtLeast > 0 {
+ pingTime :=
timeAfterWriteRequest.Sub(timeBeforeWriteRequest)
+ if pingTime < tt.delayAtLeast {
+ t.Errorf("Writer.Write()
completed too fast. Expected at least %v but returned after %v",
tt.delayAtLeast, pingTime)
+ }
+ }
+ if
!reflect.DeepEqual(writeResponse.GetRequest(), writeRequest) {
+ t.Errorf("Writer.Write() ReadRequest =
%v, want %v", writeResponse.GetRequest(), writeRequest)
+ }
+ for _, fieldName := range
writeRequest.GetFieldNames() {
+ if
!reflect.DeepEqual(writeResponse.GetResponse().GetResponseCode(fieldName),
tt.want.GetResponseCode(fieldName)) {
+ t.Errorf("Writer.Write()
PlcResponse.ResponseCode = %v, want %v",
+
writeResponse.GetResponse().GetResponseCode(fieldName),
tt.want.GetResponseCode(fieldName))
+ }
+ }
+ if !reflect.DeepEqual(tt.fields.device.State,
tt.newState) {
+ t.Errorf("Writer.Write() Device State =
%v, want %v",
+ tt.fields.device.State,
tt.newState)
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("Reader.Read() got timeout")
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/utils/pool/PlcConnectionPool.go
b/plc4go/internal/plc4go/utils/pool/PlcConnectionPool.go
new file mode 100644
index 0000000..f1be9e5
--- /dev/null
+++ b/plc4go/internal/plc4go/utils/pool/PlcConnectionPool.go
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package pool
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/spi/default"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "github.com/pkg/errors"
+ "github.com/viney-shih/go-lock"
+ "time"
+)
+
+type PlcConnectionPool struct {
+ driverManager plc4go.PlcDriverManager
+
+ // Maximum duration a connection can be used per lease.
+ // If the connection is used for a longer time, it is forcefully
removed from the client.
+ maxLeaseTime time.Duration
+ maxWaitTime time.Duration
+
+ poolLock lock.RWMutex
+ connections map[string]*PooledPlcConnection
+}
+
+func NewPlcConnectionPool(driverManager plc4go.PlcDriverManager)
*PlcConnectionPool {
+ return NewPlcConnectionPoolWithMaxLeaseTime(driverManager,
time.Second*20)
+}
+
+func NewPlcConnectionPoolWithMaxLeaseTime(driverManager
plc4go.PlcDriverManager, maxLeaseTime time.Duration) *PlcConnectionPool {
+ return &PlcConnectionPool{
+ driverManager: driverManager,
+ maxLeaseTime: maxLeaseTime,
+ maxWaitTime: maxLeaseTime * 5,
+ poolLock: lock.NewCASMutex(),
+ connections: make(map[string]*PooledPlcConnection),
+ }
+}
+
+func (t *PlcConnectionPool) GetConnection(connectionString string) <-chan
plc4go.PlcConnectionConnectResult {
+ ch := make(chan plc4go.PlcConnectionConnectResult)
+
+ go func() {
+ // Check if we've already got this connection
+ t.poolLock.Lock()
+ defer t.poolLock.Unlock()
+ // Try to get a connection for the given url.
+ // If this returns ok, this means there is already a connection
+ // available, so we'll try to use that. If it fails, we need
+ // to create a completely new connection.
+ if connection, ok := t.connections[connectionString]; ok {
+ // Use an existing connection
+
+ // Check if the connection is available.
+ if connection.state == StateIdle {
+ // As soon as we have the lock, return the
connection.
+ pooledConnection :=
t.connections[connectionString]
+ ch <-
_default.NewDefaultPlcConnectionConnectResult(pooledConnection, nil)
+ } else if connection.state == StateInUse {
+ // If the connection is currently busy, add the
new channel to the queue for this connection.
+ pooledConnection :=
t.connections[connectionString]
+ pooledConnection.enqueue(ch)
+ } else {
+ ch <-
_default.NewDefaultPlcConnectionConnectResult(nil, errors.New("timeout"))
+ }
+ } else {
+ // Create a new connection.
+
+ pooledPlcConnection :=
NewPooledPlcConnection(connectionString, t)
+ t.connections[connectionString] = pooledPlcConnection
+
+ // Initialize the new connection.
+ connectionResultChan :=
t.driverManager.GetConnection(connectionString)
+
+ // Allow us to finish this function and return the lock
quickly
+ go func() {
+ // Wait for the connection to be established.
+ connectionResult := <-connectionResultChan
+
+ // If the connection was successful, pass the
active connection into the container.
+ // If something went wrong, we have to remove
the connection from the pool and return the error.
+ if connectionResult.GetErr() == nil {
+ // Make sure we have the lock here so
all following operations will execute atomically.
+ pooledPlcConnection.lock.Lock()
+ defer pooledPlcConnection.lock.Unlock()
+
+ // Inject the real connection into the
container.
+ pooledPlcConnection.activeConnection =
connectionResult.GetConnection()
+ // Mark the connection as being used.
+ pooledPlcConnection.state = StateInUse
+
+ // Return the pooled connection to the
client.
+ ch <-
_default.NewDefaultPlcConnectionCloseResult(pooledPlcConnection, nil)
+ } else {
+ // Mark the connection as broken.
+ pooledPlcConnection.state = StateInvalid
+
+ // Remove the broken connection from
the pool.
+ t.poolLock.Lock()
+ defer t.poolLock.Unlock()
+ delete(t.connections, connectionString)
+
+ // Forward the error to the client.
+ ch <-
_default.NewDefaultPlcConnectionConnectResult(nil, connectionResult.GetErr())
+ }
+ }()
+ }
+ }()
+
+ return ch
+}
+
+func (t *PlcConnectionPool) returnConnection(pooledConnection
*PooledPlcConnection) error {
+ // If the connection is marked as "invalid", destroy it and remove it
from the pool.
+ if pooledConnection.state == StateInvalid {
+ // At least try to close the invalid connection.
+ pooledConnection.activeConnection.Close()
+
+ // TODO: Either try to reconnect or cancel all waiting
connections
+
+ // Remove the connection from the pool.
+ t.poolLock.Lock()
+ defer t.poolLock.Unlock()
+ delete(t.connections, pooledConnection.connectionString)
+ return nil
+ }
+
+ pooledConnection.lock.Lock()
+ defer pooledConnection.lock.Unlock()
+ // Check how many others are waiting for this connection.
+ if len(pooledConnection.queue) > 0 {
+ // There are waiting clients, give the connection to the next
client in the line.
+ next := pooledConnection.queue[0]
+ pooledConnection.queue = pooledConnection.queue[1:]
+ next <-
_default.NewDefaultPlcConnectionConnectResult(pooledConnection, nil)
+ } else {
+ // Otherwise, just mark the connection as idle.
+ pooledConnection.state = StateIdle
+ }
+
+ return nil
+}
diff --git a/plc4go/internal/plc4go/utils/pool/PlcConnectionPool_test.go
b/plc4go/internal/plc4go/utils/pool/PlcConnectionPool_test.go
new file mode 100644
index 0000000..919e375
--- /dev/null
+++ b/plc4go/internal/plc4go/utils/pool/PlcConnectionPool_test.go
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package pool
+
+import (
+ "github.com/apache/plc4x/plc4go/internal/plc4go/simulated"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "testing"
+ "time"
+)
+
+func TestPlcConnectionPool_GetConnection(t1 *testing.T) {
+ type fields struct {
+ driverManager plc4go.PlcDriverManager
+ }
+ type args struct {
+ connectionString string
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ wantErr bool
+ wantTimeout bool
+ }{
+ {name: "simple",
+ fields: fields{
+ driverManager: func() plc4go.PlcDriverManager {
+ driverManager :=
plc4go.NewPlcDriverManager()
+
driverManager.RegisterDriver(simulated.NewDriver())
+ return driverManager
+ }(),
+ }, args: args{
+ connectionString: "simulated://1.2.3.4:42",
+ },
+ wantErr: false,
+ wantTimeout: false,
+ },
+ {name: "simpleWithTimeout",
+ fields: fields{
+ driverManager: func() plc4go.PlcDriverManager {
+ driverManager :=
plc4go.NewPlcDriverManager()
+
driverManager.RegisterDriver(simulated.NewDriver())
+ return driverManager
+ }(),
+ }, args: args{
+ connectionString:
"simulated://1.2.3.4:42?connectionDelay=5",
+ },
+ wantErr: false,
+ wantTimeout: true,
+ },
+ }
+ for _, tt := range tests {
+ t1.Run(tt.name, func(t1 *testing.T) {
+ t := NewPlcConnectionPool(tt.fields.driverManager)
+ got := t.GetConnection(tt.args.connectionString)
+ select {
+ case connectResult := <-got:
+ if tt.wantErr && (connectResult.GetErr() ==
nil) {
+
t1.Errorf("PlcConnectionPool.GetConnection() = %v, wantErr %v",
connectResult.GetErr(), tt.wantErr)
+ } else if connectResult.GetErr() != nil {
+
t1.Errorf("PlcConnectionPool.GetConnection() error = %v, wantErr %v",
connectResult.GetErr(), tt.wantErr)
+ }
+ case <-time.After(3 * time.Second):
+ if !tt.wantTimeout {
+
t1.Errorf("PlcConnectionPool.GetConnection() got timeout")
+ }
+ }
+ })
+ }
+}
diff --git a/plc4go/internal/plc4go/utils/pool/PooledPlcConnection.go
b/plc4go/internal/plc4go/utils/pool/PooledPlcConnection.go
new file mode 100644
index 0000000..20765cc
--- /dev/null
+++ b/plc4go/internal/plc4go/utils/pool/PooledPlcConnection.go
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package pool
+
+import (
+ _default "github.com/apache/plc4x/plc4go/internal/plc4go/spi/default"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go"
+ "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+ "github.com/viney-shih/go-lock"
+ "time"
+)
+
+type PooledPlcConnectionState int32
+
+const (
+ StateInitialized PooledPlcConnectionState = iota
+ StateIdle
+ StateInUse
+ StateInvalid
+)
+
+type PooledPlcConnection struct {
+ connectionString string
+ // Reference to the pool (used for giving back connection)
+ connectionPool *PlcConnectionPool
+
+ // The lock for manipulating the pools state.
+ lock lock.Mutex
+ // The actual connection being pooled.
+ activeConnection plc4go.PlcConnection
+ // The current state of this connection.
+ state PooledPlcConnectionState
+ // Queue of waiting clients.
+ queue []chan plc4go.PlcConnectionConnectResult
+}
+
+func NewPooledPlcConnection(connectionString string, connectionPool
*PlcConnectionPool) *PooledPlcConnection {
+ return &PooledPlcConnection{
+ connectionString: connectionString,
+ state: StateInitialized,
+ lock: lock.NewCASMutex(),
+ queue: make([]chan
plc4go.PlcConnectionConnectResult, 0),
+ connectionPool: connectionPool,
+ }
+}
+
+func (t *PooledPlcConnection) setActiveConnection(activeConnection
plc4go.PlcConnection) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+ t.activeConnection = activeConnection
+}
+
+func (t *PooledPlcConnection) setState(state PooledPlcConnectionState) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+ t.state = state
+}
+
+func (t *PooledPlcConnection) enqueue(ch chan
plc4go.PlcConnectionConnectResult) {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+ t.queue = append(t.queue, ch)
+}
+
+func (t *PooledPlcConnection) Connect() <-chan
plc4go.PlcConnectionConnectResult {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ panic("Called 'Connect' on a pooled connection")
+}
+
+func (t *PooledPlcConnection) BlockingClose() {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+
+ // Call close and wait for the operation to finish.
+ <-t.Close()
+}
+
+func (t *PooledPlcConnection) Close() <-chan plc4go.PlcConnectionCloseResult {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+
+ result := make(chan plc4go.PlcConnectionCloseResult)
+
+ go func() {
+ // Check if the connection is still alive, if it is, put it
back into the pool
+ pingResults := t.Ping()
+ pingTimeout := time.NewTimer(time.Second * 5)
+ select {
+ case pingResult := <-pingResults:
+ {
+ if pingResult.GetErr() != nil {
+ // Mark the connection as broken ...
+ t.state = StateInvalid
+ }
+ }
+ case <-pingTimeout.C:
+ {
+ // Mark the connection as broken ...
+ t.state = StateInvalid
+ }
+ }
+
+ // Return the connection to the pool and don't actually close
it.
+ err := t.connectionPool.returnConnection(t)
+
+ // Finish closing the connection.
+ result <- _default.NewDefaultPlcConnectionCloseResult(t, err)
+ }()
+
+ return result
+}
+
+func (t *PooledPlcConnection) IsConnected() bool {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.IsConnected()
+}
+
+func (t *PooledPlcConnection) Ping() <-chan plc4go.PlcConnectionPingResult {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.Ping()
+}
+
+func (t *PooledPlcConnection) GetMetadata() model.PlcConnectionMetadata {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.GetMetadata()
+}
+
+func (t *PooledPlcConnection) ReadRequestBuilder() model.PlcReadRequestBuilder
{
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.ReadRequestBuilder()
+}
+
+func (t *PooledPlcConnection) WriteRequestBuilder()
model.PlcWriteRequestBuilder {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.WriteRequestBuilder()
+}
+
+func (t *PooledPlcConnection) SubscriptionRequestBuilder()
model.PlcSubscriptionRequestBuilder {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.SubscriptionRequestBuilder()
+}
+
+func (t *PooledPlcConnection) UnsubscriptionRequestBuilder()
model.PlcUnsubscriptionRequestBuilder {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.UnsubscriptionRequestBuilder()
+}
+
+func (t *PooledPlcConnection) BrowseRequestBuilder()
model.PlcBrowseRequestBuilder {
+ if t.activeConnection == nil {
+ panic("No active connection")
+ }
+ return t.activeConnection.BrowseRequestBuilder()
+}
diff --git a/plc4go/pkg/plc4go/pool/plc_connection_pool.go
b/plc4go/pkg/plc4go/pool/plc_connection_pool.go
new file mode 100644
index 0000000..1a91b9d
--- /dev/null
+++ b/plc4go/pkg/plc4go/pool/plc_connection_pool.go
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package pool