Search code examples
c#wpfmvvmdomain-driven-designi2c

How to handle retrieving i2c buffer and setting model/entity properties?


I've been learning up on design patterns in order to create some more manageable code within my projects. However, I'm having some trouble determining the best implementation to get byte-array buffer data from an i2c interface to my entities. It's a simple dashboard-like application that queries a smart-battery for its state and about 12 different register values. It then presents the data to the user either in a small widget or in a larger "details" window. I'm trying to avoid creating more anemic models, as I've found myself falling into that pit from my early days developing with the MVVM pattern and am trying to break the habit.

Program Functionality
Retrieve data from a SmartBattery through SMBUS(i2c) via a FTDI cable and display it within a WPF application.

Current Structure
FTDI

  • IFtdiService - Interface that defines i2c functions specific to the Ftdi cable
  • FtdiService - Implements IFtdiService with external Ftdi library (.dll)
  • FtdiController - Manages initialization, configuration, and transmission and receiving of commands to and from the battery via the FtdiCable

Battery
Data retrieved from the battery is in a byte array format, between 2 and 32 bytes in length, and varies from little to big endian (trust me, I know it's strange). Basically, you have to send a write command with the "command" being the register (say Voltage is 0x09) to the device address (0x0b). After performing a read-command with a passed buffer, the buffer contains the data from the battery.

It made sense to me to keep the Ftdi communication layer agnostic to the applications' function, as I could potentially substitute i2c for SPI and so on (or even communicate with something other than a battery). So, I've ensured the layer knows nothing about the type of device with which it's communicating. But, this leaves me with the question:

At what layer do I put the knowledge of the battery's registers?
Each register has an address, length, unit type, and format. i.e.

Voltage
address: 0x09
length: 2 bytes
unit/format: mV

Of the registers, I have the following converters in mind:

  • Metric - for amperes, volts, etc.
  • Temperature - register stores temp in units of 0.1K, so it'll need offset to be in celsius correctly.
  • Integer - Used for State of Charge (units are %), and Run Time (units are minutes)
  • ASCII - Used for serial number, device name, etc.

So, with this in mind, how should I go about doing this? My current ideas are:

  • Anemic Model + Battery Service: Create a service capable of managing batteries, and converting byte array data to the correct data type for the model. I'll then have a BatteryManager (think controller) that will contain the timed loop to query the battery using the FtdiController that has an instance of the service to properly handle the batteries and emit a notification for the ViewModels to receive
  • Rich-Model: This feels wrong, but it might be because I've not had a lot of experience. If the battery model/entity were to be more feature-rich, it might have the capability to perform these conversions itself? It seems like that functionality should be at a lower level, however.

I think the first method would be easier, but I'd like to make this application a "model" for future projects and would like to get it right the first time.

Thanks for the help, and sorry for the epic novel.


Solution

  • Use Anemic Model + Battery Service.

    This way you can properly unit test the service layer via DI and as you said make it possible to use different services to serve different hardware types.

    The harder question is where the put the conversion you need. Part of the problem is the byte array itself is coupled to the hardware. I would look into abstracting that out as well. Then your service(s) could return the abstracted datatype that your models would know about and could work with without any concern as to where it came from or what it really contains.