This blog presents the revision 2 from the material from the blog:
WCF with F# interactive – Data Server example - Dec/25/2010
http://caxelrud.blogspot.com/2010/12/wcf-with-f-interactive-data-server.html
These are the improvements:
- The data value is of type Object. The examples show how to work with basic types as float, integer, strings and other types as dateTime. It can also use array of the basic types.
- The data server also stores status and time-stamp.
- The data server optionally can operate as a historian using an in-memory circular buffer storing values (objects), status and time-stamps.
Check the code and examples at:
http://cid-bdc87ef39b001785.office.live.com/view.aspx/Fsharp/DataServer%5E_doc%5E_1%5E_0.docx
Computer Science Experimentation
Monday, May 23, 2011
Thursday, May 5, 2011
F# interactive - how to hide/show the console
The F# interactive allows great object visibility and interactive capabilities even in running applications like server processes. So, it is a good idea to keep the F# interactive console available even in production versions of the code. But, it is not acceptable to leave several consoles open in the computer screen. The idea of this document is to show that consoles can be hidden and be shown as needed.
The following code shows how to manage application (start) and hide/show the F# interactive console applications.
The following code shows how to manage application (start) and hide/show the F# interactive console applications.
//module ApplMgm
open System
open System.Diagnostics
open System.Collections.Generic
open System.Runtime.InteropServices;
System.Console.Title<-"AppMgm"
[< dllimport("user32.dll")>]
extern IntPtr FindWindow(string lpClassName,string lpWindowName);
[<dllimport("user32.dll")>]
extern bool ShowWindow(IntPtr hWnd, int nCmdShow)
let fsharpPath= @"C:\Program Files (x86)\Microsoft F#\v4.0\fsi.exe"
type Process={Path:string;ExecName:string;ConsoleName:string}
type AppMgm()=
let mutable applications= new Dictionary()
let fsharpPath= @"C:\Program Files (x86)\Microsoft F#\v4.0\fsi.exe"
member this.Apps with get() = applications and set a = applications <- a
member this.fsharpFullPath= @"C:\Program Files (x86)\Microsoft F#\v4.0\fsi.exe"
member this.start(keyName:string) =
let procPath=applications.[keyName].Path
let procName=applications.[keyName].ExecName
Process.Start(fsharpPath,@"--readline+ --load:"+"\""+procPath+procName+"\"") |>ignore
()
member this.hide(keyName:string) =
let consName=applications.[keyName].ConsoleName
let hWnd = FindWindow(null, consName)
if (hWnd <> 0n) then
let b1=ShowWindow(hWnd, 0); // 0 = SW_HIDE
()
member this.show(keyName:string) =
let consName=applications.[keyName].ConsoleName
let hWnd = FindWindow(null, consName)
if (hWnd <> 0n) then
let b1=ShowWindow(hWnd, 1); // 1 = SW_SHOWNORMA
()
//--------------------------------------------------------------------------------------------------
//Configuration
let A= new AppMgm()
let d= new Dictionary()
let projPath= @"C:\Users\caxelrud\Documents\Visual Studio 2010\Projects\Utilities\"
d.Add("ap1",{Path=projPath;ExecName="app_1.fsx";ConsoleName="Application1"})
d.Add("ap2",{Path=projPath;ExecName="app_2.fsx";ConsoleName="Application2"})
//d.["ap2"]
A.Apps<-d
//A.Apps
A.start("ap1")
A.hide("ap1")
A.show("ap1")
Friday, February 18, 2011
F# Linear Algebra with Blas and Lapack
I am testing the F# MathProvider library (http://mathprovider.codeplex.com/).
Mathprovider site has a very interesting section called "About the Performance" that I recommend.
For now, I tried the following sample code with no problems.
Mathprovider site has a very interesting section called "About the Performance" that I recommend.
For now, I tried the following sample code with no problems.
// reference F# PowerPack & MathProvider
#r @"FSharp.PowerPack.dll"
//#r @"D:\FSharp\math-provider\MathProvider\MathProvider\bin\Release\MathProvider.dll"
#r @"C:\cs\Others\MathProvider\Net 4.0\MathProvider.dll"
#time
// rename the module name
module L = MathProvider.LinearAlgebra
// set the directory of the Lapack runtime (blas.dll & lapack.dll)
System.Environment.CurrentDirectory <- @"C:\cs\Others\MathProvider\LapackRuntime"
//System.Environment.CurrentDirectory <- @"D:\FSharp\math-provider\LapackRuntime\Netlib"
//System.Environment.CurrentDirectory <- @"D:\FSharp\math-provider\LapackRuntime\MKL"
// start the native provider, otherwise (implementent) F# implementation will be used
L.startProvider()
// create two 3x3 matrices
let A = matrix [ [12.; -51.; 4.; ]; [6.; 167.; -68.;]; [-4.; 24.; -41.; ] ]
let B = matrix [ [ 2.; 1.; 1.;] ; [ 1.; 2.; 1.;]; [ 1.; 1.; 2.;] ]
// determinant
let det = L.det A
// inverse
let inv = L.inv A
// qr decomposition
let q, r = L.qr A
// lu decomposition
let p, l, u = L.lu A
// cholesky decomposition
let ch = L.chol B
// svd decomposition
let v, s, ut = L.svd A
// eigen decomposition for symetric matrix
let a, b = L.cov A A |> L.eigenSym
(* result *)
(*
val A : matrix = matrix [[12.0; -51.0; 4.0]
[6.0; 167.0; -68.0]
[-4.0; 24.0; -41.0]]
val B : matrix = matrix [[2.0; 1.0; 1.0]
[1.0; 2.0; 1.0]
[1.0; 1.0; 2.0]]
val det : float = -85750.0
val inv : matrix = matrix [[0.06081632653; 0.02326530612; -0.03265306122]
[-0.006040816327; 0.005551020408; -0.009795918367]
[-0.009469387755; 0.0009795918367; -0.02693877551]]
val r : matrix = matrix [[-14.0; -21.0; 14.0]
[0.0; -175.0; 70.0]
[0.0; 0.0; -35.0]]
val q : matrix = matrix [[-0.8571428571; 0.3942857143; 0.3314285714]
[-0.4285714286; -0.9028571429; -0.03428571429]
[0.2857142857; -0.1714285714; 0.9428571429]]
val u : matrix = matrix [[12.0; -51.0; 4.0]
[0.0; 192.5; -70.0]
[0.0; 0.0; -37.12121212]]
val p : (int -> int)
val l : matrix = matrix [[1.0; 0.0; 0.0]
[0.5; 1.0; 0.0]
[-0.3333333333; 0.03636363636; 1.0]]
val ch : matrix = matrix [[1.414213562; 0.7071067812; 0.7071067812]
[0.0; 1.224744871; 0.4082482905]
[0.0; 0.0; 1.154700538]]
val v : matrix = matrix [[-0.2543778627; -0.5139835724; -0.81921474]
[0.9464104305; 0.0419982359; -0.3202240549]
[0.1989954776; -0.8567712854; 0.4757559925]]
val ut : matrix = matrix [[0.00960262784; 0.922507462; -0.385859783]
[-0.07574450365; 0.3854399898; 0.9196188256]
[-0.9970810196; -0.0203960004; -0.0735761066]]
val s : Vector = vector [|190.5672437; 32.85688323; 13.69492038|]
val b : Matrix = matrix [[0.9970810196; -0.07574450365; -0.00960262784]
[0.0203960004; 0.3854399898; -0.922507462]
[0.0735761066; 0.9196188256; 0.385859783]]
val a : vector = vector [|187.5508443; 1079.574776; 36315.87438|]
*)
Thursday, February 17, 2011
Dynamic Compilation and Execution using F#
Dynamic Compilation and Execution using F#
Celso Axelrud
Feb/17/2011
The following example shows how to compile a module from a string inside an executable and make it available to be used by the same executable.
For better understanding of the steps, the example uses F# interactive.
Here is the code:
#r "FSharp.Compiler.dll"
#r "FSharp.Compiler.CodeDom.dll"
#I @"C:\Users\caxelrud\Documents\Visual Studio 2010\Projects\NLMPC"
open System
open System.IO
open System.CodeDom.Compiler
open Microsoft.FSharp.Compiler.CodeDom
(* For this example, copy Fsharp.Core.* from C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\2.0\Runtime\v4.0 and FSharp.Powerpack.dll to the local directory*)
let CompileFSharpString(str, assemblies, output) =
use pro = new FSharpCodeProvider()
let opt = CompilerParameters(assemblies, output)
let res = pro.CompileAssemblyFromSource( opt, [|str|] )
printfn "%A" res.Errors
if res.Errors.Count = 0 then
Some(FileInfo(res.PathToAssembly))
else None ;;
let (++) v1 v2 = Path.Combine(v1, v2)
let path= @"C:\Users\caxelrud\My Documents\Visual Studio 2010\Projects\NLMPC"
let defaultAsms = [|path++"System.dll"; path++"FSharp.Core.dll"; path++"FSharp.Powerpack.dll"|]
let randomFile() = path ++ Path.GetRandomFileName() + ".dll";;
//let randomFile() = __SOURCE_DIRECTORY__ ++ Path.GetRandomFileName() + ".dll"
type System.CodeDom.Compiler.CodeCompiler with
static member CompileFSharpString (str, ?assemblies, ?output) =
let assemblies = defaultArg assemblies defaultAsms
let output = defaultArg output (randomFile())
printfn "%A %s" assemblies output
CompileFSharpString(str, assemblies, output);;
// Module definition
let library = "
module Temp.Main
let f(x,y) = sin x + cos y
" ;;
// Create the assembly
let fileinfo = CodeCompiler.CompileFSharpString(library);;
// Just testing in the interactive env.
#r "zmm30vu5.eqq.dll"
let a = Temp.Main.f(0.5 * Math.PI, 0.0);; // val a : float = 2.0
// Purely reflective invocation of the function.
let asm = Reflection.Assembly.LoadFrom(fileinfo.Value.FullName)
let mth = asm.GetType("Temp.Main").GetMethod("f")
// Wrap weakly typed function with strong typing.
let f(x,y) = mth.Invoke(null, [|box (x:float); box (y:float)|]) :?> float;;
// Call the function
let b = f (0.5 * Math.PI, 0.0);; // val b : float = 2.0
Sunday, January 30, 2011
Linear Programming in F#
Linear Programming in F#
Celso Axelrud
Jan/30/2011
Introduction
This document presents a Linear Programming function developed in F# and executed in the interactive environment.
I am targeting small LP programs on very fast computers.
I selected the simplest and shortest code from all references. It means that has no linear algebra operation and not subroutine calls.
The F# code presented here is based on the subroutine Linpro.
Fortran version at:
http://www.sie.arizona.edu/faculty/addenda/yak/SIE270/for/linpro
Pascal version at:
http://www.sie.arizona.edu/faculty/addenda/yak/SIE270/pas/linpro.pas
There are other references to a subroutine Linpro but it is not the same code.
Fortran version at:
http://www.slac.stanford.edu/accel/ilc/codes/lcopt/source/linpro.f
Python version at:
http://adorio-research.org/wordpress/?p=194
Source Code and Results
Celso Axelrud
Jan/30/2011
Introduction
This document presents a Linear Programming function developed in F# and executed in the interactive environment.
I am targeting small LP programs on very fast computers.
I selected the simplest and shortest code from all references. It means that has no linear algebra operation and not subroutine calls.
The F# code presented here is based on the subroutine Linpro.
Fortran version at:
http://www.sie.arizona.edu/faculty/addenda/yak/SIE270/for/linpro
Pascal version at:
http://www.sie.arizona.edu/faculty/addenda/yak/SIE270/pas/linpro.pas
There are other references to a subroutine Linpro but it is not the same code.
Fortran version at:
http://www.slac.stanford.edu/accel/ilc/codes/lcopt/source/linpro.f
Python version at:
http://adorio-research.org/wordpress/?p=194
Source Code and Results
(*
Linpro - this function computes the solution of a linear programming
problem by using the Simplex method. The Objective function
is maximized. Parameters a,b, and c are presumed transmitted
as global variables declared in the calling program
Usage:
Linpro n m1 m2 m3
Parmeters:
a = matrix of coefficients (global)
b = right hand side vector (global)
c = coefficients of the objective function(global)
Input:
n = number of variables
m1 = number of constraints of the Le type
m2 = number of constraints of the Eq type
m3 = number of constraints of the Ge type
Output:
x.[i] = i-th component of the optimal solution
for i = 1, ... ,N
x.[n+1] = optimal value of the objective function
Kod = key showing the category of the Lp problem
if Kod = 1 then an optimal exists
if Kod = 2 then no feasible solution exists
if Kod = 3 then the objective function is not
bounded
Remarks:
matrix a needs to be re-assign before calling the function again
Conditions:
M1+M2+M3 must be less then or equal to 50
N must not be larger then 50 unless the subroutine is
redimenstion
Example:
Maximize Z = f(x,y) = 3x + 2y
subject to: 2x + y ≤ 18
2x + 3y ≤ 42
3x + y ≤ 24
x ≥ 0 , y ≥ 0
result:
(x,y) = (3,12)
Z=33
*)
let a:float[,]= Array2D.zeroCreate 52 151
let b:float[]= Array.zeroCreate 50
let c:float[]= Array.zeroCreate 50
//Linpro
let Linpro n m1 m2 m3 =
let x:float array = Array.zeroCreate 50
let mutable i,i0,i1,i2=0,0,0,0
let mutable j,j0,j1,j2,L=0,0,0,0,0
let mutable L1,L2,L3,Kod=0,0,0,0
let mutable m4,m5,mm,n1,n5,nm,nn=0,0,0,0,0,0,0
let mutable key=0
let mutable ifirst=0
let mutable u=0.0
let mutable Key=0
let eps=1.0e-3
let kk:int array = Array.zeroCreate 150
//Load up the initial tableau with slack and artificial
//variables, original and secondary objective functions
L1<- n + 1
L2<- m1+m2+m3+1
n1<- n + 1
//set the size of the appended matrix
mm<- m1+m2+m3+2
nn<- n+m1+m2+(2*m3)+1
//initialize both objectives to zero
for i= L2 to mm do
for j= 1 to nn do
a.[i,j]<- 0.0
L3<- m1+m2+m3
// initialize all slack and artificial coefficients to zero
for i= 1 to L3 do
for j= L1 to nn do
a.[i,j]<- 0.0
L<-n
if m1 > 0 then
// Fix slack coefficients for Le constraints
for i= 1 to m1 do
a.[i,L+i]<- 1.0
L<- L + m1
if m3 > 0 then
// Fix slack coefficients for Ge constraints
for i= 1 to m3 do
i1<-m1+m2+i
a.[i1,L+i]<- -1.0
L<- L + m3
m4<- m2 + m3
if (m4 > 0) then
// Fix artificial coefficient for for Eq and Ge constraints
for i= 1 to m4 do
a.[m1+i,L+i]<- 1.0
m5<- mm - 1
// Place the objective function
for j= 1 to n do
a.[m5,j]<- c.[j]
i2<- m1+m2+m3;
if (m4 > 0) then
i1<- m1 + 1
//construct the secondary objective function
for j= 1 to n do
for i= i1 to i2 do
a.[mm,j]<- a.[mm,j] + a.[i,j]
j1<- n+m1+1
j2<- n+m1+m3
if (m3 > 0) then
for j= j1 to j2 do
a.[mm,j]<- -1.0
for i= i1 to i2 do
a.[mm,nn]<- a.[mm,nn] + b.[i]
for i= 1 to i2 do
a.[i,nn]<- b.[i]
a.[m5,nn]<- 0.0
// Load up the initial basic solution
if (m1 > 0) then
for i= 1 to m1 do
kk.[i]<- i + n
if (m4 > 0) then
i1<- m1 + 1
for i= i1 to i2 do
kk.[i]<- n+m1+m3+i-i1+1
else
//If no constraint of Eq and Gt type, then no secondary
// objective function is needed
mm<- mm - 1
ifirst<- 0
u<- 0.0
while (ifirst = 0) || (u > eps) do
ifirst<- 1
n5<- nn - 1;
// Check for the pivot element find the largest
// coefficient of the objective
u<-a.[mm,1]
j0<- 1
for j= 1 to n5 do
if (a.[mm,j] > u) then
u<- a.[mm,j]
j0<- j
if (u > eps) then
Key<- 0
i0<- 0
while (Key = 0) && (i0 <= i2) do
i0<- i0 + 1
if (a.[i0,j0] >= eps) then Key<- 1
// Test if there is a positive a[i,j] in this column
// if not then the objective function is bounded
if (Key = 0) then
Kod<- 3
//goto 10;
else
if (i0 < i2) then
i1<- i0 + 1
u<- a.[i0,nn]/a.[i0,j0]
for i= i1 to i2 do
if (a.[i,j0] >= eps) then
if ((a.[i,nn]/a.[i,j0]) < u) then
i0<- i
u<- a.[i,nn]/a.[i,j0]
//Perform the elimination step
u<- a.[i0,j0]
for j= 1 to nn do
a.[i0,j]<- a.[i0,j]/u
for i= 1 to mm do
if (i <> i0) then
for j= 1 to nn do
if (j <> j0) then
a.[i,j]<- a.[i,j]-a.[i0,j]*a.[i,j0]
for i= 1 to mm do
if (i <> i0) then
a.[i,j0]<- 0.0;
//Register the newest basis vector and go back to
//perform the next step of elimination
kk.[i0]<- j0
else if (m4 > 0) then
L<- n+m1+m3;
// Check for feasible solution from the secondary
//objective
for i= 1 to i2 do
if (kk.[i] > L) then
Kod<- 2
//goto 10;
if Kod<>2 then
mm<- mm - 1
// Remove artificail variables and the secondary
//objective function
m4<- 0
nm<- nn-m2-m3
for i= 1 to mm do
a.[i,nm]<- a.[i,nn]
nn<- nm
ifirst<- 0
//From here we go back to continue elimination
if Kod<>2 || Kod<>3 then Kod<- 1
// Set up the optimal solution
for i= 1 to n do
x.[i]<- 0.0
for i= 1 to i2 do
j<- kk.[i];
x.[j]<- a.[i,nn]
x.[n+1]<- -a.[mm,nn]
x,Kod
(*
//Example 1
let n=2
let m1=3
let m2,m3=0,0
c.[1..2]<-[|3.0;2.0|]
b.[1..3]<-[|18.0;42.0;24.0|]
a.[1..3,1..2]<-array2D [[2.0;1.0];[2.0;3.0];[3.0;1.0]]
let r=Linpro n m1 m2 m3
printfn "LP category: %i" (snd r)
printfn "LP Objective Function: %A" ((fst r).[3])
printfn "LP Varaibles: %A" ((fst r).[1..2])
(*
>
LP category: 0
LP Objective Function: 33.0
LP Varaibles: [|3.0; 12.0|]
*)
*)
//Example 2
(*
R = –2x + 5y, subject to:
x <= 200
y <= 170
x >= 100
y >= 80
y + x >= 200
Solution: x=100,y=170
*)
let n=2
let m1=2
let m2=0
let m3=3
c.[1..2]<-[|-2.0;5.0|]
b.[1..3]<-[|200.0;170.0;100.0;80.0;200.0|]
a.Initialize()
a.[1..5,1..2]<-array2D [[1.0;0.0];[0.0;1.0];[1.0;0.0];[0.0;1.0];[1.0;1.0]]
let r=Linpro n m1 m2 m3
printfn "LP category: %i" (snd r)
printfn "LP Objective Function: %A" ((fst r).[3])
printfn "LP Varaibles: %A" ((fst r).[1..2])
Sunday, January 9, 2011
OPC.NET client in F#
Introduction
This document presents an example of OPC client in F# interactive using OPC foundation OPC .NET API 1.
The website http://www.opcconnect.com/dotnet.php describes several options on how to use OPC with .NET. Here is part of the text:
"Using .NET for client development
Microsoft doesn't expect us to throw out all our COM code just yet. In fact there are well defined mechanisms for bridging from the .NET virtual machine (the Common Language Runtime) to the old world of COM servers.
An early option, using the OPC Automation interfaces, was to add a reference to the Automation server or DLL directly to a .NET project. Coding with C# or another .NET language then became reasonably straightforward. However, see this discussion (via Google Groups) about problems with the OPC Automation wrapper.
OPC Foundation .NET API and Runtime Callable Wrappers
OPC Foundation supplies a set of Runtime Callable Wrappers (RCWs), allowing OPC custom interfaces to be accessed from .NET clients.
These .NET wrappers are available to all as part of the OPC Core Components set. RCWs are provided for all published specifications, but still leave much COM interop work to be done by the developer.
A better option for Foundation members is to download the OPC .NET API, which supports DA 2 and 3, DX and HDA. The .NET API provides a unified set of interfaces for accessing both COM and SOAP/XML servers, and also includes C# and VB.NET clients which exploit these interfaces.
See this thread from the OPC Foundation Message Board for a comparison of the Runtime Callable Wrappers and the .NET API. This article from Advosol, outlining the requirements of a .NET API, may also be useful.
The Message Board also has this interesting thread on the background to the OPC .NET API. Note that the .NET API does not have the status of a full OPC specification - it is simply an implementation provided as a convenience to OPC Foundation members.
The .NET API requires the .NET Framework version 2.0.
For the low-down on the techniques involved in interfacing COM with .NET, take a look at Adam Nathan's book .NET and COM: The Complete Interoperability Guide."
Example
This example tests most of the functionalities described in "OPC .NET API Overview Version 1.00 Draft 2 December 1, 2003" from OPC Foundation http://www.opcfoundation.org/ .
#r
"C:\cs\Others\NET API 2.00\Source\Bin\OpcNetApi.dll"
#r
"C:\cs\Others\NET API 2.00\Source\Bin\OpcNetApi.Com.dll"
open Opc
open OpcCom
//5.2 Opc.IDiscovery
//5.2.1 EnumerateHosts Method
let se= new OpcCom.ServerEnumerator()
//5.2.2 GetAvailableServers Method
// Show Servers--------------------------------------------------
let servers=se.GetAvailableServers(Opc.Specification.COM_DA_30)
for i in servers do
printfn "(%s) " i.Name
> (Advosol.DA3CBCS)
(ICONICS.SimulatorOPCDA)
//5.1 Opc.Factory
let mFactory = new OpcCom.Factory()
let mURL = new Opc.URL("opcda://localhost/ICONICS.SimulatorOPCDA");;
> val mFactory : Factory
val mURL : URL = opcda://localhost/ICONICS.SimulatorOPCDA
//5.3 Opc.Server
//5.3.1 Constructor
//5.3.2 Properties
//5.3.3 Duplicate Method
//5.3.4 Connect Method
//5.3.5 Disconnect Method
//5.4 Opc.Da.Server
//5.4.1 Constructor
let mserver = new Opc.Da.Server(mFactory, mURL)
//5.4.2 Properties
mserver;;
> val it : Da.Server =
Opc.Da.Server {Filters = 9;
IsConnected = false;
Locale = null;
Name = "ICONICS.SimulatorOPCDA";
Subscriptions = seq [];
SupportedLocales = null;
Url = opcda://localhost/ICONICS.SimulatorOPCDA;}
//5.4.3 Connect Method
let mCredentials = new System.Net.NetworkCredential()
let mConnectData = new Opc.ConnectData(mCredentials);
mserver.Connect(mURL, mConnectData)
// 4.1 Opc.IServer Interface
//4.1.3 GetSupportedLocale Method
//let l=mserver.GetSupportedLocales
//4.1.2 SetLocale Method
//mserver.SetLocale("en-US")
//4.1.1 GetLocale Method
//mserver.GetLocale()
//mserver.GetErrorText
//4.7 Opc.Da.IServer Interface
//4.7.2 SetResultsFilters Method
mserver.SetResultFilters(0x09) //Minimal
//4.7.1 GetResultsFilters Method
mserver.GetResultFilters()
//4.7.3 GetStatus Method
let Sstat=mserver.GetStatus()
Sstat;;
> val it : Da.ServerStatus =
Opc.Da.ServerStatus
{CurrentTime = 1/9/2011 9:57:36 PM;
LastUpdateTime = 1/1/0001 12:00:00 AM;
ProductVersion = "3.12.0";
ServerState = running;
StartTime = 1/9/2011 3:15:22 PM;
StatusInfo = "The server is running normally.";
VendorInfo = "ICONICS Simulator OPC-DA Server and Simulator OPC-AE Server";}
//4.7.5 Read Method
//4.3 Opc>Da.Item Class
let someItems : Opc.Da.Item array = Array.zeroCreate 2
someItems.[0] <- new Opc.Da.Item()
someItems.[0].ItemName <- "TAG_0000";
someItems.[0].ClientHandle <- 0
someItems.[1] <- new Opc.Da.Item()
someItems.[1].ItemName <- "TAG_0001";
someItems.[1].ClientHandle <- 1
mserver.Read(someItems)
mserver.Read([|someItems.[0]|]);;
> val it : Da.ItemValueResult [] =
[|Opc.Da.ItemValueResult {ClientHandle = null;
DiagnosticInfo = null;
ItemName = "TAG_0000";
ItemPath = null;
Key = "TAG_0000
null";
Quality = good;
QualitySpecified = true;
ResultID = S_OK;
ServerHandle = null;
Timestamp = 1/9/2011 5:00:22 PM;
TimestampSpecified = true;
Value = 20.0;}|]
//4.7.5 Write Method
let v1=new Opc.Da.ItemValue(ItemName="TAG_0000",Value=10.0)
let v2=new Opc.Da.ItemValue(ItemName="TAG_0001",Value=11.0)
mserver.Write([|v1;v2|])
mserver.Read([|someItems.[0];someItems.[1]|])
//4.7.6 CreateSubscription (Group)
let g1State = new Opc.Da.SubscriptionState()
g1State.Name<-"Group1"
g1State.ClientHandle<-1
g1State;;
> val it : Da.SubscriptionState = Opc.Da.SubscriptionState {Active = true;
ClientHandle = 1;
Deadband = 0.0f;
KeepAlive = 0;
Locale = null;
Name = "Group1";
ServerHandle = null;
UpdateRate = 0;}
let g1 = mserver.CreateSubscription(g1State)
printfn "Number of groups:%i" mserver.Subscriptions.Count
seq { for i in mserver.Subscriptions -> i.Name }|>Seq.iter (printfn "%s")
> Number of groups:1
Group1
//4.7.8 Browse Method
//4.5 Opc.Da.BrowseElement Class
//4.7.9 Browse Next Method
//4.7.10 GetProperties Method
//4.2 Opc.ItemIdentifier Class
let i0=Opc.ItemIdentifier("TAG_0000")
let i1=Opc.ItemIdentifier("TAG_0001")
let r=mserver.GetProperties([|i0;i1|],null,true)
printfn "DataType:%A" r.[0].[0].Value
printfn "Value: %A" r.[0].[1].Value
printfn "Quality: %A" r.[0].[2].Value
printfn "Timestamp: %A" r.[0].[3].Value;;
> DataType:System.Double
Value: 10.0
Quality: good
Timestamp: 1/9/2011 4:01:33 PM
//4.8 Opc.Da.ISubscription
//4.8.1 DataChanged Event
//4.8.2 GetResultsFilter Method
//4.8.3 SetResultsFilter Method
//4.8.4 GetState Method
let r2=g1.GetState()
r2;;
> val it : Da.SubscriptionState = Opc.Da.SubscriptionState {Active = true;
ClientHandle = 1;
Deadband = 0.0f;
KeepAlive = 0;
Locale = "";
Name = "Group1";
ServerHandle = 17;
UpdateRate = 50;}
//4.8.5 ModifyState Method
//4.8.6 AddItems Method
let r3=g1.AddItems(someItems)
let g1SH=Seq.toList(seq { for i in r3 -> i.ServerHandle })
seq { for i in r3 -> i.ResultID }|>Seq.iter (printfn "%A");;
> S_OK S_OK
//4.8.7 ModifyItems Method
//4.8.8 RemoveItems Method
//4.8.9 Read Method
let someItems_1 : Opc.Da.Item array = Array.zeroCreate 2
someItems_1.[0] <- new Opc.Da.Item()
someItems_1.[0].ServerHandle <- g1SH.[0]
someItems_1.[1] <- new Opc.Da.Item()
someItems_1.[1].ServerHandle <- g1SH.[1]
let r4=g1.Read(someItems_1)
//Opc.Da.ItemProperty
printfn "Value: %A" r4.[0].Value
printfn "Quality: %A" r4.[0].Quality
printfn "Timestamp: %A" r4.[0].Timestamp;;
> Value: 10.0
Quality: good
Timestamp: 1/9/2011 10:01:33 PM
printfn "Value: %A" r4.[1].Value
printfn "Quality: %A" r4.[1].Quality
printfn "Timestamp: %A" r4.[1].Timestamp;;
> Value: 11.0
Quality: good
Timestamp: 1/9/2011 10:01:33 PM
//4.8.10 Write Method
//4.4 Opc.Da.ItemValue Class
let someValues_1 : Opc.Da.ItemValue array = Array.zeroCreate 2
someValues_1.[0]<-new Opc.Da.ItemValue(ItemName="TAG_0000",Value=20.0)
someValues_1.[0].ServerHandle <- g1SH.[0]
someValues_1.[1]<-new Opc.Da.ItemValue(ItemName="TAG_0001",Value=21.0)
someValues_1.[1].ServerHandle <- g1SH.[1]
let r5=g1.Write(someValues_1)
seq { for i in r5 -> i.ResultID }|>Seq.iter (printfn "%A");;
> S_OK S_OK
//4.8.11 BeginReadMethod
//4.8.12 BeginWriteMethod
//4.8.13 CancelMethod
//4.8.14 Refresh Method
//4.8.15 SetEnable Method
//4.8.16 GetEnable Method
//5 Client API
//5.1.2 System Type Property
//5.1.3 UseRemoting Property
//5.1.4 CreateInstance Method
//5.4.5 CreateSubscription Method
//5.5 Opc.Da.Subscription
//5.5.1 Constructor
//5.5.2 Properties
//4.7.7 CancelSubscription
mserver.CancelSubscription(g1)
//5.4.3 Disconnect Method
mserver.Disconnect();;
Saturday, December 25, 2010
WCF with F# interactive – Data Server example
Window Communication Foundation provides a runtime environment for services with the following characteristics:
- Exposes CLR types as services and consumes other services as CLR types. The conversion for the different protocol is done behind the scene, so that all the coding is done as you are in .NET environment.
- Implements the concept of remote objects with concurrency management (introduced by .NET Remoting).
- Implements a set of industrial standard protocols (TCP, HTCP, WSDL, REST)
- Implements fast communication protocol as MSWindows pipes
- Allows multiple bindings for the same .NET service code.
- Provides Hosting.
- Manages Fault, Security and Transactions.
The following example implements a client/server data-server.
Data-server is a service that can be used alone or added to an application engine. It provides efficient storage and retrieve of data. It allows usage of groups of points to improve the reading efficiency. The following example serves float type data. Other types of data as integer, double and string can be added. Another option is to create a generic contract and use multiple servers for each type of data.
The data server provides the same features that OPC-DA, with the addition of the possibility of extra bindings to pipes, html and other web-services protocols. The same way, OPC-DA .NET 3.0 (1.2) is a wrapper on the OPC server (based on COM) to provide the same extra functionalities.
In order to allow an easy environment for development and visualization and dynamic modifications of the application, this example was developed as scripts that are executed in the F# interactive environment. To add intellisense, it is used 2 instances of Visual Studio with F# Interactive.
The first VS is used to develop the service contract and to develop and execute the server in F# interactive.
The second VS is used to develop and execute the client in F# interactive.
This example, a MSWindows pipe binding without any security checks was used.
In order to allow all code development inside F# scripts, the server and client binding configuration was done programmatically. For the same reason, the client works directly with the channel.
The VS were opened in administrator mode to avoid any security blocking (for the file C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe).
The file DS_Contract.fsx implements the data server contract.
This example code includes:
- Definition of DataItem type.
- Definition of IDataServer type with abstract types that includes the signature of all remote methods (interface).
- Definition of DataServer type with the implementation of remote and local methods. The local methods can be only used inside the server host.
This server can be used as stand-alone or embedded in another process, since it executes in a separeted thread to respond to the clients. For the stand-alone case, all the management of data items will be done remotely. For the embedded case, the local application can manage the data items.
The data items are kept in a immutable list, so it can’t be removed or updated after it is inserted. In order to modify it, the application needs to generate the full list of data items. This property provides maximum and consistent data reading speed.
The data server uses a dictionary to relate the item name (tag) to the position of the data list.
In order increase reading speed, the client should create groups of data. The group will hold an array to the data list positions. The group should be deleted if not needed anymore.
DS_Contract.fsx code:
DS_Server.fsx code:
First, the service is started at the server F# interactive window.
Now, we can use local methods to register and manipulate data items in the server F# interactive window.
DS_Client.fsx code:
The client is started at the client F# interactive window.
- Exposes CLR types as services and consumes other services as CLR types. The conversion for the different protocol is done behind the scene, so that all the coding is done as you are in .NET environment.
- Implements the concept of remote objects with concurrency management (introduced by .NET Remoting).
- Implements a set of industrial standard protocols (TCP, HTCP, WSDL, REST)
- Implements fast communication protocol as MSWindows pipes
- Allows multiple bindings for the same .NET service code.
- Provides Hosting.
- Manages Fault, Security and Transactions.
The following example implements a client/server data-server.
Data-server is a service that can be used alone or added to an application engine. It provides efficient storage and retrieve of data. It allows usage of groups of points to improve the reading efficiency. The following example serves float type data. Other types of data as integer, double and string can be added. Another option is to create a generic contract and use multiple servers for each type of data.
The data server provides the same features that OPC-DA, with the addition of the possibility of extra bindings to pipes, html and other web-services protocols. The same way, OPC-DA .NET 3.0 (1.2) is a wrapper on the OPC server (based on COM) to provide the same extra functionalities.
In order to allow an easy environment for development and visualization and dynamic modifications of the application, this example was developed as scripts that are executed in the F# interactive environment. To add intellisense, it is used 2 instances of Visual Studio with F# Interactive.
The first VS is used to develop the service contract and to develop and execute the server in F# interactive.
The second VS is used to develop and execute the client in F# interactive.
This example, a MSWindows pipe binding without any security checks was used.
In order to allow all code development inside F# scripts, the server and client binding configuration was done programmatically. For the same reason, the client works directly with the channel.
The VS were opened in administrator mode to avoid any security blocking (for the file C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe).
The file DS_Contract.fsx implements the data server contract.
This example code includes:
- Definition of DataItem type.
- Definition of IDataServer type with abstract types that includes the signature of all remote methods (interface).
- Definition of DataServer type with the implementation of remote and local methods. The local methods can be only used inside the server host.
This server can be used as stand-alone or embedded in another process, since it executes in a separeted thread to respond to the clients. For the stand-alone case, all the management of data items will be done remotely. For the embedded case, the local application can manage the data items.
The data items are kept in a immutable list, so it can’t be removed or updated after it is inserted. In order to modify it, the application needs to generate the full list of data items. This property provides maximum and consistent data reading speed.
The data server uses a dictionary to relate the item name (tag) to the position of the data list.
In order increase reading speed, the client should create groups of data. The group will hold an array to the data list positions. The group should be deleted if not needed anymore.
DS_Contract.fsx code:
#r "System.ServiceModel"
#r "System.Runtime.Serialization"
//namespace RTE.DataServer
open System
open System.ServiceModel
open System.Collections.Concurrent
open System.Collections.Generic
open System.Runtime.Serialization
[<DataContract(Namespace="http://RTE/DataServer")>]
type DataItem()=
let mutable Tag_=""
let mutable Description_=""
let mutable Value_=0.0
let mutable Status_=0
[<datamember>]
member this.Tag with get()=Tag_ and set x = Tag_<-x
[<datamember>]
member this.Description with get()=Description_ and set x = Description_<-x
[<datamember>]
member this.Value with get()=Value_ and set x = Value_<-x
[<datamember>]
member this.Status with get()=Status_ and set x = Status_<-x
override this.ToString()=
sprintf "Tag=%s, Description=%s Value=%f, Status=%i" Tag_ Description_ Value_ Status_
//service contract
[<Servicecontract (namespace="http://RTE/DataServer" )>]
type IDataServer=
[<operationcontract>]
abstract SetDataValueByName : tag:string -> value:float -> int
[<operationcontract>]
abstract SetDataStatusByName : tag:string -> value:int -> int
[<operationcontract>]
abstract GetDataValueByName : tag:string -> float*int
[<operationcontract>]
abstract RegisterData : tag:DataItem -> int
[<operationcontract>]
abstract RegisterDataArray : tags:DataItem[] -> int
[<operationcontract>]
abstract AddGroup : group:string -> tags:string[] -> int*int[]
[<operationcontract>]
abstract DeleteGroup : group:string -> int
[<operationcontract>]
abstract GetGroup : group:string -> int*DataItem[]
[<operationcontract>]
abstract GetGroupArray : group:string -> int*float[]*int[]
//service implementation
[<Servicebehavior(instancecontextmode=instancecontextmode.single,concurrencymode=concurrencymode.single)>]
type DataServer()=
//Constructor & Fields
let DataDict= new ConcurrentDictionary()
let DataList= new List()
let GroupDict= new ConcurrentDictionary()
let lockObj= ref 0
//--------------------------------------------------------------------
//Properties
member this.dataDict with get()=DataDict
member this.dataList with get()=DataList
member this.groupDict with get()=GroupDict
//--------------------------------------------------------------------
//Local Methods
//--------------------------------------------------------------------
member this.SetDataValueByNameLocal (tag:string) (value:float) =
lock(lockObj) (fun()->
try
let i=DataDict.Item(tag)
DataList.Item(i).Value<-value
0
with
|_ -> 1
)
//--------------------------------------------------------------------
member this.SetDataStatusByNameLocal (tag:string) (status:int) =
lock(lockObj) (fun()->
try
let i=DataDict.Item(tag)
DataList.Item(i).Status<-status
0
with
|_ -> 1
)
//--------------------------------------------------------------------
member this.GetDataValueByNameLocal tag =
lock(lockObj) (fun()->
try
let i=DataDict.Item(tag)
DataList.Item(i).Value,0
with
|_ -> 0.0,1
)
//--------------------------------------------------------------------
//Group Methods
member this.AddGroupLocal (group:string) (tags:string[])=
if GroupDict.ContainsKey(group) then
(1,[||])
else
let l= Array.zeroCreate(tags.Length)
let e= Array.zeroCreate(tags.Length) //mark tags that doesn't exit
//Check if all tags exit
let mutable k=0
let mutable j=0
for i in tags do
if DataDict.ContainsKey(i) then
l.[k]<-DataDict.Item(i)
else
e.[k]<-1
j<-1
k<-k+1
if j=0 then
//
GroupDict.TryAdd(group,l)|>ignore
(j,e)
else
(j,e)
//--------------------------------------------------------------------
member this.DeleteGroupLocal (group:string)=
if GroupDict.ContainsKey(group) then
GroupDict.TryRemove(group)|>ignore
0
else
1
//--------------------------------------------------------------------
member this.GetGroupLocal (group:string)=
if GroupDict.ContainsKey(group) then
let l=GroupDict.Item(group)
let a= Array.zeroCreate(l.Length)
let mutable k=0
for i in l do
a.[k]<-DataList.[i]
k<-k+1
(0,a)
else
(1,[||])
//--------------------------------------------------------------------
member this.GetGroupArrayLocal (group:string)=
if GroupDict.ContainsKey(group) then
let l=GroupDict.Item(group)
let s= Array.zeroCreate(l.Length)
let v : float array= Array.zeroCreate(l.Length)
let mutable k=0
for i in l do
v.[k]<-DataList.[i].Value
s.[k]<-DataList.[i].Status
k<-k+1
(0,v,s)
else
(1,[||],[||])
//--------------------------------------------------------------------
member this.RegisterDataLocal (tag:DataItem) =
lock(lockObj) (fun()->
if DataDict.TryAdd(tag.Tag,DataList.Count) then
DataList.Add(tag)
0
else
1)
//--------------------------------------------------------------------
member this.RegisterDataArrayLocal (tags:DataItem[]) =
//this.RegisterTag(i)
lock(lockObj) (fun()->
if List.fold (&&) false [for t in tags ->DataDict.ContainsKey(t.Tag)] then
1
else
for i in tags do
DataDict.TryAdd(i.Tag,DataList.Count)|>ignore
DataList.Add(i)
0)
//--------------------------------------------------------------------
interface IDataServer with
member this.AddGroup (group:string) (tags:string[])=
this.AddGroupLocal group tags
//----------------------------------------------------------------
member this.DeleteGroup group=
this.DeleteGroupLocal group
//-----------------------------------------------------------------
member this.GetGroup group=
this.GetGroupLocal group
//----------------------------------------------------------------
member this.SetDataValueByName tag value =
this.SetDataValueByNameLocal tag value
//----------------------------------------------------------------
member this.GetDataValueByName tag =
this.GetDataValueByNameLocal tag
//----------------------------------------------------------------
member this.SetDataStatusByName tag status =
this.SetDataStatusByNameLocal tag status
//----------------------------------------------------------------
member this.RegisterData (tag:DataItem) =
this.RegisterDataLocal(tag)
//----------------------------------------------------------------
member this.RegisterDataArray (tags:DataItem[])=
this.RegisterDataArrayLocal(tags)
//----------------------------------------------------------------
member this.GetGroupArray (group:string)=
this.GetGroupArrayLocal group
//----------------------------------------------------------------
The file DS_Server.fsx implements the Data Server host. DS_Server.fsx code:
First, the service is started at the server F# interactive window.
//Server Host
#r "System.ServiceModel"
#r "System.Runtime.Serialization"
//namespace RTE.DataServer
open System
open System.ServiceModel
open System.Collections.Concurrent
open System.Collections.Generic
open System.Runtime.Serialization
#I @"C:\Users\caxelrud\Documents\Visual Studio 2010\Projects\MailboxWCF"
#load "DS_Contract.fsx"
let DS= new DS_Contract.DataServer()
let h=
//System.ServiceModel
let baseAddress= new Uri("net.pipe://RTE/dataserver/ds1")
let s = new ServiceHost(DS,[|baseAddress|])
//Programmatic Binding
let binding=
let b=
new NetNamedPipeBinding(securityMode=NetNamedPipeSecurityMode.None)
b
s.AddServiceEndpoint(typeof,binding,baseAddress) |>ignore
s
h.Open() Now, we can use local methods to register and manipulate data items in the server F# interactive window.
// Test Local
//Item
let tag1=new DS_Contract.DataItem(Tag="Tag_1",Description="my tag 1",Value=5.0);;
> val tag1 : DS_Contract.DataItem =
Tag=Tag_1, Description=my tag 1 Value=5.000000, Status=0
let r1=DS.RegisterDataLocal(tag1)
printfn "Tag_1 value=%A" (DS.GetDataValueByNameLocal("Tag_1"));;
> Tag_1 value=(5.0, 0)
let r2=DS.SetDataValueByNameLocal "Tag_1" 10.0
printfn "Tag_1 value=%A" (DS.GetDataValueByNameLocal("Tag_1"));;
> Tag_1 value=(10.0, 0)
printfn "tag 1=%f" (DS.dataList.Item(DS.dataDict.Item("Tag_1")).Value) //only local operation
> tag 1=10.000000
//Array
let tag2=new DS_Contract.DataItem(Tag="Tag_2",Description="my tag 2",Value=2.0)
let tag3=new DS_Contract.DataItem(Tag="Tag_3",Description="my tag 3",Value=3.0)
let tag4=new DS_Contract.DataItem(Tag="Tag_4",Description="my tag 4",Value=4.0)
let r3=DS.RegisterDataArrayLocal([|tag2;tag3;tag4|])
printfn "value=%A" (DS.GetDataValueByNameLocal("Tag_2"))
> value=(2.0, 0)
//only local FSI
DS.dataDict;;
> val it : ConcurrentDictionary =
dict [("Tag_2", 1); ("Tag_4", 3); ("Tag_1", 0); ("Tag_3", 2)]
DS.dataList;;
> val it : List =
seq
[Tag=Tag_1, Description=my tag 1 Value=10.000000, Status=0
{Description = "my tag 1";
Status = 0;
Tag = "Tag_1";
Value = 10.0;};
Tag=Tag_2, Description=my tag 2 Value=2.000000, Status=0
{Description = "my tag 2";
Status = 0;
Tag = "Tag_2";
Value = 2.0;};
Tag=Tag_3, Description=my tag 3 Value=3.000000, Status=0
{Description = "my tag 3";
Status = 0;
Tag = "Tag_3";
Value = 3.0;};
Tag=Tag_4, Description=my tag 4 Value=4.000000, Status=0
{Description = "my tag 4";
Status = 0;
Tag = "Tag_4";
Value = 4.0;}]
//Group
let r4=DS.AddGroupLocal "G1" [|"Tag_1";"Tag_2"|];;
> val r4 : int * int [] = (0, [|0; 0|])
let r5=DS.AddGroupLocal "G2" [|"Tag_1";"Tag_2";"Tag_4"|]
> val r5 : int * int [] = (0, [|0; 0; 0|])
let r6=DS.GetGroupLocal "G1";;
> val r6 : int * DS_Contract.DataItem [] =
(0,
[|Tag=Tag_1, Description=my tag 1 Value=10.000000, Status=0;
Tag=Tag_2, Description=my tag 2 Value=2.000000, Status=0|])
printfn "transaction status=%d, value=%A" (fst r6) (snd r6);;
> transaction status=0, value=[|Tag=Tag_1, Description=my tag 1 Value=10.000000, Status=0;
Tag=Tag_2, Description=my tag 2 Value=2.000000, Status=0|]
let r7,r8,r9=DS.GetGroupArrayLocal "G1"
printfn "transaction status=%d, value=%A, status=%A" r7 r8 r9;;
> transaction status=0, value=[|10.0; 2.0|], status=[|0; 0|]
//only local FSI
DS.groupDict;;
> val it : ConcurrentDictionary =
dict [("G2", [|0; 1; 3|]); ("G1", [|0; 1|])]
//-------------------------------------------------------------------------
Console.WriteLine("Server is running. Press return to exit");;
Console.ReadLine()|>ignore
h.Close() //close it before updates !
DS_Client.fsx code:
The client is started at the client F# interactive window.
//Client
#r "System.ServiceModel"
#r "System.Runtime.Serialization"
open System
open System.ServiceModel
open System.Collections.Concurrent
open System.Collections.Generic
open System.Runtime.Serialization
#I @"C:\Users\caxelrud\Documents\Visual Studio 2010\Projects\MailboxWCF"
#load "DS_Contract.fsx"
let binding=new NetNamedPipeBinding(securityMode=NetNamedPipeSecurityMode.None)
let address=new EndpointAddress("net.pipe://RTE/dataserver/ds1")
let factory= new ChannelFactory(binding,address)
let channel=factory.CreateChannel()
// Remote Test
let tag10=new DS_Contract.DataItem(Tag="Tag_10",Description="my tag 10",Value=5.0);;
> val tag10 : DS_Contract.DataItem =
Tag=Tag_10, Description=my tag 10 Value=5.000000, Status=0
let r1=channel.RegisterData(tag10)
printfn "Tag_10 value=%A" (channel.GetDataValueByName("Tag_10"));;
> Tag_10 value=(5.0, 0)
let r2=channel.SetDataValueByName "Tag_10" 10.0
printfn "Tag_10 value=%A" (channel.GetDataValueByName("Tag_10"));;
> Tag_10 value=(10.0, 0)
//Array
let tag20=new DS_Contract.DataItem(Tag="Tag_20",Description="my tag 20",Value=2.0)
let tag30=new DS_Contract.DataItem(Tag="Tag_30",Description="my tag 30",Value=3.0)
let tag40=new DS_Contract.DataItem(Tag="Tag_40",Description="my tag 40",Value=4.0)
let r3=channel.RegisterDataArray([|tag20;tag30;tag40|])
printfn "value=%A" (channel.GetDataValueByName("Tag_20"));;
> value=(2.0, 0)
//Group
let r4=channel.AddGroup "G10" [|"Tag_10";"Tag_20"|];;
let r5=channel.AddGroup "G20" [|"Tag_10";"Tag_20";"Tag_40"|];;
let r6=channel.GetGroup "G10";;
printfn "transaction status=%d, value=%A" (fst r6) (snd r6);;
> transaction status=0, value=[|Tag=Tag_10, Description=my tag 10 Value=10.000000, Status=0;
Tag=Tag_20, Description=my tag 20 Value=2.000000, Status=0|]
let r7,r8,r9=channel.GetGroupArray "G10"
printfn "transaction status=%d, value=%A, status=%A" r7 r8 r9;;
> transaction status=0, value=[|10.0; 2.0|], status=[|0; 0|]
Subscribe to:
Posts (Atom)