Sunday, October 11, 2009

Pos .Net Series, Post #11 – Sharing Devices Across A Network

This is the eleventh post in a series on Pos .Net. You can find the first post here, or see all the posts here. You can get the sample code for this post here.

Pos .Net doesn’t natively support sharing devices across a network. The only networking feature available in Pos .Net is the ability to use a device connected to the local machine when the software is being run under a terminal sessions via Microsoft Terminal Server. While this is useful in some situations, many people want to be able to share a device attached to one machine so it can be used from others, in much the same way as Windows allows sharing of printers.

While Pos .Net doesn’t provide this feature itself, you can implement your own device sharing architecture. Doing this properly is not for the faint hearted though. I’ve posted some sample code for sharing a PosPrinter object between multiple PC’s here. The sample uses .Net Remoting to handle the network requests, you may prefer to use a more modern technology such WCF, but I used remoting in the sample because it was one of the simplest mechanisms available and required less code to make it work.

The sample I’ve provided is obviously not intended for production use. It only demonstrates sharing a a PosPrinter, and not any other types of device. Sharing other ‘output’ devices could be done easily by extending the code, but input devices (such as Barcode Scanners and MSRs) would require significantly more work as the server must also become a client, and the client also a server to enable the two-way communication required. The architecture for an input device would be very similar to the WS-POS specification which uses web services to share devices across the network.The sample code also doesn’t contain any error handling and so isn’t robust enough for a real world implementation. Finally, the sample also supports only a limited set of printing functionality. To fully implement a shared printer you would need to build your own PML to allow single-call printing of entire documents with a full feature set. Trying to implement the device any other way will likely result in a system that is too slow and not robust enough, as it will involve too many network calls.

The code in the sample is heavily commented, with some comments explaining the general architecture and others explaining what the code does. While comments that explain what code does are typically bad, since this is a sample designed to be used as a learning tool and everyone who views it will have a different level of knowledge, I felt it was appropriate to provide as many comments of both sorts as possible.

You will need to properly configure or disable firewalls on both the client and server, and ensure both machines can see each other on the network, in order for the sample to work correctly. This will be a problem with pretty much any networking implementation.

The sample code is split into four projects. First, there is a ‘common’ library which contains interfaces and base classes that are required on both the server and the client machines to enable the remote communication. There is a server library which contains a DeviceManager class, which an be instantiated in any host process that can act as a remoting server (i.e a windows service, windows application, ASP .Net application etc). This class is responsible for locating and opening the locally attached Pos .Net devices and setting up the code that listens for and processes incoming requests to use the devices. There is also a very simple Windows Forms application that loads the device manager, for the purposes of the demo. Finally there is a client application that communicates with the server, and allows the user to select a printer and then print simple text to it. Technically you can also print text containing escape codes using the sample if you can embed them in the sting sent to the remote server, but there is no support for printing barcodes, bitmaps and so on.

So, the server application depends on the server library and the common library. The client application depends only on the common library.

Some pieces of the architecture may seem a bit odd at first. For example, the DeviceManager class stores and exposes via a property a static collection of Pos .Net devices. The remote device classes which handle requests for specific device classes obtain a reference to a device from this static collection on every request, and then use that reference for their work. The static collection is used so the devices do not have to be located, instantiated and opened on every remote call, and this helps to improve the performance of the system. It would seem, however, to have been simpler to just have the remote device class to use a field to keep a reference to the particular Pos .Net device object it is associated with. Unfortunately this is not possible, for an object to be accessible it must be serialisable but the Pos .Net objects aren’t and so any class that has a field or public member that exposes a Pos .Net object cannot be serialised. Therefore, it is not possible to refer to a Pos .Net object inside the remote device classes except within the local scope of a method, and therefore the static collection on DeviceManager is used.

Another issue I had with the remoting was that some of the .Net enumeration types could not be exposed on public interfaces due to security issues (or so .Net claimed), therefore the printer station parameter on the print method of the remote pos printer class accepts an integer for the station and not the enum value. You can however cast the enum value to an int when passing it, so you can continue to use the enumeration values for convenience.

The way a client communicates with the server is relatively simple. First, the client obtains a list of available devices by name using the GetDeviceList method. In the sample, these names are loaded into a combobox where the user can select a device to work with. The client may then request a ‘remote device object’ using the GetDevice call and passing in the name of the device it wants to work with. The object retuned must then be cast to the interface of the remote device type being worked with, which in the case of the sample is always IRemotePosPrinter since only PosPrinters have been implemented. Once a remote device object reference has been obtained, the methods and properties of the interface can be called just like any other object, and these calls will be sent to the server via the .Net Remoting system and the actual code will then execute on the server side.

So, check out the sample and if you’re game feel free to base your own system on it’s design.


