5

My question is: How can I maximize encapsulation for wrapper-classes that interface with hardware.

The hardware is connected to a PC through COM-Ports or USB ports and I am either reading/writing to it directly or through an API. Either of which I want to wrap in a class.

I have been reading about software development and architecture in general (in C#), but I still do not have a feeling for how to architecture classes and objects around hardware.

Things that have been going through my head in this regard are the following aspects:

  1. When and how should I initialize the hardware, so that the clients of the encapsulating class need not worry about this (or the littlest possible), e.g.: Is it OK to initialize the hardware/port once at program startup and close it at the end (which is what I am currently doing) or should I always open and close the hardware/port as needed.
  2. Related to the first point: When and how should I release and destruct the hardware and their wrapping/related class.
  3. Should I store hardware state in the class or always query the device?
  4. How to handle hardware errors/exceptions (gracefully)? Right now, since I am initializing the hardware at startup, if some of it is not found, my program will not startup correctly. I would prefer it to startup and signal that some hardware was not found, so that it can be connected. But I cannot think of a decent architecture to achieve this.
  5. How can I make this fit in with dependency inversion: I am currently using factories to initialize the hardware and pass out the wrapper-classes through a DI-container. This however means that I am initializing only once at startup.

I realize that this is a broad topic with various subtopics, but these points are all closely related to each other and I am finding it difficult to find good and coherent information on this topic.

FYI: I am a C# developer, but I am interested in the general concepts.

  • 1
    Voted to close as off-topic: "Questions asking us to find or recommend tools, libraries, programming languages, resources (including books, blogs, tutorials, and examples), or projects to undertake are off-topic here as they attract opinionated answers that won't have lasting value to others. You may be able to get help in The Whiteboard, our chat room." – code_dredd Jul 26 '17 at 8:22
  • It's true, we don't recommend reading material. If however you were looking for a field of study you could ask us what the right buzz words to search for would be. Consider editing before this get's closed. – candied_orange Jul 26 '17 at 8:42
  • Software and hardware are two sides of the same coin. Hardware offers efficiency with the tradeoff of flexibility, while software offers flexibility with the tradeoff of efficiency. Ideally where these worlds collide is the compiler, and it is also here where you might try to get the best of both worlds, flexibility and efficiency. In other words, don't compromise code structure for speed if you can help it, because speed isn't your first priority. – Neil Jul 26 '17 at 10:12
  • 2
    I don't understand the downvotes. This is a very good and answerable question. – RubberDuck Jul 26 '17 at 10:15
  • 1
    @gnat I revised the question to hopefully be more in-line with guidelines for SE. I hope it is more acceptable in its new form. Again: The question is how to achieve maximum encapsulation. The answer by RubberDuck is already very good and I believe it also illustrates, that the my question (in its new form) is answerable without going over board. – packoman Jul 27 '17 at 8:28
5

You mentioned COM ports, so I'll specifically address that case.

  • Designing and structuring classes around hardware interfaces to maximize encapsulation.

There are two broad ways to utilize COM ports, Command/Response and Streaming. How you design your interface will depend on how you plan to use the port. Will you only be reading and writing to the device occasionally or will you be opening a constant stream of data from one device to another?

For Command/Response I would create an asynchronous interface something like this.

public interface IDevice : IDisposable
{
    // Sends command without listening for a response
    Task Send(Command command);

    // Sends Command and waits for response
    Task<Response> Send(Command command);

    // Sends Command and listens for Response. Cancellable
    Task<Response> Send(Command command, CancellationToken token);
}

For data streaming, I usually leverage the Stream class along with a custom StreamWriter or StreamReader.

In either case, you want to be sure to have interfaces or abstract classes to depend on, because you'll want to mock out the hardware to test other parts of your code.

  • How to handle initialization and releasing/destroying of the hardware and their wrapping/related class.

You have basically two options for initialization here. You can open the stream in the constructor or provide an Open() method. Either will work and both have their pros and cons. Opening the connection in the ctor means you might get exceptions from the ctor and some folks don't like that. On the other hand, having a method that must be called before using a class is a kind of temporal coupling that a lot of people don't care for. It's easy to forget to call the method to actually put the class in a usable state. So, choose the one that fits best with your team's sensibilities.

For for destruction, you'll want to implement the IDisposable interface so you can close and release the port in a controlled and reliable way.

using( var device = new Device("COM5")
{
    // do stuff with port
} 

As we exit the using block, the Dispose() method you implemented is called, releasing the port.

  • How to store hardware state (for example storing the state in the hardware wrapper-class, a context vs. always querying the hardware, when a state is requested).

I typically don't store much state. Typically we're querying the hardware for whatever it's current state is and refuting that.

  • How to make this fit in with OO best practices, in particular dependency inversion.

Just treat the hardware like you'd treat a database. Code to interfaces instead of implementations. We created an interface that represents our hardware and an implementation of that interface, so any client code relies on our IDevice interface. You just constructor inject the implementation like you would any other class.

public class Foo
{
      private readonly IDevice _device;

      public Foo(IDevice device)
      {
           _device = device;
      }

      //...
}

Actually, I typically inject a factory instead of an instance when I'm dealing with IDisposable to make testing easier.

1

There may be dependencies on your specific hardware. E.g. in our case, we send a command at startup for the hardware to do some initialization / calibration steps. Our hardware does not accept fresh commands before it has finished sending its response to the previous command (though the action initiated by the command may last "forever" - but the acknowledge must be received before the next command). I prefer Start and Stop commands in the interface (hence it does work with dependency injection also).

For communicating via serial port, we extracted a simple interface ISerialPort and added a SerialPortWrapper which contains a .Net SerialPort instance and exposes the interface (which allows us to write some dummies which may send specific hardware error responses for tests).

Also a multi-threaded application must make sure that commands are not sent at the same time over the port. We take care that there exists only one wrapper per COM port, and of an appropriate synchronization mechanism in that wrapper instance.

Errors sent from the hardware are wrapped into an ErrorEvent when a thread for monitoring the device is used. You cannot use an Exception in that context as it would only kill the monitoring thread.

The state of the device is best queried from the device - if it does cope with it (I've experienced problems with high frequency queries, there might be interrupt issues in the device); alternatively, a cache can decorate the communication class, and re-issue a query depending on the time since that value was queried last.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.