C# Live Weight Reader Tutorial
Introduction
In this tutorial, we will be creating a simple console application in C# that opens, claims and enables a scale device and provides a live weight readings of items placed on the scale platter. This tutorial only covers a very basic aspect of development using OPOS with C#, but should suffice as a starting point for any application that looks to read weights from a Datalogic scale device.
Prerequisites
Developing an application for a Datalogic scale device requires you to first have Datalogic's OPOS installed. The OPOS installer ensures the required ActiveX Controls are registered and any supporting files are present.
Creating a project
-
Using Visual Studio 2019, navigate to File > New > Project ...
-
In the Create a new project dialog, set the filter to C#, select Console App and select Next
-
In the Configure your new project dialog, declare your Project name, Location and Solution name as desired and then select Next.
-
In the Additional information dialog, make sure the Target Framework is set to .NET 5.0 and then select Create.
-
After the project has initialized, you must create and use an x86 configuration; failure to do this will result in the unhandled exception 80040154 Class not registered (0x80040154 (REGDB_E_CLASSNOTREG)) when you run the application. The Configuration Manager can be accessed by navigating to Build > Configuration Manager....
Exposing OPOS control objects
The very first step is to reference the scale ActiveX Control as a dependency
.
-
Using the Visual Studio 2019 Solution Explorer, right mouse click Dependencies and select Add COM Reference from the context menu.
-
When the Reference Manager dialog appears, filter on the keyword OPOS, select OPOS 1.14.1 Constants and OPOS Scale Control and then select OK.
-
At this point, expanding the Dependencies node of the Solution Explorer, you will see
Interop.OPOSCONSTANTSLib
andInterop.OposScale_CCO
. -
Near the top of your source file, add the statements
using OPOSCONSTANTSLib
andusing OposScale_CCO;
using System;
using OPOSCONSTANTSLib;
using OposScale_CCO;
namespace CSharpScaleApp
{
class Program
{
static void Main(string[] args)
{
}
}
}
Creating the OPOS scale object
Creating the scale object requires you to simply declare and instantiate it. However, if you attempt to build the application at this point you will encounter error CS1752: Interop type 'OPOSScaleClass' cannot be embedded. Use the applicable interface instead.
using System;
using OPOSCONSTANTSLib;
using OposScale_CCO;
namespace CSharpScaleApp
{
class Program
{
private static OPOSScaleClass scale;
static void Main(string[] args)
{
// Create a scale object.
scale = new OPOSScaleClass();
}
}
}
This issue is easily resolved by the following:
-
Expand the Dependencies and COM nodes in the Solution Explorer.
-
Right mouse click on
Interop.OposScale_CCO
and select Properties from the context menu. -
Set
Embed Interop Types
to No.
Accessing the OPOS Scale interface
At this point, you are ready to use the scale
object.
The OPOS standard defines a general sequence to access a device:
- open: open a context to the device
- claim: claim control over the device
- enable: enable the device's operation
- disable: disable the device's operation
- release: release control over the device
- close: close the context to the device
Opening a scale context requires you to reference a device profile (a device profile simply being a name representing a set of parameters relevant to the device). When OPOS is installed, scale profiles are seen as the subkey names installed under the UPOS-specified registry key HKEY_LOCAL_MACHINE\Software\Wow6432Node\OLEforRetail\ServiceOPOS\SCALE
. It is your choice whether you want to programmatically obtain the profile names from the registry or to simply hard-code them in your application.
If the scale context is successfully opened, you can then claim the device, gaining exclusive access to it. And if the device is successfully claimed, you can then enable it to perform subsequent operations.
using System;
using OPOSCONSTANTSLib;
using OposScale_CCO;
namespace CSharpScaleApp
{
class Program
{
private static OPOSScaleClass scale;
static void Main(string[] args)
{
// Create a scale object.
scale = new OPOSScaleClass();
// Open a context with the scale (e.g. "USBScale").
string profileName = "<your selected profile>";
scale.Open(profileName);
// Claim control of the scale using a 1000 millisecond timeout.
scale.ClaimDevice(1000);
if (scale.Claimed)
{
// Enable the scale and event transmission.
scale.DeviceEnabled = true;
scale.DataEventEnabled = true;
//
// more to come ....
//
// Disable and release.
scale.DataEventEnabled = false;
scale.ReleaseDevice();
}
// Finally, close the scale context.
scale.Close();
}
}
}
Implementing event functions
At this point, the scale object can act as a sink (an object that receives input from the device). However, to make the application useful you must connect the scale object to a StatusUpdateEvent()
method, allowing the application to retrieve the live weight data obtained by the scale object.
To see the event methods of the associated interface class _IOPOSScaleEvents_Event
-
Right mouse click on
OPOSScaleClass
in your source code and then select Peek Definition from the context menu. -
Scrolling to the bottom of the displayed window you see four event methods.
-
Placing your cursor over
_IOPOSScaleEvents_StatusUpdateEventEventHandler
you will see the method signature.
For our purposes, we need to provide a fairly simple StatusUpdateEvent() method.
static private void StatusUpdateEvent(int value)
{
int status = (int) scale.ResultCode;
if (value == (int)OPOSScaleConstants.SCAL_SUE_STABLE_WEIGHT)
{
Console.WriteLine(WeightFormat(scale.ScaleLiveWeight));
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_UNSTABLE)
{
Console.WriteLine("Scale weight unstable");
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_ZERO)
{
Console.WriteLine(WeightFormat(scale.ScaleLiveWeight));
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_OVERWEIGHT)
{
Console.WriteLine("Weight limit exceeded.");
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_NOT_READY)
{
Console.WriteLine("Scale not ready.");
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_UNDER_ZERO)
{
Console.WriteLine("Scale under zero weight.");
}
else
{
Console.WriteLine("Unknown status [{0}]", value);
}
}
static private string WeightFormat(int weight)
{
string weightStr = string.Empty;
string units = UnitAbbreviation(scale.WeightUnits);
if (units == string.Empty)
{
weightStr = string.Format("Unknown weight unit");
}
else
{
double dWeight = 0.001 * (double)weight;
weightStr = string.Format("{0:0.000} {1}", dWeight, units);
}
return weightStr;
}
static private string UnitAbbreviation(int units)
{
string unitStr = string.Empty;
switch ((OPOSScaleConstants)units)
{
case OPOSScaleConstants.SCAL_WU_GRAM: unitStr = "gr."; break;
case OPOSScaleConstants.SCAL_WU_KILOGRAM: unitStr = "kg."; break;
case OPOSScaleConstants.SCAL_WU_OUNCE: unitStr = "oz."; break;
case OPOSScaleConstants.SCAL_WU_POUND: unitStr = "lb."; break;
}
return unitStr;
}
Tying it all together
All that remains to be done is to connect the scale object to the delegate method StatusUpdateEvent()
and to write some simple thread management code.
Connecting the scale object to the delegate method is done by simply adding the statement scale.StatusUpdateEvent += StatusUpdateEvent;
to your code.
Finally, a more complete example can be found in the Datalogic OPOS Examples.
using System;
using System.Threading;
using OPOSCONSTANTSLib;
using OposScale_CCO;
namespace CSharpScaleApp
{
class Program
{
private static OPOSScaleClass scale;
static void Main(string[] args)
{
// Setup the console program to exit gracefully.
var exitEvent = new ManualResetEvent(false);
Console.CancelKeyPress += (sender, eventArgs) =>
{
eventArgs.Cancel = true;
exitEvent.Set();
};
// Create a scale object.
scale = new OPOSScaleClass();
// Open a context with the scale (e.g. "USBScale").
string profileName = "<your selected profile>";
scale.Open(profileName);
// Claim control of the scale using a 1000 millisecond timeout.
scale.ClaimDevice(1000);
if (scale.Claimed)
{
// Tell the scale we intend to perform "live" weighing.
scale.StatusNotify = (int)OPOSScaleConstants.SCAL_SN_ENABLED;
if (scale.ResultCode == (int)OPOS_Constants.OPOS_SUCCESS)
{
// Subscribe to the delegate.
scale.StatusUpdateEvent += StatusUpdateEvent;
// Enable scale events.
scale.DeviceEnabled = true;
if (scale.DeviceEnabled)
{
// Enable event data transmission.
scale.DataEventEnabled = true;
// Wait for exit event.
exitEvent.WaitOne();
// Disable, release and close the scale.
scale.DataEventEnabled = false;
}
// Unsubscribe from the delegate.
scale.StatusUpdateEvent -= StatusUpdateEvent;
}
// Disable and release.
scale.DataEventEnabled = false;
scale.ReleaseDevice();
}
// Finally, close the scale context.
scale.Close();
}
static private void StatusUpdateEvent(int value)
{
int status = (int) scale.ResultCode;
if (value == (int)OPOSScaleConstants.SCAL_SUE_STABLE_WEIGHT)
{
Console.WriteLine(WeightFormat(scale.ScaleLiveWeight));
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_UNSTABLE)
{
Console.WriteLine("Scale weight unstable");
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_ZERO)
{
Console.WriteLine(WeightFormat(scale.ScaleLiveWeight));
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_OVERWEIGHT)
{
Console.WriteLine("Weight limit exceeded.");
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_NOT_READY)
{
Console.WriteLine("Scale not ready.");
}
else if (value == (int)OPOSScaleConstants.SCAL_SUE_WEIGHT_UNDER_ZERO)
{
Console.WriteLine("Scale under zero weight.");
}
else
{
Console.WriteLine("Unknown status [{0}]", value);
}
}
static private string WeightFormat(int weight)
{
string weightStr = string.Empty;
string units = UnitAbbreviation(scale.WeightUnits);
if (units == string.Empty)
{
weightStr = string.Format("Unknown weight unit");
}
else
{
double dWeight = 0.001 * (double)weight;
weightStr = string.Format("{0:0.000} {1}", dWeight, units);
}
return weightStr;
}
static private string UnitAbbreviation(int units)
{
string unitStr = string.Empty;
switch ((OPOSScaleConstants)units)
{
case OPOSScaleConstants.SCAL_WU_GRAM: unitStr = "gr."; break;
case OPOSScaleConstants.SCAL_WU_KILOGRAM: unitStr = "kg."; break;
case OPOSScaleConstants.SCAL_WU_OUNCE: unitStr = "oz."; break;
case OPOSScaleConstants.SCAL_WU_POUND: unitStr = "lb."; break;
}
return unitStr;
}
}
}