Technorati Tags: ,,,,

11 comments:

  1. Hi Yort
    I'm creating a POS system with some basic devices support (printer, barcode, scale).
    Any POS terminal machine must be a client of a main POS server with all logic, but no devices.
    How do I connect a POS machine with physical devices, via Terminal Server, to the main server which processes data capture by those devices?
    Thank you!!!

    ReplyDelete
  2. Hi,

    Try checking out the forum post here;

    http://social.msdn.microsoft.com/Forums/en-US/posfordotnet/threads?prof=required

    Or failing that, download the Pos .NET SDK and check the documentation.

    Basically you need to install all the service objects and Pos .Net on the server and then edit the rdp file on the client used to connect to the server to enable the device redirection.

    http://technet.microsoft.com/en-us/library/cc732188(WS.10).aspx

    ReplyDelete
  3. I think sharing a PosPrinter over network is not so hard in case it uses serial connection. You only need a com port forwarding/sharing - there are some programs like com0com, etc. If the serial port is accessed from remote computer, you can install the service object.

    ReplyDelete
  4. Hi,

    That would be true if you are;

    1. Writing to the COM port yourself (which is not the case when using Pos .Net), OR

    2. Those programs are able to intercept the data going to the serial port and forward it to another PC AND that PC is running the same model of printer. If the printers are different, then you don't actually want the same set of escape codes.

    Also, by implementing my suggestion the connection type (serial, usb, parallel etc) is irrelevant.

    But yes, if you are in a situation where you are lucky enough to use one of those systems then they would work fine. Thanks for the tip !

    ReplyDelete
  5. Yort,
    I have POS System that uses (OPOS) to interact with devices. I am trying to write a virtual printer for the system. What's the best way to do this? I cannot use POS for .net as the system was not built using that. Any pointers would be really helpful.

    ReplyDelete
  6. Hi Bigb,

    If you're using OPOS to handle your printing, then probably the best thing to do is create your own service object. OPOS is based on the UPOS standard, just like Pos .Net is, so both use service objects.

    In the case of OPOS the service object is a COM component and may need to implement specific interfaces. I'm not an expert on OPOS or writing service objects, so you should use Google to look for tutorials and code samples.

    Beware though, creating a service object for a printer (or any kind of printer driver, virtual or otherwise) is not a small job - certainly not if you intend to implement the full set of printing features. If you just want to capture unformatted text to a file or console window etc. then you'll probably be ok, but otherwise you'll have to implement not only all the text formatting and alignment stuff, but also bitmap printing, barcode printing, page mode etc.

    Anyway, try looking for information on "writing opos service objects".

    Good luck.

    ReplyDelete
  7. Hi, first of all great work!

    It works perfectly if I use the Microsoft PosPrinter Simulator, but as soon as I use The Epson TM-88v, it gives me the following error :
    ype 'jp.co.epson.pos.comm.v3_0001.CommControlException' in Assembly 'Epson.opos.tm.comm.v3_0001, Version=1.11.4218.16768, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

    Would you know a workaround?

    ReplyDelete
  8. Hi Carlos,

    I'm sure I tested this before I published it, but looking at the code now I think there is a problem. I haven't got time to test or redo the sample at the moment, but my suggestion would be this; move the m_PosExplorer and m_PosDotNetDevices variables into a (new) internal static class in the same project, and change the code in the DeviceManager class that uses those variables to reference them via the internal class instead.

    As stated in my blog post, the Pos .Net objects and services objects aren't serialisable and so cannot be contained within any part of a class exposed via remoting, which DeviceManager is. By moving the (class level( references to those objects out into another class that is not exposed via remoting, that should fix the serialisation error.

    ReplyDelete
  9. Hello Yort,

    Thank you for your swift response. I tried your suggestion, but no success. I think the problem resides in the getDevice() method in the DeviceManager Class, witch sends the serviceobject, hence serializing it. I cannot simply move this method to the internal static class that I created since the method is part of the IDeviceManager interface, so it must be implemented. The interface is used in the client application, where the getdevice is called, and it is at this point where the service object gets serialized and creates the error. Is this explanation plausible? I'm trying to workaround the interface, but I'm having a hard time.

    ReplyDelete
  10. Hi,

    Could someone help me out giving me a simple application which send and receive data from a PINPAD?

    Regards,
    Hina

    ReplyDelete
    Replies
    1. Hi Hina,

      I'm sorry I don't have any such code to share with you. We don't use the POS .Net/OPOS pinpad drivers where I live as the banks are security conscious and such a solution is not good enough for their approval (must use a bank approved EFTPOS SDK for payments, we don't use pinpads for anything non-payment). Perhaps other readers will be able to help.

      Good luck.

      Yort

      Delete