****topcua - a Tcl binding to OPC/UA**** OPC Unified Architecture (OPC UA) is a machine to machine communication protocol for industrial automation developed by the OPC Foundation. Refer to https://en.wikipedia.org/wiki/OPC_Unified_Architecture for a detailed overview. A proof-of-concept extension called '''topcua''' provides a Tcl binding to a C based OPC UA implementation from https://open62541.org/ and can be found in https://www.androwish.org/index.html/dir?name=jni/topcua The documentation can be found in https://www.androwish.org/index.html/wiki?name=topcua The code is very portable and might run on all Tcl supported platforms provided that the platform's C compiler supports C99. ****A short example script**** The source tree contains an example script implementing a webcam in a few lines of code. The most interesting piece is how variables/data/things can be mapped between Tcl and OPC/UA domains as shown in the '''_lastimg''' and '''_setparm''' procedures and how the corresponding items in the OPC/UA address space are created in the '''opcua add ...''' invocations. This allows to connect from an OPC/UA client tool like https://www.unified-automation.com/products/development-tools/uaexpert.html%|%UAExpert%|% or https://www.prosysopc.com/products/opc-ua-client%|%OPC UA Client%|% and to display the camera image. But be aware that this is early alpha quality stuff and may contain memory leaks and all other kinds of serious bugs. Although it might seem to be some kind of Tcl dream in Industry 4.0 and to have the theoretical capability of controlling drilling rigs, nuclear power plants, low earth orbital stations, and so on it is far from being complete, tested, verified, and certified. ====== # A little OPC/UA webcam in about 100 LOC # # Requires Linux, MacOSX, or FreeBSD, due to tcluvc support, # but can be easily modified for Windows to use tclwmf instead. package require Tk package require topcua package require tcluvc # hide Tk toplevel wm withdraw . # get first available camera set cam [lindex [uvc devices] 0] if {$cam eq {}} { puts stderr "no camera found" exit 1 } # open camera if {[catch {uvc open $cam capture} cam]} { puts stderr "open failed: $cam" exit 1 } # set format to 320x240 foreach {i fmt} [uvc listformats $cam] { if {[dict get $fmt "frame-size"] eq "320x240"} { uvc format $cam $i 1 break } } # photo image for capture set img [image create photo] # image capture callback proc capture {cam} { # limit frame rate, otherwise it consumes too much CPU for # image processing and the OPC/UA server part starves lassign [uvc counters $cam] all done dropped if {$all % 20 == 0} { uvc image $cam $::img set ::png [$::img data -format png] } } # create OPC/UA server opcua new server 4840 S # implementation of OPC/UA data sources namespace eval ::opcua::S { # data source callback proc _lastimg {node op {value {}}} { if {$op eq "read"} { return [list ByteString $::png] } # hey, this is a camera, not a screen return -code error "write shouldn't happen" } # data source callback proc _setparm {name node op {value {}}} { if {$op eq "read"} { array set p [uvc parameter $::cam] set v 0 if {[info exists p($name)]} { set v $p($name) } return [list Int32 $v] } set v [dict get $value "value"] catch {uvc parameter $::cam $name $v} return {} } } # create our namespace in OPC/UA land set ns [opcua add S Namespace LilWebCam] # get Objects folder set OF [lindex [opcua translate S [opcua root] / Objects] 0] # create an object in our namespace in Objects folder set obj [opcua add S Object "ns=$ns;s=LilWebCam" $OF Organizes "$ns:LilWebCam"] # create some variables in our folder to deal with camera settings set att [opcua attrs default VariableAttributes] dict set att dataType [opcua types nodeid Int32] dict set att accessLevel 3 ;# writable foreach name {brightness contrast gain gamma hue saturation} { opcua add S Variable "ns=$ns;s=[string totitle $name]" $obj HasComponent "$ns:[string totitle $name]" {} $att [list ::opcua::S::_setparm $name] } # get node identifier of Image data type, a subtype of ByteString set IT [lindex [opcua translate S [opcua root] / Types / DataTypes / BaseDataType / ByteString / Image] 0] # create variable in our folder to return last photo image set att [opcua attrs default VariableAttributes] dict set att dataType $IT ;# Image data type dict set att valueRank -1 ;# 1-dimensional array opcua add S Variable "ns=$ns;s=Image" $obj HasComponent "$ns:Image" {} $att ::opcua::S::_lastimg # start server using Tk's event loop opcua start S # start camera uvc start $cam ====== ****The client for the short example script**** For the above webcam (the server) a corresponding client can be found in the source tree, too. ====== # A little OPC/UA webcam client example package require Tk package require topcua wm title . "Client of LilWebCam" set img [image create photo] label .label -image $img pack .label # create client opcua new client C # connect to server opcua connect C opc.tcp://localhost:4840 # get the namespace set ns [opcua namespace C LilWebCam] # monitor callback proc proc monitor {data} { $::img configure -format png -data [dict get $data value] } # make a subscription with 200 ms rate set sub [opcua subscription C new 1 200.0] # make a monitor to the camera image set mon [opcua monitor C new $sub data monitor "ns=${ns};Image"] # handle OPC/UA traffic (the subscription/monitor) proc do_opcua_traffic {} { after cancel do_opcua_traffic if {[catch {opcua run C 20}]} { # this most likely is the server shutting down exit } after 200 do_opcua_traffic } do_opcua_traffic ====== ****Custom Data Types**** It is possible to define custom data types in the form of data structures and enumerations while the current '''topcua''' extension currently allows only the former. A data structure is expressed as specific nodes in the OPC/UA address space and communicated to the outside world as so called extension object. In order for a generic client to interpret that extension object, a description for serialization is stored as an XML string in another node in the OPC/UA address space. The process of definition of structures and serialization is performed with the '''opcua deftypes''' and '''opcua gentypes''' subcommands as shown in this example. ====== package require topcua # create server opcua new server 4840 S # create our namespace set NS http://www.androwish.org/TestNS/ set nsidx [opcua add S Namespace $NS] # create structs opcua deftypes S $NS { struct KVPair { String name String value } struct RGB { UInt16 red UInt16 green UInt16 blue } struct NamedColor { String name RGB color } } # import type defs opcua gentypes S # make some variables using the structs from above set OF [lindex [opcua translate S [opcua root] / Objects] 0] foreach {name type} { X1 KVPair X2 RGB X3 NamedColor } { set att [opcua attrs default VariableAttributes] dict set att dataType [opcua types nodeid S $type] dict set att value [list $type [opcua types empty S $type]] opcua add S Variable "ns=${nsidx};s=$name" $OF Organizes "${nsidx}:$name" {} $att } # start server opcua start S # enter event loop vwait forever ====== ****Exercises for the interested reader**** * make the camera using the tclwmf extension from http://www.androwish.org to run this on Windows * make the camera using the borg extension from http://www.androwish.org to run this on a tablet or smartphone * add more camera controls using appropriate mappings between tcluvc parameters and OPC/UA variables * use e.g. SQLite as persistent data store for variable values * create some methods to query e.g. an SQLite database (and avoid SQL insertion problems for the query's parameters) <> Android | Dev. Tools