I am in a situation where "I don't know what I don't know" so I am unsure if this is even the correct way of approaching this problem, apologies if this comes off as plain ignorant.
I have a program which connects to Ethernet controllers. The program allows users to configure what is connected to the system and set up I/O communication.
Each controller is its own device, and may have different IO depending on what model it is. Controllers have their own API.
The program saves the configuration to an XML config file which is read at startup. I need to then connect to each unknown device and set up a connection to each, leaving me with a method of referring to each device at a later time.
Here is what I am trying to achieve:
using Brainboxes.IO;
public class BrainBoxes
{
public string[] Devices = new string[] { "192.168.16.147", "192.168.16.148", "192.168.16.149", "192.168.16.150" };
List<string> EDDeviceList = new List<string>();
public BrainBoxes() // set up devices and connections to all devices connected in the constructor
{
foreach (string Device in Devices)
{
EDDevice BB400 = EDDevice.Create("192.168.16.147");
// BB400 is a typical name but how do I make this dynamic at the same time making it
// available for other members of the class?
EDDeviceList.Add(BB400); // add the device to a list to refer to later in the constructor
}
for (int i = 0; i < EDDeviceList.Count - 1; i++) { BB400.Connect()}; // connect to each device in sequence.
}
public void Outputs(int Relay)
{
// this would be a switch statement
BB400.Outputs[Relay].Value = 1;
Thread.Sleep(75);
BB400.Outputs[Relay].Value = 0;
}
~BrainBoxes()
{
BB400.Disconnect();
}
}
It sounds like you're trying to do quite a few things at once. To paraphrase what you want: to achieve (looking at both your question, your sample code and your comment)
When your application starts, you want it to connect to a collection of different devices automatically
When running, users can connect to and configure (the right) device
Ensure that connections are closed when the application stops
Also your question is rather open ended and from your first statement, I'm going to assume that you're a beginner. I know that it's quite dry, but you are going to have to look up the documentation for the hardware you're using. Luckily, it looks quite comprehensive
You need to give your class a more representative name. E.g. BrainboxController or BrainboxManager as, by the sounds of it, that is what it's for.
Looks like BB400 is one of the possible hardware devices, it is part of an inheritance hierarchy, so you don't want to restrict yourself to just that
I would avoid doing a lot of work in the constructor, it makes it harder to find problems
Use a dictionary to store your devices, that's how you'll "refer to each device at a later time"
public class BrainboxController : IDisposable
{
private readonly HashSet<string> _deviceIps; // potentially you can get away without having this if you call InitialiseDevices() in the constructor
private Dictionary<string, EDDevice> _devices = new Dictionary<string, EDDevice>(); // possibly use IDevice<C, P> instead of EDDevice
public BrainboxController(IEnumerable<string> devices)
{
_deviceIps = new HashSet<string>(devices);
}
public void InitialiseDevices()
{
foreach (string ip in _deviceIps)
_devices.Add(ip, EDDevice.Create(ip));
}
public void AddDevice(string ip)
{
if (_deviceIps.Add(ip))
_devices.Add(ip, EDDevice.Create(ip));
}
public void RemoveDevice(string ip)
{
if(_devices.ContainsKey(ip))
{
var device = _devices[ip];
device.Disconnect();
device.Dispose();
_devices.Remove(ip);
_deviceIps.Remove(ip);
}
}
public EDDevice GetDevice(string deviceIp)
{
if (_devices.ContainsKey(deviceIp))
return _devices[deviceIp];
return null;
}
public string GetConfiguration(string deviceIp)
{
if (_devices.ContainsKey(deviceIp))
return _devices[deviceIp].Describe(); // I'm assuming that this gets the config data
return "Device not found";
}
public bool SetConfiguration(string deviceIp, string xml)
{
if (_devices.ContainsKey(deviceIp))
{
_devices[deviceIp].SendCommand(xml); // I'm assuming this is how the config data is set
return true;
}
// log device not found
return false;
}
public IOList<IOLine> GetOutputs(string deviceIp, int relay)
{
if (_devices.ContainsKey(deviceIp))
return _devices[deviceIp].Outputs[relay];
// log device not found
return new IOList<IOLine>();
}
public void Dispose()
{
foreach(var device in _devices.Values)
{
device.Disconnect();
device.Dispose();
}
}
}
Strictly speaking, if you follow the single responsibility principle, this class should just be managing your devices and their connections. The methods GetConfiguration(), SetConfiguration() and GetOutputs() are shown as examples and really should live somewhere else.
Your calling code could be look like this (without dependency injection):
var deviceAddresses = new[] { "192.168.16.147", "192.168.16.148", "192.168.16.149", "192.168.16.150" };
var controller = new BrainboxController(deviceAddresses);
controller.InitialiseDevices();
var currentDevice = controller.GetDevice("192.168.16.147");
// do something with currentDevice
Finally, whatever it is you're trying to do with your Outputs method, that looks like business logic and this also should live somewhere else.
Related
Another speed bump on my way to trying to use a Dictionary with a WebAPI. I am trying to implement something with MangoDB and this is only my second time so I am not very knowledgeable here. I have set up a connection with the following code.
private readonly IMongoCollection<Dictionary<Guid, Order>> ordersCollection;
public MongoDBRepository(IMongoClient mongoClient)
{
IMongoDatabase database = mongoClient.GetDatabase(databaseName);
ordersCollection = database.GetCollection<Dictionary<Guid, Order>>(collectionName);
}
Now I have implemented a way to save as I have verified it in the VSC MongoDB extension. I used the following code.
public void CreateOrder(Order order)
{
var values = new Dictionary<Guid, Order>
{
{
Guid.NewGuid(), order
}
};
ordersCollection.InsertOne(values);
}
I've now added several Dictionaries to the DB and I want to get them all. The method in the controller I have says:
[HttpGet]
public Dictionary<Guid, API.Models.Order> GetOrders()
{
var data = repository.GetOrders();
return data;
}
The best I can figure then is that I need to implement something along the lines of:
public Dictionary<Guid, Order> GetOrders()
{
return ordersCollection.Find(new BsonDocument()).ToDictionary();
}
I did some searches and found https://mongodb.github.io/mongo-csharp-driver/2.0/apidocs/html/M_MongoDB_Bson_BsonDocument_ToDictionary.htm and I verified I have a using MongoDB.Bson but it doesn't appear. I am assuming it's because of the way I am utylizing orderCollection. I have found other questions on here using other methods but I only have access to InsertOne, InsertMany, Find, ReplaceOne... Any pointers would be appreciated.
I am currently working on a blazor server project which will display information from modbus tcp/ip devices. I have a class called "DeviceModel" which models a Modbus device. A simplified example is shown below.
public string DeviceName {get;set;}
public string IpAddress {get;set;}
public string Port {get;set;}
public int[] Registers {get;set;}
public string Alarm1 {get;set;}
The device model class also contains methods to parse information from the Registers. For example the snippet below will check the value at a certain index in the Registers array. Based on that value it will set the Alarm1 property to ON or OFF.
public void CheckAlarm1(){
int status = Registers[4];
Alarm1 = status == 1 ? "ON" : "OFF";
}
I have another class called "NetworkAccess" which handles the TCP/IP connection to a device. A simplified example is shown below
// ModbusClient is a package which handles the reading/writing to TCP/IP Modbus
private ModbusClient _client;
public string IPAddress {get;set;}
public string Port {get;set;}
public DeviceModel Device {get;set;}
public NetworkAccess(DeviceModel dev){
IPAddress = dev.IPAddress;
Port = dev.Port;
_client = new ModbusClient(IPAddress,Port);
_client.Connect();
}
The NetworkAccess class handles reading and writing data to/from the device on the network. An example method which would write data to a single register on the Modbus device is below.
public void WriteSingleRegister(int address,int dataToAdd){
_client.WriteSingleRegister(address,dataToAdd);
}
Within my Razor Component for the webpage, within the OnInitialized() method I get a List containing DeviceModels from a database which fills in information such as IPAddress,Port, and Name for each device. To read information to the device, I have another method "GetData()" shown below
public async void GetData(){
foreach(var device in Devices){
NetworkAccess network = new NetworkAccess(dev);
var dataUpdate = await network.ReadRegistersAsync(0,20);
dev.Registers= dataUpdate;
}
}
The way I currently have this setup works fine. In order to write to a device I would do something like this in my Razor Component
NetworkAccess network = new NetworkAccess(dev);
network.WriteRegistersAsync(0,new int[] {0,0,0,...}};
Where I am having trouble is I am not sure the of the correct (or best) way to handle my situation. In my head it makes more sense to me if I had methods within my DeviceModel class for specific operations such as "ResetAlarm1" or "ClearRegisters". That way I could do
dev.ResetAlarm1();
rather than doing this in my razor component below
NetworkAccess net = new NetworkAccess(dev)
dev.WriteRegister(6,0); // where 6 is the register to write to and 0 is value to write
I guess my question is should I add "NetworkAccess" to the device model and handle creating the connection and reading/writing to the device within that? Or does it make more sense to keep NetworkAccess and DeviceModel seperate?
I hope this post makes sense. This is more a question about design than it is about fixing a problem. While my current solution is working fine, I want to better understand if this is the correct approach or if I am way off.
Thanks for any help!
Or does it make more sense to keep NetworkAccess and DeviceModel separate?
As single responsibility principle of SOLID says:
The single-responsibility principle (SRP) is a computer-programming
principle that states that every module, class or function in a
computer program should have responsibility over a single part of that
program's functionality, and it should encapsulate that part. All of
that module, class or function's services should be narrowly aligned
with that responsibility.
Read more about single responsibility principle of SOLID here.
So making separate method dev.ResetAlarm1() in Device class is more preferable for me.
It is hard to say whether my refactoring code is appropriate to you, but I tried to do my best:
public class Device
{
public string Name { get; set; }
public string IpAddress { get; set; }
public string Port { get; set; }
public int[] Registers { get; set; }
public string[] Alarms { get; set; }
public void CheckAlarm(int registerIndex)
{
int status = Registers[registerIndex];
Alarms[registerIndex] = status == 1 ? "ON" : "OFF";
}
}
So I'm not sure if it is correct for me to ask this, but I've been self learning WPF and I can't figure out a method to save the data the user enters in my application.
Let's say a project requires the user to input a IList<int> of values. So I have a class storing that information. This information can be loaded from a json filed if the user has already input it and saved within the application.
public class Vault : BindableBase
{
public Vault(string savedFilePath = null)
{
if (string.IsNullOrEmpty(savedFilePath))
{
Measures = new List<int> { 1, 2, 3, 4 };
}
else
{
Measures = (List<int>)JsonConverter.DeserializeObject<List<int>>(savedFilePath);
}
}
public IList<int> Measures { get; set; }
}
Now, when I create the application view, I want to load all the ViewModels the user will use. In each ViewModel, an element of the Measures List must go.
public MainWindowViewModel()
{
vault = new Vault(savedFilePath);
Collection = new ObservableCollection<object>
{
new FirstViewViewModel(vault.Measures[0]),
new SecondViewViewModel(vault.Measures[1])
};
}
So that when I press Save, the Vault class can be serialized.
public void Save()
{
File.WriteAllText(fileLocation, JsonConvert.SerializeObject(vault));
}
As I want to modify the values in Vault with the user input, I need a direct reference to it, therefore in the ViewModels what I do is
public class FirstViewViewModel : BindableBase
{
private int _measure;
public FirstViewViewModel(int measure)
{
_measure = measure;
}
public int Measure
{
get => _measure;
set => SetProperty(ref _measure, value);
}
}
Nevertheless this seems an awful way to connect the user input with the data i want to save in a file.
This is a simplified case of what I want to achieve. However I am sure there are a better way that would allow me to change the values in Vault when Raising a property on the ViewModel. Ideally one that would make UnitTest easy (I haven't started with that yet).
If anyone could offer me a clue to find a better method to deal with this kind of situation, I would really appreciate it.
This will probably get flagged for being too broad in scope, but in general you should serialize the data to a database. This article is a great place to start:
https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/workflows/new-database
If your data structures are very lite then you might want to use something like SQLite, which stores the database in a local file and doesn't require installing any 3rd-party applications along with your application. Plenty of info here on how to get that working with Entity Framework:
Entity Framework 6 with SQLite 3 Code First - Won't create tables
I'm currently trying to find a better design for my multi-module solution using DI/IOC, but now I'm somehow lost. I have a solution where different kind of entities can be distributed to recipients via different channels.
This is a simplified version of my classes:
#region FTP Module
public interface IFtpService
{
void Upload(FtpAccount account, byte[] data);
}
public class FtpService : IFtpService
{
public void Upload(FtpAccount account, byte[] data)
{
}
}
#endregion
#region Email Module
public interface IEmailService :IDistributionService
{
void Send(IEnumerable<string> recipients, byte[] data);
}
public class EmailService : IEmailService
{
public void Send(IEnumerable<string> recipients, byte[] data)
{
}
}
#endregion
public interface IDistributionService { }
#region GenericDistributionModule
public interface IDistributionChannel
{
void Distribute();
}
public interface IDistribution
{
byte[] Data { get; }
IDistributionChannel DistributionChannel { get; }
void Distribute();
}
#endregion
#region EmailDistributionModule
public class EmailDistributionChannel : IDistributionChannel
{
public void Distribute()
{
// Set some properties
// Call EmailService???
}
public List<string> Recipients { get; set; }
}
#endregion
#region FtpDistributionModule
public class FtpDistributionChannel : IDistributionChannel
{
public void Distribute()
{
// Set some properties
// Call FtpService???
}
public FtpAccount ftpAccount { get; set; }
}
#endregion
#region Program
public class Report
{
public List<ReportDistribution> DistributionList { get; private set; }
public byte[] reportData{get; set; }
}
public class ReportDistribution : IDistribution
{
public Report Report { get; set; }
public byte[] Data { get { return Report.reportData; } }
public IDistributionChannel DistributionChannel { get; private set; }
public void Distribute()
{
DistributionChannel.Distribute();
}
}
class Program
{
static void Main(string[] args)
{
EmailService emailService = new EmailService();
FtpService ftpService = new FtpService();
FtpAccount aAccount;
Report report;
ReportDistribution[] distributions =
{
new ReportDistribution(new EmailDistributionChannel(new List<string>("test#abc.xyz", "foo#bar.xyz"))),
new ReportDistribution(new FtpDistributionChannel(aAccount))
};
report.DistributionList.AddRange(distributions);
foreach (var distribution in distributions)
{
// Old code:
// if (distribution.DistributionChannel is EmailDistributionChannel)
// {
// emailService.Send(...);
// }else if (distribution.DistributionChannel is FtpDistributionChannel)
// {
// ftpService.Upload(...);
// }else{ throw new NotImplementedException();}
// New code:
distribution.Distribute();
}
}
}
#endregion
In my current solution it is possible to create and store persistent IDistribution POCOs (I'am using a ReportDistribution here) and attach them to the distributable entity (a Report in this example).
E.g. someone wants to distribute an existing Report via Email to a set of recipients. Therefore he creates a new ReportDistribution' with anEmailDistributionChannel'. Later he decides to distribute the same Report via FTP to a specified FtpServer. Therefore he creates another ReportDistribution with an FtpDistributionChannel.
It is possible to distribute the same Report multiple times on the same or different channels.
An Azure Webjob picks up stored IDistribution instances and distributes them. The current, ugly implementation uses if-else to distribute Distributions with a FtpDistributionChannel via a (low-level) FtpService and EmailDistributionChannels with an EmailService.
I'm now trying to implement the interface method Distribute() on FtpDistributionChannel and EmailDistributionChannel. But for this to work the entities need a reference to the services. Injecting the Services into the entities via ConstructorInjection seems to be considered bad style.
Mike Hadlow comes up with three other solutions:
Creating Domain Services. I could e.g. create a FtpDistributionService, inject a FtpService and write a Distribute(FtpDistributionChannel distribution) method (and also a EmailDistributionService). Apart from the drawback mentioned by Mike, how can I select a matching DistributionService based on the IDistribution instance? Replacing my old if-else with another one does not feel right
Inject IFtpService/EMailService into the Distribute() method. But how should I define the Distribute() method in the IDistribution interface? EmailDistributionChannel needs an IEmailService while FtpDistributionChannel need an IFtpService.
Domain events pattern. I'm not sure how this can solve my problem.
Let me try to explain why I came up with this quite complicated solution:
It started with a simple list of Reports. Soon someone asked me to send reports to some recipients (and store the list of recipients). Easy!
Later, someone else added the requirement to send a report to a FtpAccount. Different FtpAccounts are managed in the application, therefore the selected account should also be stored.
This was to the point where I added the IDistributionChannel abstraction. Everything was still fine.
Then someone needed the possibility to also send some kind of persistent Logfiles via Email. This lead to my solution with IDistribution/IDistributionChannel.
If now someone needs to distribute some other kind of data, I can just implement another IDistribution for this data. If another DistributionChannel (e.g. Fax) is required, I implement it and it is available for all distributable entities.
I would really appreciate any help/ideas.
First of all, why do yo create interfaces for the FtpAccount? The class is isolated and provide no behavior that need to be abstracted away.
Let's start with your original problem and build from there. The problem as I interpret it as that you want to send something to a client using a different set of mediums.
By expressing it in code it can be done like this instead:
public void SendFileToUser(string userName, byte[] file)
{
var distributions = new []{new EmailDistribution(), new FtpDistribution() };
foreach (var distribution in distributions)
{
distribution.Distribute(userName, file);
}
}
See what I did? I added a bit of context. Because your original use case was way to generic. It's not often that you want to distribute some arbitrary data to an arbitrary distribution service.
The change that I made introduces a domain and a real problem.
With that change we can also model the rest of the classes a bit different.
public class FtpDistributor : IDistributor
{
private FtpAccountRepository _repository = new FtpAccountRepository();
private FtpClient _client = new FtpClient();
public void Distribute(string userName, byte[] file)
{
var ftpAccount = _repository.GetAccount(userName);
_client.Connect(ftpAccount.Host);
_client.Authenticate(ftpAccount.userName, ftpAccount.Password);
_Client.Send(file);
}
}
See what I did? I moved the responsibility of keeping track of the FTP account to the actual service. In reality you probably have an administration web or similar where the account can be mapped to a specific user.
By doing so I also isolated all handling regarding FTP to within the service and therefore reduced the complexity in the calling code.
The email distributor would work in the same way.
When you start to code problems like this, try to go from top->down. It's otherwise easy to create an architecture that seems to be SOLID while it doesn't really solve the actual business problem.
Update
I've read your update and I don't see why you must use the same classes for the new requirements?
Then someone needed the possibility to also send some kind of persistent Logfiles via Email
That's an entirely different use case and should be separated from the original use case. Create new code for it. The SmtpClient in .NET is quite easy to us and do not need to be abstracted away.
If now someone needs to distribute some other kind of data, I can just implement another IDistribution for this data.
Why? what complexity are you trying to hide?
If another DistributionChannel (e.g. Fax) is required, I implement it and it is available for all distributable entities
No. Distributing thing A is not the same as distributing thing B. You can't for instance transport parts of a large bridge on an airpane, either a freight ship or a truck is required.
What I'm trying to say is that creating too generic abstractions/contracts to promote code reuse seems like a good idea, but it usually just make your application more complex or less readable.
Create abstractions when there is real complexity issues and not on before hand.
Working on a sideproject with WP8, but having trouble getting IsolatedStorage working. I have looked at dozens of posts seemingly asking the same question, but I haven't been able to get any of the solutions to work. The application is a simple task organizer where I have created my own Task Objects, one being a Summary Task and each SummaryTask containing a list of BasicTasks. I have tried using XMLSerializing only to run into problems because I was using an ObservableCollection. Thought I could change the collection to a Subclass of INotifyPropertyChanged but that didn't work either. Quite frankly, I'm still getting the hang of the different between the two anyways. So anyways, my latest attempt involves trying to use IsolatedStorage Settings and that didn't work either. Here is my class definition:
class SummaryTask : TaskItem
{
public List<BasicTask> children = new List<BasicTask>();
private string sumTaskName;
private int sumTaskId;
public SummaryTask()
{
}
public SummaryTask(string name, int id)
{
sumTaskName = name;
sumTaskId = id;
}
public string SumTaskName
{
get { return sumTaskName; }
set { sumTaskName = value; }
}
public int SumTaskId
{
get { return sumTaskId; }
set { sumTaskId = value; }
}
public void addTask(string taskName, string taskText, int taskId){
children.Add(new BasicTask(taskName, taskText, taskId));
}
public List<BasicTask> CHILDREN
{
get { return children; }
}
}
}
I create a list of this SummaryTask in a Global variable and use it throughout my pages for easy access. Here is what the beginning of my MainPage.xaml.cs file looks UPDATED:
public MainPage()
{
InitializeComponent();
BackKeyPress += OnBackKeyPressed;
if (Global.settings.Contains("list"))
{
Global.list = (List<SummaryTask>)Global.settings["list"];
}
else
{
Global.list = new List<SummaryTask>();
}
}
Guidance on the poor quality of my code and how to improve it is also accepted. Thank you.
Edit: The exception indicates that an item with the same key has already been created. The stacktrace doesn't show anything of importance in this case. I should also note that the exception is thrown after adding an object to the list and trying to save it, not while compiling.
The piece of code I am using to try to save to the Isolated Storage is here, it triggers when I navigate to MainPage.xaml:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
resultList.SelectedItem = null;
Global.settings["list"] = Global.list;
Global.settings.Save();
}
No exceptions anymore, but exiting the app and reentering isn't pulling up any saved data.
The problem with Add is very simple to fix - just use the indexer instead, which allows you to overwrite an entry with the same name:
settings["list"] = Global.list;
That won't fix the Save call... but you'd need to give more details about what exception (not just "it tells me", the full exception details) to help us help you more.