aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README9
-rw-r--r--dispatch.c160
-rw-r--r--dispatch.h21
-rw-r--r--gpib_function.c352
-rw-r--r--gpib_function.mexglxbin0 -> 18750 bytes
-rw-r--r--gpib_test.m17
-rw-r--r--lgpib.m99
7 files changed, 658 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..3c760c7
--- /dev/null
+++ b/README
@@ -0,0 +1,9 @@
+This original files were found at
+http://code.google.com/p/matlab-gpib/
+
+and cloned with the following command
+hg clone https://code.google.com/p/matlab-gpib/
+
+This code is distributed under GNU GPL v2 license
+
+
diff --git a/dispatch.c b/dispatch.c
new file mode 100644
index 0000000..11f46f5
--- /dev/null
+++ b/dispatch.c
@@ -0,0 +1,160 @@
+#include "math.h"
+#include "mex.h"
+#define DISPATCH_C
+#include "dispatch.h"
+
+
+/* Matlab Bridge (c) 2010 Richard George, University of Oxford
+ *
+ * Compile by typing the following from the Matlab console:
+ *
+ * mex [other_module.c] dispatch.c
+ */
+
+int counter = 0;
+
+extern DispatchTableEntry dispatch_table[];
+
+
+char *help_help="List the available functions\n";
+
+int runDispatchTable(DispatchTableEntry functions[], const char *request, int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]);
+
+int help_nonspecific_handler(int nlhs, mxArray*plhs[], int nrhs, const mxArray* prhs[]);
+int help_specific_handler(int nlhs, mxArray*plhs[], int nrhs, const mxArray* prhs[]);
+
+/* Example dispatch table - define a function called 'help' that can print the contents of the dispatch table */
+
+DispatchTableEntry internal_dispatch_table[] = {
+ { "help" , 0 , 0 , help_nonspecific_handler, &help_help},
+ { "help" , 0 , 1 , help_specific_handler, 0 },
+ /* Sentinel */
+ { 0, 0, 0, 0, 0}
+};
+
+int help_nonspecific_handler(int nlhs, mxArray*plhs[], int nrhs, const mxArray* prhs[]) {
+ displayValidFunctions(dispatch_table);
+}
+
+int help_specific_handler(int nlhs, mxArray*plhs[], int nrhs, const mxArray* prhs[]) {
+ if (nrhs==1) {
+ const char *request = mxArrayToString(prhs[0]);
+ mexPrintf("help for %s:\n\n", request);
+ int i;
+ for (i=0;dispatch_table[i].function_name!=0;i++) {
+ if (strcmp(request, dispatch_table[i].function_name)==0) {
+ if ((*(dispatch_table[i].help))!=0) mexPrintf("%s\n", *(dispatch_table[i].help));
+ }
+ }
+ }
+ return 0;
+}
+
+/* This code searches the dispatch table, looking for a match and checking the number of arguments passed */
+
+int runDispatchTable(DispatchTableEntry functions[], const char *request, int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
+ int result;
+ int i;
+ int k=0;
+
+ int flag_rhs_error=0;
+ int flag_lhs_error=0;
+
+ for (i=0;functions[i].function_name!=0;i++) {
+ /* mexPrintf("runDispatchTable: Comparing %s %s\n", functions[i].function_name, request); */
+ if (strcmp(request, functions[i].function_name)==0) {
+ if (nlhs==functions[i].nlhs) {
+
+ if ((nrhs-1)==functions[i].nrhs) {
+ result = functions[i].handler(nlhs, &plhs[0], nrhs-1, &prhs[1]);
+ return 1;
+ }
+ else {
+ flag_rhs_error=1;
+ k=i;
+ }
+
+ }
+ else {
+ flag_lhs_error=1;
+ k=i;
+ }
+ }
+ }
+
+ if (flag_rhs_error) mexPrintf("Expecting %d parameter(s) on the right of function %s, but found %d\n", functions[k].nrhs,request, nrhs-1);
+ if (flag_lhs_error) mexPrintf("Expecting %d parameter(s) on the left of function %s, but found %d\n", functions[k].nlhs,request, nlhs);
+
+ if ((flag_lhs_error==1) || (flag_rhs_error==1)) return -2;
+ return -1;
+}
+
+/* This function prints the functions defined in a dispatch table passed in as the argument */
+
+void displayValidFunctions(DispatchTableEntry functions[]) {
+ mexPrintf("Available functions are:\n\n");
+ int i,j;
+ char c;
+
+ for (i=0;functions[i].function_name!=0;i++) {
+ c='A';
+ mexPrintf("function ");
+
+ if (functions[i].nlhs>0) {
+ mexPrintf("[");
+ for (j=0;j<functions[i].nlhs;j++) {
+ if (j>0) mexPrintf(",");
+ mexPrintf("%c",c++);
+ }
+ mexPrintf("]=");
+ }
+
+ mexPrintf("%s(",functions[i].function_name);
+
+ if (functions[i].nrhs>0) {
+ for (j=0;j<functions[i].nlhs;j++) {
+ if (j>0) mexPrintf(",");
+ mexPrintf("%c",c++);
+ }
+ }
+ mexPrintf(")\n");
+ if (functions[i].help!=0) {
+ mexPrintf("\n%s\n\n",*functions[i].help);
+ }
+ }
+}
+
+/* The entry point from MATLAB
+ *
+ * The first argument from Matlab prhs[0] is used to look-up a value in the dispatch table. If a match is found, the handler function is called with the remaining arguments
+ */
+
+void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
+
+ /* Setup on first run */
+ if (counter==0)
+ {
+ mexPrintf("adding exit hook for gpib_function()\n");
+ mexAtExit(ExitFcn);
+ }
+
+ /* Decode the requested feature */
+ if (nrhs>=1) {
+ const char *request = mxArrayToString(prhs[0]);
+ int found;
+
+ found=runDispatchTable(internal_dispatch_table, request, nlhs, plhs, nrhs, prhs);
+
+ if (found<0) {
+ found=runDispatchTable(dispatch_table, request, nlhs, plhs, nrhs, prhs);
+ }
+ }
+ else {
+ /* Display a help message */
+ displayValidFunctions(dispatch_table);
+ }
+
+ counter++;
+ return;
+}
+
diff --git a/dispatch.h b/dispatch.h
new file mode 100644
index 0000000..8d8c819
--- /dev/null
+++ b/dispatch.h
@@ -0,0 +1,21 @@
+#ifndef DISPATCH_H
+#define DISPATCH_H
+
+typedef struct tagDispatchTableEntry {
+ char *function_name;
+ int nlhs;
+ int nrhs;
+ int (*handler)(int,mxArray *[],int,const mxArray *[]);
+ char **help;
+} DispatchTableEntry;
+
+#ifndef DISPATCH_C
+extern int counter;
+extern char *help_help;
+#endif
+
+void displayValidFunctions();
+void ExitFcn();
+int help_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+
+#endif \ No newline at end of file
diff --git a/gpib_function.c b/gpib_function.c
new file mode 100644
index 0000000..9bd120f
--- /dev/null
+++ b/gpib_function.c
@@ -0,0 +1,352 @@
+#include "math.h"
+#include "string.h"
+#include "mex.h"
+#include <gpib/ib.h>
+#include "dispatch.h"
+
+/* Matlab:linux-GPIB Bridge (c) 2010 Richard George, University of Oxford
+ *
+ * Compile by typing the following from the Matlab console:
+ *
+ * mex gpib_function.c dispatch.c -lgpib
+ */
+
+extern DispatchTableEntry dispatch_table[];
+
+int verbose = 2;
+int write_verbose = 0;
+
+int timeout_for_double(const double *t);
+
+int ibfind_handler(int,mxArray*[],int,const mxArray*[]);
+int ibsta_handler(int,mxArray*[],int,const mxArray*[]);
+int ibcntl_handler(int,mxArray*[],int,const mxArray*[]);
+int about_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int quiet_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int verbose_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibrdl_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibrd_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibwrt_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibclr_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibrsp_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibeos_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibeot_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+int ibtmo_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[]);
+
+char *about_desc = "Matlab to linux-GPIB Bridge (c) 2010 Richard George, University of Oxford\n";
+char *ibfind_desc = "[handle]=ibfind('device_name') looks up a device in the /etc/gpib.conf file, and returns a handle to this device\n";
+char *ibwrt_desc = "[status,written_count]=ibwrt(handle,buffer) writes to GPIB-handle, from buffer, using length of string\n";
+char *ibrdl_desc = "[data,status,read_count]=ibrdl(handle) returns up to length bytes from the specified device\n";
+char *ibrd_desc = "[data,status,read_count]=ibrd(handle,length) returns up to 8192 bytes from the specified device\n";
+char *ibclr_desc = "ibclr(handle) performs a device clear on the specified device\n";
+char *ibeos_desc = "ibeos(handle,end_of_string)\n";
+char *ibeot_desc = "iteot(handle,value)\n";
+char *ibtmo_desc = "ibtmo(handle,timeout) Set the read time-out, in seconds. The resolution of timeout is log-spaced, e.g. 100ms,300ms,1s,3s,10s,30s ...\n";
+char *ibsta_desc = "return the value of ibsta\n";
+char *ibcntl_desc = "return the value of ibcntl\n";
+char *ibrsp_desc = "[result]=ibrsp(handle) performs a serial poll on the specified device\n";
+char *quiet_desc = "turn off debug output from other functions in this library\n";
+char *verbose_desc = "turn on debug output from other functions in this library\n";
+
+DispatchTableEntry dispatch_table[] = {
+ /*
+ * Add entries here:
+ *
+ * Matlab name : # LHS args : # RHS args : C function : Help text */
+ { "about" , 0 , 0 , about_handler , &about_desc},
+ { "ibfind" , 1 , 1 , ibfind_handler , &ibfind_desc},
+ { "ibrdl" , 3 , 2 , ibrdl_handler , &ibrdl_desc},
+ { "ibrd" , 3 , 1 , ibrd_handler , &ibrd_desc},
+ { "ibsta" , 1 , 0 , ibsta_handler , &ibsta_desc},
+ { "ibcntl" , 1 , 0 , ibcntl_handler , &ibcntl_desc},
+ { "ibwrt" , 2 , 2 , ibwrt_handler , &ibwrt_desc},
+ { "ibclr" , 1 , 1 , ibclr_handler , &ibclr_desc},
+ { "ibrsp" , 1 , 1 , ibrsp_handler , &ibrsp_desc},
+ { "ibeos" , 1 , 2 , ibeos_handler , &ibeos_desc},
+ { "ibeot" , 1 , 2 , ibeot_handler , &ibeot_desc},
+ { "ibtmo" , 1 , 2 , ibtmo_handler , &ibtmo_desc},
+ { "quiet" , 0 , 0 , quiet_handler , &quiet_desc},
+ { "verbose" , 0 , 0 , verbose_handler , &verbose_desc},
+ /* Sentinel */
+ {0,0,0,0,0}
+};
+
+int timeout_for_double(const double *t)
+{
+ const double valid_timeouts[] = {0.0,10e-6,30e-6,100e-6,300e-6,1e-3,3e-3,10e-3,30e-3,100e-3,300e-3,1.0,3.0,10.0,30.0,100.0,300.0,1000.0};
+
+ if (*t<=0.0) return 0;
+ if (*t>=1000.0) return 17;
+
+ int i=0;
+
+ while ((valid_timeouts[i]<*t)) i++;
+
+ return i;
+}
+
+int ibeot_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ if (nrhs==2)
+ {
+ int iresult = -1;
+ double *handle = mxGetPr(prhs[0]);
+ double *eot = mxGetPr(prhs[0]);
+
+ iresult = ibeot((int)*handle,(int)eot);
+
+ if (nlhs==1)
+ {
+ plhs[0]=mxCreateDoubleScalar((double)iresult);
+ }
+ return 0;
+
+ }
+
+ return -1;
+}
+
+int ibeos_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ if (nrhs==2)
+ {
+ int iresult = -1;
+ double *handle = mxGetPr(prhs[0]);
+ double *eos = mxGetPr(prhs[0]);
+
+ iresult = ibtmo((int)*handle,(int)eos);
+
+ if (nlhs==1)
+ {
+ plhs[0]=mxCreateDoubleScalar((double)iresult);
+ }
+ return 0;
+
+ }
+
+ return -1;
+}
+
+int ibtmo_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+
+ if (nrhs==2)
+ {
+ int iresult = -1;
+ double *handle = mxGetPr(prhs[0]);
+ double *timeout = mxGetPr(prhs[1]);
+ int tmo=timeout_for_double(timeout);
+ if (verbose>3) mexPrintf("Handle = %d, Timeout %g sec => %d\n",(int)*handle,*timeout,tmo);
+
+ iresult = ibtmo((int)*handle,tmo);
+
+ if (nlhs==1)
+ {
+ plhs[0]=mxCreateDoubleScalar((double)iresult);
+ }
+ return 0;
+
+ }
+
+ return -1;
+}
+
+int ibfind_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ const char *device = mxArrayToString(prhs[0]);
+ if (verbose>2) mexPrintf("ibfind('%s')\n",device);
+ int result = ibfind(device);
+ if (verbose>2) mexPrintf("returns %d\n",result);
+ plhs[0] = mxCreateDoubleScalar((double)result);
+ return 0;
+}
+
+int ibsta_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ if (nrhs==1)
+ {
+ plhs[0] = mxCreateDoubleScalar((double)ibsta);
+ }
+
+ return 0;
+}
+
+int ibcntl_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ if (nrhs==1)
+ {
+ plhs[0] = mxCreateDoubleScalar((double)ibcntl);
+ }
+
+ return 0;
+}
+
+int ibwrt_handler(int nlhs, mxArray*plhs[], int nrhs, const mxArray* prhs[]) {
+ if (nrhs==2) {
+ double *handle = mxGetPr(prhs[0]);
+
+ mwSize n=mxGetN(prhs[1]), m=mxGetM(prhs[1]);
+ mxChar *buffer=mxGetChars(prhs[1]);
+
+ char *message = 0;
+ int i;
+
+ message = calloc(n*m+2, sizeof(char));
+
+ if (message!=0) {
+ for (i=0;i<n*m;i++)
+ {
+ message[i]=(char)buffer[i];
+ if (write_verbose>0) mexPrintf("message[%d]=0x%02X",i,(int)((unsigned char)message[i]));
+ if ((write_verbose>0) & (message[i]>32) & (message[i]<126)) mexPrintf(" (%c)",message[i]);
+ if (write_verbose>0) mexPrintf("\n");
+ }
+ message[i]=0;
+ if (write_verbose>0) mexPrintf("calling ibwrt(handle=%d,message,length=%d)\n",(int)*handle,(int)n*m);
+
+ int result = ibwrt((int)*handle, message, n*m);
+
+ if (write_verbose>0) mexPrintf("ibwrt returns 0x%04X, ibcntl=%d\n",result,ibcntl);
+
+ free(message);
+
+ if (nlhs>=1) plhs[0] = mxCreateDoubleScalar((double)result);
+ if (nlhs>=2) plhs[1] = mxCreateDoubleScalar((double)ibcntl);
+ }
+
+ }
+
+ return 0;
+}
+
+int ibrdl_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ if (nrhs==2)
+ {
+ int i;
+ double *handle = mxGetPr(prhs[0]);
+ double *length = mxGetPr(prhs[1]);
+
+ if (nlhs==3)
+ {
+ mwSize lengths[2];
+ /* mexPrintf("sizeof(mxChar)=%d, sizeof(char)=%d\n",sizeof(mxChar),sizeof(char)); */
+
+ char *buffer=calloc(((int)*length)+2,sizeof(char));
+ int result = ibrd((int)*handle,(void *)buffer,(int)*length);
+
+ /* mexPrintf("result = %d, buffer = %s\n",result,buffer); */
+
+ lengths[0]=1;
+ lengths[1]=(mwSize)(ibcntl);
+ plhs[0]=mxCreateCharArray(2,lengths);
+ mxChar *mxCharBuffer=(mxChar *)mxGetPr(plhs[0]);
+ for (i=0;i<ibcntl;i++) mxCharBuffer[i]=(mxChar)buffer[i];
+
+ free(buffer);
+
+ if (nlhs>=1) plhs[1]=mxCreateDoubleScalar((double)result);
+ if (nlhs>=2) plhs[2]=mxCreateDoubleScalar((double)ibcntl);
+ }
+
+ }
+
+ return 0;
+}
+
+int ibrd_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ int i;
+ char *buffer;
+
+ if (nrhs==1)
+ {
+ double *handle = mxGetPr(prhs[0]);
+
+ if (nlhs==3)
+ {
+ mwSize lengths[2];
+ /* mexPrintf("sizeof(mxChar)=%d, sizeof(char)=%d\n",sizeof(mxChar),sizeof(char)); */
+
+ char *buffer=calloc(8193,sizeof(char));
+ int result = ibrd((int)*handle,(void *)buffer,8192);
+
+ /* mexPrintf("result = %d, buffer = %s\n",result,buffer); */
+
+ lengths[0]=1;
+ lengths[1]=(mwSize)(ibcntl);
+ plhs[0]=mxCreateCharArray(2,lengths);
+ mxChar *mxCharBuffer=(mxChar *)mxGetPr(plhs[0]);
+ for (i=0;i<ibcntl;i++) mxCharBuffer[i]=(mxChar)buffer[i];
+
+ free(buffer);
+
+ if (nlhs>=1) plhs[1]=mxCreateDoubleScalar((double)result);
+ if (nlhs>=2) plhs[2]=mxCreateDoubleScalar((double)ibcntl);
+
+ }
+
+ }
+
+ return 0;
+}
+
+int ibclr_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+ {
+ int result = -1;
+
+ if (nrhs==1) {
+ double *handle = mxGetPr(prhs[0]);
+ result = ibclr((int)*handle);
+
+ if (nlhs==1) {
+ plhs[0]=mxCreateDoubleScalar((double)result);
+ }
+ }
+
+ return 0;
+}
+
+int ibrsp_handler(int nlhs,mxArray*plhs[],int nrhs,const mxArray* prhs[])
+{
+ if (nrhs==1)
+ {
+ double *handle = mxGetPr(prhs[0]);
+ char result = 0;
+
+ ibrsp((int)*handle,&result);
+
+ if (nlhs==1)
+ {
+ plhs[0]=mxCreateDoubleScalar((double)result);
+ }
+
+ }
+
+ return 0;
+}
+
+int quiet_handler(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
+{
+ verbose = 0;
+ return 0;
+}
+
+int verbose_handler(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
+{
+ verbose = 1;
+ mexPrintf("gpib_function: debug output is now enabled. Call gpib_function('quiet') to disable.\n");
+ return 0;
+}
+
+/* Example handler */
+int about_handler(int nlhs,mxArray *plhs[],int nrhs,const mxArray *prhs[])
+{
+ mexPrintf("Matlab to Linux-GPIB interface, (c) 2010 Richard George, University of Oxford\n\n");
+ return 0;
+}
+
+void ExitFcn()
+{
+ mexPrintf("Exit function called for gpib_function()\n");
+}
diff --git a/gpib_function.mexglx b/gpib_function.mexglx
new file mode 100644
index 0000000..dde4198
--- /dev/null
+++ b/gpib_function.mexglx
Binary files differ
diff --git a/gpib_test.m b/gpib_test.m
new file mode 100644
index 0000000..8ecf3d4
--- /dev/null
+++ b/gpib_test.m
@@ -0,0 +1,17 @@
+%% Open scope
+%
+% get a handle to the oscilloscope from /etc/gpib.conf
+%
+hscope = gpib_function('ibfind','LECROY_WR');
+%% Write a message
+%
+% Send the *IDN? message to the scope
+%
+[status,write_count] = gpib_function('ibwrt',hscope,'*IDN?');
+fprintf('status = 0x%04X\nwrite_count = %d bytes\n',status,write_count);
+%% Read the reply
+%
+% Show the identifier returned by the LeCroy
+%
+[reply,status,read_count] = gpib_function('ibrdl',hscope,4096);
+fprintf('reply = %s\nstatus = 0x%04X\nread_count = %d bytes\n',strtrim(reply),status,read_count); \ No newline at end of file
diff --git a/lgpib.m b/lgpib.m
new file mode 100644
index 0000000..0d2010e
--- /dev/null
+++ b/lgpib.m
@@ -0,0 +1,99 @@
+classdef lgpib
+
+ properties
+ handle
+ end
+
+ methods
+
+ function [obj] = lgpib(name)
+
+ if exist('gpib_function')~=3
+ lgpib.Compile();
+ end
+
+ obj.handle = gpib_function('ibfind',name);
+
+ if (obj.handle<0)
+ fprintf('lgpib Error: Could not open device ''%s''\n',name);
+ end
+
+ end
+
+ function [status,write_count] = write(obj,message)
+
+ [gpib_status,gpib_count] = gpib_function('ibwrt',obj.handle,message);
+
+ if nargout>=1
+ status = gpib_status;
+ end
+
+ if nargout>=2
+ write_count=gpib_count;
+ end
+ end
+
+ function [reply,status,read_count] = read(obj,l)
+
+ if nargin==1
+ l=8192;
+ end
+
+ [gpib_reply,gpib_status,gpib_count] = gpib_function('ibrdl',obj.handle,l);
+
+ reply = gpib_reply;
+
+ if nargout>=2
+ status = gpib_status;
+ end
+
+ if nargout>=3
+ write_count=gpib_count;
+ end
+ end
+
+ function [reply] = query(obj,message,l)
+ if nargin==2
+ l = 8192;
+ end
+
+ obj.write(message);
+
+ reply = obj.read(l);
+ end
+
+ function [reply] = set_timeout(obj,tmo)
+ reply = gpib_function('ibtmo',obj.handle,tmo);
+ end
+
+ function [reply] = serial_poll(obj)
+ reply = gpib_function('ibrsp',obj.handle);
+ end
+
+ function [status] = get_status(obj)
+ status = gpib_function('ibsta',obj.handle);
+ end
+
+ function [cntl] = get_counter(obj)
+ status = gpib_function('ibcntl',obj.handle);
+ end
+
+ function [status] = clear(obj)
+ status = gpib_function('ibclr',obj.handle);
+ end
+
+ function [status] = interface_clear(obj)
+ status = obj.clear();
+ end
+
+ end
+
+ methods (Static)
+ function Compile
+ fprintf('Compiling gpib_function.c\n');
+ mex gpib_function.c dispatch.c -lgpib
+ end
+ end
+
+end
+ \ No newline at end of file