I don’t use enumerators very much so I guess I made an incorrect assumption about how they work. If there’s a better way to accomplish what I’m trying to do here let me know…
I have a custom SerialConnection class. In it are enumerators for the 5 commands that can be sent to the devices my app can communicate with. The reason for the enumerators is that each device model uses a different command string to do the same thing. For example, the command to see if the device is alive is different for each model:
device / command
z8r 64
z16r 64
z32r 128
z64r 192
cmdIsAlive.z8r will return “64”. The current device model is stored in a property in the class called RouterModel. My assumption was that I could do something like cmdIsAlive.RouterModel (where the value of RouterModel is, in this case, “z8r”) but I guess not.
Is there a way to do this without having to build a complex set of select case statements to fetch the correct enum based on the current RouterModel?
Every time I communicate with the device, I need to reference these enumerators at least 3 times - to open communication, to tell it what I’m going to send it, and to close communications. So I’m looking for a simple way to build these command strings up.
Currently I need to do this, and I’ll need to do something similar in each command. It seems kind of crazy to wind up with “A500405A” - I mean, it works, but it’s a lot of code just to generate an 8 character string. I guess I could put this code in computed properties so each function just has the line that generates the string, and all the lifting is done in the computed Property’s Get method.
var commandString as string
var cmdBegin, cmdEnd, cmdIsAlive as integer
Select case GetRouterModel(16)
case "z8r", "z16r"
cmdBegin = integer(RouterConnect.cmdBegin.z8r)
cmdEnd = integer(RouterConnect.cmdEnd.z8r)
cmdIsAlive = integer(RouterConnect.cmdIsAlive.z8r)
case "z32r"
cmdBegin = integer(RouterConnect.cmdBegin.z32r)
cmdEnd = integer(RouterConnect.cmdEnd.z32r)
cmdIsAlive = integer(RouterConnect.cmdIsAlive.z32r)
case "z64r"
cmdBegin = integer(RouterConnect.cmdBegin.z64r)
cmdEnd = integer(RouterConnect.cmdEnd.z64r)
cmdIsAlive = integer(RouterConnect.cmdIsAlive.z64r)
case "z128r"
cmdBegin = integer(RouterConnect.cmdBegin.z128r)
cmdEnd = integer(RouterConnect.cmdEnd.z128r)
cmdIsAlive = integer(RouterConnect.cmdIsAlive.z128r)
case "z256r"
cmdBegin = integer(RouterConnect.cmdBegin.z256r)
cmdEnd = integer(RouterConnect.cmdEnd.z256r)
cmdIsAlive = integer(RouterConnect.cmdIsAlive.z256r)
end select
commandString = cmdBegin.ToHex(2)+routerID.ToHex(2)+cmdIsAlive.ToHex(2)+cmdEnd.ToHex(2)
I’m confused. You’re referring to RouterModel as both a class containing properties and as a String containing “z8r”.
If it’s the former, you should be making the property of type cmdIsAlive and then just refer to that directly. RouterModel.device I presume.
If you need the integer value, use CType although you can only automagically convert back if the enum values are unique, which they are not in your example.
RouterModel is not a class, it’s a string that holds the name of the currently installed router. It lives in a custom SerialConnection class.
cmdBegin and the other commands are enums that match the integer value of the (ultimately) hex command that’s sent to the router, for each of the models of router. The name of each item in the enum is the router model and its value is the integer value of the command it corresponds to.
The idea is that you call cmdbegin.z8r and it returns the integer that I will later convert to a hex value that means “I’m about to send a command” to the router, and it’s the right value for that model.
I might argue you could do a different design. Use Enums, but also use Dictionaries.
Something like this:
var commandLookups as Dictionary = GetLookupDict()
// a dictionary holding Dictionaries.
// The first level is for the router model
// the second level holds the commands
// CommandEnum is an enum holding .begin, .end, and .isAlive
var routerModel as RouterModelEnum = GetRouterModel()
var commandString as string = _
commandLookups.value(routerModel).value(CommandEnums.Begin) + _
commandLookups.value(routerModel).value(CommandEnums.End) +_
commandLookups.value(routerModel).value(CommandEnums.IsAlive)
To set up the commandLookups Dictionary of Dictionaries would look like this:
public function GetLookupsDict() as Dictionary
var commandLookups as new Dictionary
var d as Dictionary
// set up commands for one model:
d = new Dictionary
d.value(commandEnum.Begin) = 64
d.value(commandEnum.End) = 128
d.value(commandEnum.IsAlive) = 192
commandLookups.value("z8r") = d
// set up commands for another model
d = new Dictionary
d.value(commandEnum.Begin) = 88
d.value(commandEnum.End) = 89
d.value(commandEnum.IsAlive) = ...
commandLookups.value("z16r") = d
[etc...]
return commandLookups
The benefit of this data-driven design is that you could even rewrite the GetLookupsDict() function to pull data from a text file, JSON blog, CSV file, XML, HTTPS url, etc.
Personally I think you’re going about this all wrong. I would make the serial connection class into the parent class and either make the configuration loadable or just make subclasses for each model. Then you can keep your RouterModel as a string and when you need a new serial connection, you’d have a shared method on serial connection that returns the right subclass for the device. As long as the interfaces stay the same, you shouldn’t have any trouble. Better yet, use interfaces to make sure the api stays the same.
I like @Mike_D’s Dictionary approach. I’m setting it up with dictionaries now, but in a slightly different way (below).
At this point I don’t want to have to refactor everything, because this “3 day” project has already morphed into a week due to feature creep and I’m not sure I’ll be able to finish it today. There is an outside chance I might sell this app, though I doubt there’s much of a market for control software for discontinued, unsupported 25 year old AES/EBU Audio routers - most of the industry uses more modern audio interconnect formats these days, thus different hardware. So like most of my apps, this is for internal use. I’m interested in fine-tuning my programming skills and taking a different approach, so I’ll keep @Greg_O 's idea in mind, but for now I think I want to stick with what I’ve built, because I’d like to get this installed on the router early next week to capture tape for a client.
What I’m now doing is this:
RouterConnect is my custom SerialConnection class. I have eliminated the Enumerations entirely. Instead I made a dictionary for each model, each containing all of the commands, with the value specific to that model.
//Populate each of the command lists, by model
Z8R = new Dictionary
Z8R.value("Begin") = 165
Z8R.value("End") = 90
Z8R.value("GetRoute") = 32
Z8R.value("SendRoute") = 16
Z8R.value("IsAlive") = 64
Z16R = new Dictionary
Z16R.value("Begin") = 165
Z16R.value("End") = 90
Z16R.value("GetRoute") = 32
Z16R.value("SendRoute") = 16
Z16R.value("IsAlive") = 64
//And so on for the rest of them
RouterCommand is a dictionary.
RouterModel is a property of the class. I’ve converted this to a Computed Property. When it gets set, it moves the appropriate command set dictionary into RouterCommand
select case value
case "z-8.8r"
RouterCommand = Z8R
case "z-16.16r"
RouterCommand = Z16R
case "z-32.32r"
RouterCommand = Z32R
case "z-64.64r"
RouterCommand = Z64R
case "z-128.128r"
RouterCommand = Z128R
case "z-256.256r"
RouterCommand = Z256R
end select
mRouterModel = value
Now, I just need to call on RouterCommand.Value("End") and it will drop the correct value into my command string for the RouterModel that has been set in the class.
Greg’s suggestion of constants is still valid. My personal philosophy is that any time you are typing a string constant twice or more as a literal in the code, it should be a constant instead. This makes future maintenance easier, as well as internationalization for string constants that surface in the UI.