blob: 07775eb949e189840b56ae1fbebabe9fbff39578 [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>PLC4X &#x2013; </title>
<script src="../../js/jquery.slim.min.js" type="text/javascript"></script>
<!--script src="../../js/popper.min.js" type="javascript"></script-->
<script src="../../js/bootstrap.bundle.min.js" type="text/javascript"></script>
<!-- The tooling for adding images and links to Apache events -->
<script src="https://www.apachecon.com/event-images/snippet.js" type="text/javascript"></script>
<!-- FontAwesome -->
<link rel="stylesheet" href="../../css/all.min.css" type="text/css"/>
<!-- Bootstrap -->
<link rel="stylesheet" href="../../css/bootstrap.min.css" type="text/css"/>
<!-- Some Maven Site defaults -->
<link rel="stylesheet" href="../../css/maven-base.css" type="text/css"/>
<link rel="stylesheet" href="../../css/maven-theme.css" type="text/css"/>
<!-- The PLC4X version of a bootstrap theme -->
<link rel="stylesheet" href="../../css/themes/plc4x.css" type="text/css" id="pagestyle"/>
<!-- A custom style for printing content -->
<link rel="stylesheet" href="../../css/print.css" type="text/css" media="print"/>
<meta http-equiv="Content-Language" content="en"/>
</head>
<body class="composite">
<nav class="navbar navbar-light navbar-expand-md bg-faded justify-content-center border-bottom">
<!--a href="/" class="navbar-brand d-flex w-50 mr-auto">Navbar 3</a-->
<a href="https://plc4x.apache.org/" id="bannerLeft"><img src="../../images/apache_plc4x_logo_small.png" alt="Apache PLC4X"/></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsingNavbar3">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse w-100" id="collapsingNavbar3">
<ul class="navbar-nav w-100 justify-content-center">
<li class="nav-item">
<a class="nav-link" href="../../index.html">Home</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="../../users/index.html">Users</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../developers/index.html">Developers</a>
</li>
<li class="nav-item">
<a class="nav-link" href="../../apache/index.html">Apache</a>
</li>
</ul>
<ul class="nav navbar-nav ml-auto justify-content-end">
<li class="nav-item row valign-middle">
<a class="acevent" data-format="wide" data-mode="light" data-event="random" style="width:240px;height:60px;"></a>
</li>
</ul>
</div>
</nav>
<div class="container-fluid">
<div class="row h-100">
<nav class="col-sm-push col-md-2 pt-3 sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a href="../../users/index.html" class="nav-link">Section Home</a>
</li>
<li class="nav-item">
<a href="../../users/download.html" class="nav-link">Download</a>
</li>
<li class="nav-item">
<a href="../../users/adopters.html" class="nav-link">Adopters</a>
</li>
<li class="nav-item">
<a href="../../users/commercial-support.html" class="nav-link">Commercial support</a>
</li>
<li class="nav-item">
<a href="../../users/gettingstarted.html" class="nav-link">Getting Started</a>
<ul class="flex-column pl-4 nav">
<li class="nav-item">
<strong class="nav-link">Go</strong>
</li>
<li class="nav-item">
<a href="../../users/getting-started/plc4j.html" class="nav-link">Java</a>
</li>
<li class="nav-item">
<a href="../../users/getting-started/using-snapshots.html" class="nav-link">Using SNAPSHOTS</a>
</li>
<li class="nav-item">
<a href="../../users/getting-started/general-concepts.html" class="nav-link">General Concepts</a>
</li>
<li class="nav-item">
<a href="../../users/getting-started/virtual-modbus.html" class="nav-link">Virtual Modbus</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="../../users/blogs-videos-and-slides.html" class="nav-link">Blogs, Videos and Slides</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/index.html" class="nav-link">Protocols</a>
<ul class="flex-column pl-4 nav">
<li class="nav-item">
<a href="../../users/protocols/ab-eth.html" class="nav-link">AB-ETH</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/ads.html" class="nav-link">ADS/AMS</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/bacnetip.html" class="nav-link">BACnet/IP</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/canopen.html" class="nav-link">CANopen</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/deltav.html" class="nav-link">DeltaV</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/df1.html" class="nav-link">DF1</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/ethernet-ip.html" class="nav-link">EtherNet/IP</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/firmata.html" class="nav-link">Firmata</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/knxnetip.html" class="nav-link">KNXnet/IP</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/modbus.html" class="nav-link">Modbus</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/opc-ua.html" class="nav-link">OPC UA</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/plc4x.html" class="nav-link">PLC4X (Proxy)</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/s7.html" class="nav-link">S7 (Step7)</a>
</li>
<li class="nav-item">
<a href="../../users/protocols/simulated.html" class="nav-link">Simulated</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="../../users/transports/index.html" class="nav-link">Transports</a>
<ul class="flex-column pl-4 nav">
<li class="nav-item">
<a href="../../users/transports/tcp.html" class="nav-link">TCP</a>
</li>
<li class="nav-item">
<a href="../../users/transports/udp.html" class="nav-link">UDP</a>
</li>
<li class="nav-item">
<a href="../../users/transports/serial.html" class="nav-link">Serial</a>
</li>
<li class="nav-item">
<a href="../../users/transports/socketcan.html" class="nav-link">SocketCAN</a>
</li>
<li class="nav-item">
<a href="../../users/transports/raw-socket.html" class="nav-link">Raw Socket</a>
</li>
<li class="nav-item">
<a href="../../users/transports/pcap-replay.html" class="nav-link">PCAP Replay</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="../../users/integrations/index.html" class="nav-link">Integrations</a>
<ul class="flex-column pl-4 nav">
<li class="nav-item">
<a href="../../users/integrations/apache-calcite.html" class="nav-link">Apache Calcite</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/apache-camel.html" class="nav-link">Apache Camel</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/apache-edgent.html" class="nav-link">Apache Edgent</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/apache-iotdb.html" class="nav-link">Apache IoTDB</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/apache-kafka.html" class="nav-link">Apache Kafka</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/apache-nifi.html" class="nav-link">Apache NiFi</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/apache-streampipes.html" class="nav-link">Apache StreamPipes</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/eclipse-ditto.html" class="nav-link">Eclipse Ditto</a>
</li>
<li class="nav-item">
<a href="../../users/integrations/eclipse-milo.html" class="nav-link">Eclipse Milo OPC UA Server</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="../../users/tools/index.html" class="nav-link">Tools</a>
<ul class="flex-column pl-4 nav">
<li class="nav-item">
<a href="../../users/tools/capture-replay.html" class="nav-link">Capture Replay</a>
</li>
<li class="nav-item">
<a href="../../users/tools/connection-pool.html" class="nav-link">Connection Pool</a>
</li>
<li class="nav-item">
<a href="../../users/tools/connection-cache.html" class="nav-link">Connection Cache</a>
</li>
<li class="nav-item">
<a href="../../users/tools/opm.html" class="nav-link">Object PLC Mapping (OPM)</a>
</li>
<li class="nav-item">
<a href="../../users/tools/scraper.html" class="nav-link">Scraper</a>
</li>
<li class="nav-item">
<a href="../../users/tools/testing.html" class="nav-link">PLC4X without a PLC and Unit Testing</a>
</li>
</ul>
</li>
<li class="nav-item">
<a href="../../users/industry40.html" class="nav-link">Industry 4.0 with Apache</a>
</li>
<li class="nav-item">
<a href="../../users/security.html" class="nav-link">Security</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="ml-sm-auto px-4 col-sm-pull col-md-9 col-lg-10 h-100">
<div class="sect1">
<h2 id="getting_started">Getting Started</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="initializing_a_dummy_project">Initializing a dummy project</h3>
<div class="paragraph">
<p>Just in case you want to get started with <code>Go</code>. In this part we&#8217;ll setup a new <code>Go</code> project.
If you are familiar with this, you can go to the next chapter.</p>
</div>
<div class="paragraph">
<p>Be sure to have installed <code>Go</code> on your system by downloading and installing it from <a href="https://golang.org/">https://golang.org/</a>.</p>
</div>
<div class="paragraph">
<p>Now that that&#8217;s done, create a new directory and console execute the following command:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>go mod init main</pre>
</div>
</div>
<div class="paragraph">
<p>This will effectively only create a <code>go.mod</code> file, which is sort of like Go&#8217;s counterpart to a Maven pom.xml.</p>
</div>
<div class="paragraph">
<p>This should look like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code>module main
go 1.16</code></pre>
</div>
</div>
<div class="paragraph">
<p>Now we need to create a <code>app.go</code> file which will be our main program.</p>
</div>
<div class="paragraph">
<p>Create a file <code>app.go</code> with the following content:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>package main
func main() {
}</pre>
</div>
</div>
<div class="paragraph">
<p>By executing the command:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>go run app.go</pre>
</div>
</div>
<div class="paragraph">
<p>You will execute your first <code>Go</code> progran &#8230;&#8203; however the output is rather underwhelming ;-)</p>
</div>
<div class="paragraph">
<p>You&#8217;re now ready to continue.</p>
</div>
</div>
<div class="sect2">
<h3 id="using_the_plc4go_api_directly">Using the PLC4Go API directly</h3>
<div class="paragraph">
<p>In order to write a valid PLC4X Go application, all you need, is to add a dependency to the <code>plc4go module</code>.
Now all you need to do, is execute the following command:</p>
</div>
<div class="literalblock">
<div class="content">
<pre>go get github.com/apache/plc4x/plc4go</pre>
</div>
</div>
<div class="paragraph">
<p>This will checkout the latest version of the Apache PLC4X <code>PLC4Go</code> module.
As soon as we have released a PLC4Go version by adding the name of the release-tag will use an explicit version.</p>
</div>
<div class="paragraph">
<p>This will be checked out in our home directory in</p>
</div>
<div class="literalblock">
<div class="content">
<pre>⁓/go/pkg/mod/github.com/apache/plc4/plc4go@v0.0.0-{some-commit-hash}</pre>
</div>
</div>
<div class="paragraph">
<p>In contrast to the PLC4J version this already contains all supported drivers.
Perhaps we&#8217;ll change this in the future, but for now all comes in one bundle.</p>
</div>
<div class="paragraph">
<p>Now you&#8217;re generally set to start writing your first <code>PLC4Go</code> program.</p>
</div>
<div class="sect3">
<h4 id="connecting_to_a_plc">Connecting to a PLC</h4>
<div class="paragraph">
<p>In contrast to PLC4J, which uses the service lookup to find the <code>transports</code> and the <code>drivers</code> automatically, in <code>PLC4Go</code> they need to be manually registered at the driver manager.</p>
</div>
<div class="paragraph">
<p>First we need to initialize the <code>PlcDriverManager</code> by registering the transports and drivers with it.</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Create a new instance of the PlcDriverManager
driverManager := plc4go.NewPlcDriverManager()
// Register the Transports
transports.RegisterTcpTransport(driverManager)
transports.RegisterUdpTransport(driverManager)
// Register the Drivers
drivers.RegisterKnxDriver(driverManager)
drivers.RegisterModbusDriver(driverManager)</pre>
</div>
</div>
<div class="paragraph">
<p>Now that the <code>PlcDriverManager</code> is configured, we can use it to get a new connection.</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Get a connection to a remote PLC
connectionRequestChanel := driverManager.GetConnection("modbus-tcp://192.168.23.30?unit-identifier=1")
// Wait for the driver to connect (or not)
connectionResult := &lt;-connectionRequestChanel
// Check if something went wrong
if connectionResult.Err != nil {
fmt.Printf("Error connecting to PLC: %s", connectionResult.Err.Error())
return
}
// If all was ok, get the connection instance
connection := connectionResult.Connection
// Make sure the connection is closed at the end
defer connection.Close()</pre>
</div>
</div>
<div class="paragraph">
<p>In PLC4Go we make heavy use of Go <code>channels</code>, which are similar to <code>Futures</code> or <code>Promisses</code>.</p>
</div>
<div class="paragraph">
<p>And please pay attention to the <code>defer</code> command.
This adds a call to a stack of things that need to be called as soon as the program terminates.
However in contrast to Java&#8217;s <code>try-finally</code> blocks, this isn&#8217;t executed at the end of the code-block, but really when the program terminates.
So when working with many connections or when using connections in loops (if for example you are polling), then this will keep on piling up active connections, till either you are no longer able to connect cause your PLC denies connections or till you run out of memory.</p>
</div>
<div class="paragraph">
<p>So if you only need the connection in a code block, be sure to explicitly close it after usage.</p>
</div>
<div class="paragraph">
<p>After this code block we should be in possession of a <code>connection</code> instance.</p>
</div>
<div class="paragraph">
<p>If we simply want to check the connectivity, we can use the <code>Ping</code> function on the connection object.
Depending on the protocol used, it will exeute a command which only will complete if the connection is available.</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Try to ping the remote device
pingResultChannel := connection.Ping()
// Wait for the Ping operation to finsh
pingResult := &lt;-pingResultChannel
if pingResult.Err != nil {
fmt.Printf("Couldn't ping device: %s", pingResult.Err.Error())
return
}</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="reading_data">Reading Data</h4>
<div class="paragraph">
<p>Most probably you will want to read something from a PLC.
This is done by a <code>PlcReadRequest</code>.</p>
</div>
<div class="paragraph">
<p>First off all, it&#8217;s probably a good idea to check if this connection supports reading:</p>
</div>
<div class="listingblock">
<div class="content">
<pre> if !connection.GetMetadata().CanRead() {
fmt.Printf("This connection doesn't support read operations")
return
}</pre>
</div>
</div>
<div class="paragraph">
<p>In order to create and run such a <code>PlcReadRequest</code>, please add the following code:</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Prepare a read-request
readRequest, err := connection.ReadRequestBuilder().
AddQuery("field1", "holding-register:1:REAL").
AddQuery("field2", "holding-register:3:REAL").
Build()
if err != nil {
t.Errorf("error preparing read-request: %s", connectionResult.Err.Error())
t.Fail()
return
}</pre>
</div>
</div>
<div class="paragraph">
<p>If you have any errors in the addresses or whatever, you will get an <code>err</code> instead of a <code>readRequest</code>.</p>
</div>
<div class="paragraph">
<p>For now, let&#8217;s assume you got all addresses correctly.</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Execute a read-request
readResponseChanel := readRequest.Execute()
// Wait for the response to finish
readRequestResult := &lt;-readResponseChanel
if readRequestResult.Err != nil {
t.Errorf("error executing read-request: %s", readRequestResult.Err.Error())
return
}</pre>
</div>
</div>
<div class="paragraph">
<p>Please note that in this case we want to return a triple: <code>PlcReadRequest</code>, <code>PlcReadResponse</code>, <code>err</code>.
As this is not supported in <code>Go</code>, the <code>PlcReadRequestResult</code> will contain all of these 3 elements.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
This will probably change soon. The API is still a bit in flux.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>Now in order to do something with the response:</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Do something with the response
value1 := readRequestResult.Response.GetValue("field1")
value2 := readRequestResult.Response.GetValue("field2")
fmt.Printf("\n\nResult field1: %f\n", value1.GetFloat32())
fmt.Printf("\n\nResult field2: %f\n", value2.GetFloat32())</pre>
</div>
</div>
<div class="paragraph">
<p>The <code>GetValue</code> function returns a <code>PlcValue</code> instance, this had accessors for the most general <code>Go</code> types.</p>
</div>
</div>
<div class="sect3">
<h4 id="writing_data">Writing Data</h4>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Not implemented yet
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="subscribing_to_data">Subscribing to Data</h4>
<div class="paragraph">
<p>As the <code>Modbus</code> protocol, which we used in the above examples, doesn&#8217;t support subscriptions, we are uing the <code>KNX</code> protocol for a demonstration on the subscription API.</p>
</div>
<div class="paragraph">
<p>Subscribing to data can be considered similar to reading data, at least the subscription itself if very similar to reading of data.</p>
</div>
<div class="paragraph">
<p>We first have to check if the connection supports this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre> if !connection.GetMetadata().CanSubscribe() {
fmt.Printf("This connection doesn't support subscriptions operations")
return
}</pre>
</div>
</div>
<div class="paragraph">
<p>Now we&#8217;ll create the subscription request.</p>
</div>
<div class="paragraph">
<p>The main difference is that while reading there is only one form how you could read, with subscriptions there are different forms of subscriptons:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Change of state (Event is sent as soon as a value changes)</p>
</li>
<li>
<p>Cyclic (The Event is sent in regular cyclic intervals)</p>
</li>
<li>
<p>Event (The Event is usually explicitly sent form the PLC as a signal)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Therefore instead of using a normal <code>AddItem</code>, there are tree different functions as you can see in the following examples.</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Prepare a subscription-request
subscriptionRequest, err := connection.SubscriptionRequestBuilder().
AddChangeOfStateItem("heating-actual-temperature", "*/*/10:DPT_Value_Temp").
AddChangeOfStateItem("heating-target-temperature", "*/*/11:DPT_Value_Temp").
AddCyclicItem("heating-valve-open", "*/*/12:DPT_OpenClose", 500 * time.Millisecond).
AddItemHandler(knxEventHandler).
Build()
if err != nil {
fmt.Printf("Error preparing subscription-request: %s", connectionResult.Err.Error())
return
}</pre>
</div>
</div>
<div class="paragraph">
<p>The <code>Event hadnler</code> for intercepting incoming events could look like this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>func knxEventHandler(event apiModel.PlcSubscriptionEvent) {
for _, fieldName := range event.GetFieldNames() {
if event.GetResponseCode(fieldName) == apiModel.PlcResponseCode_OK {
groupAddress := event.GetAddress(fieldName)
fmt.Printf("Got update for field %s with address %s. Value changed to: %s\n",
fieldName, groupAddress, event.GetValue(fieldName).GetString())
}
}
}</pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
The <code>AddCyclicField</code> method requires a third parameter <code>duration</code> which specifies the interval, in which a given value is sent (even if it has not changed).
</td>
</tr>
</table>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Here the API differs slightly form the Java version, as in the request-builder itself you specify the reference to the callback handler which should be notified on incoming data. Howerver, we will be alliging all API variants as much as possible in the near future.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>The request itself is executed exactly the same way the read and write operations are executed, using the <code>Execute</code> function.</p>
</div>
<div class="listingblock">
<div class="content">
<pre> // Execute a subscription-request
subscriptionRequestResultChanel := subscriptionRequest.Execute()
// Wait for the response to finish
subscriptionRequestResult := &lt;-subscriptionRequestResultChanel
if subscriptionRequestResult.Err != nil {
fmt.Printf("Error executing read-request: %s", subscriptionRequestResult.Err.Error())
return
}</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="pt-4 my-md-5 pt-md-5 w-100 border-top">
<div class="row justify-content-md-center" style="font-size: 13px">
<div class="col col-6 text-center">
Copyright &#169; 2017&#x2013;2022 <a href="https://www.apache.org/">The Apache Software Foundation</a>.
All rights reserved.<br/>
Apache PLC4X, PLC4X, Apache, the Apache feather logo, and the Apache PLC4X project logo are either registered trademarks or trademarks of The Apache Software Foundation in the United States and other countries. All other marks mentioned may be trademarks or registered trademarks of their respective owners.
<br/><div style="text-align:center;">Home screen image taken from <a
href="https://flic.kr/p/chEftd">Flickr</a>, "Tesla Robot Dance" by Steve Jurvetson, licensed
under <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0 Generic</a>, image cropped
and blur effect added.</div>
</div>
</div>
</footer>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="../../js/jquery.slim.min.js"></script>
<script src="../../js/popper.min.js"></script>
<script src="../../js/bootstrap.min.js"></script>
<script type="text/javascript">
$('.carousel .carousel-item').each(function(){
var next = $(this).next();
if (!next.length) {
next = $(this).siblings(':first');
}
next.children(':first-child').clone().appendTo($(this));
for (let i = 0; i < 3; i++) {
next=next.next();
if (!next.length) {
next = $(this).siblings(':first');
}
next.children(':first-child').clone().appendTo($(this));
}
});
</script>
</body>
</html>