Selecting Data Items dynamically for logging application - Database / XML / Other? - c#
I am working on an application that communicates with an embedded device to log or view data from networked machinery. The machinery is networked and communicates with data logging device via CAN (ISO 15765-4). The logging device uses an unmanaged DLL API, which I have already created a working Wrapper - and I can send single hard coded requests to the logger and properly parse the data (With the help of this website) - I do not need help with wrapping the dll, parsing CAN, or communication with the logging device, that is all working. What I am needing help with is an overall strategy for setting up the individual data items to be logged - through a database, xml serialization, or have the data embedded within the code. My thoughts are a database would be ideal for this but I am struggling on how to implement this in a good OOP strategy.
The next step is to allow user to dynamically select the data they wish to view / log, and have the application setup the requests / responses dynamically. The application that my company is currently using for this function is all in c++, and it is extremely buggy and hard to maintain as the original coding team has moved on. I am NOT trying to directly port the c++ code or structure to C#, just the functionality. I am providing some snippets of the c++ code just as an example of the type of data I am trying to work with. I am using C# because of the relative ease for creating a GUI compared to c++.
The data being logged can be 1 of 3 different types:
Boolean (true / false)
Linear (y=mx+b)
Switched enumerations (1 = Cold, 2 = Warm, 3 = Hot, 4 = Overheated)
Here are some general requirements for the application
There are approximately 400 unique data items that can be logged.
Typically, there will be between 20 - 50 items monitored at any one time, but the capability needs to exist to log ALL supported data.
Not all of the machinery supports all data items - so there is a pruning request to determine what is supported by each machine.
CAN requests are FUNCTIONAL addressing rather than PHYSICAL addressing - so requests are made with a broadcast type message, and all supporting equipment will respond. (This just means I may get multiple responses from the same request)
Data should be able to be viewed live - or logged to file for later analysis. Any saved logs would be on the order of 10,000 frames (probably never exceeding 50,000 frames)
Typical request / response turn around is ~150mS, with each request occurring in a sequential manner (must repeat each request in a loop)
So here is a typical use case:
User connects application to embedded device
Query all connected machinery for supported data
Present user with a list of supported data
User selects data from this set to log/display
"Stream" data to GUI in text or graphical format with option to save
View saved data (without embedded device connected)
I have all of the potential supported data items in an excel table currently with the thought that I could port it in to a database or xml file. Although this data is not "Top Secret", we would like to hide/encrypt it from customers/end users if possible (although I can work on that later - just bringing the point up now in case it changes the strategy)
I have been searching the web over the last couple of weeks, and I am just hitting a mental block on how to deal with this. Do I read the data list from database and create a class that houses all of the parameters? Do I create a separate class for each data item? Structure? etc? I just cant quite figure out the strategy to get me started. I am not looking for someone to do this for me, just a direction to go (small code examples would be helpful as a starting point however) I know that I will need to use threading to keep the GUI updated, so if this factors in to any strategy as well. Once the initial setup is made, it is basically an infinite loop until user cancels the action or there is a predefined trigger to halt the data flow. I know that this will tie up the GUI so a background thread will be needed to keep screen refreshed and responsive.
Below is a snippet from the c++ code -all of the data was embedded within the code, any changes that needed to be made (add or edit existing data items) required changes in about 4 different places, so it is extremely hard to maintain. This is just an example - 1 of each type of data, along with how they structured things. Basically they had a struct like block for each data item, and an enumeration that contained ALL data items. Any switch cases had separate code that defined the states. I am thinking this would be much better suited to a database than 20000 lines of code strictly dealing with the data item values / conversions / text, etc (That is how much c++ code is devoted to this)
Thanks in advance for any help or direction you can give
-Lee
// Example of a Switch Case enumerated data item
ITEMs::PARAM ITEMs::m_CASE_A =
{
0, // starting byte
0, // ending byte
0, // starting bit
4, // ending bit
1, // number of bytes
5, // number of bits
{
_T("Long Description Case A"), // Full description
_T("Case A"), // Abbreviated description
_T("CASEA"), // acronym 1
_T("CASEA"), // acronym 2
DATA_TYPE_INTEGER, // Data type
0x03, // DATA Itentifier
UOM_ENUMERATION, // Raw units to be scaled
UOM_ENUMERATION, // Default units of measure for display
0.0, // Minimum value for numeric types
FALSE, // TRUE if minimum value defined
0.0, // Maximum value for numeric types
FALSE, // TRUE if maximum value defined
-0.5, // Minimum display value
6.5, // Maximum display value
0.0, // Tolerance
FALSE, // TRUE if tolerance defined
0, // Number of digits after decimal pt
NUMERIC_FORMAT_DECIMAL, // Default format for numeric types
0.0, // Scaling per bit
0.0, // Bias to apply after scaling
FALSE, // TRUE if signed for numeric types
_T(""), // String to display when boolean parameter is ON
_T(""), // String to display when boolean parameter is OFF
_T(""), // String to display when numeric value is not valid
TRUE // Visible to end-user
}
};
// Example of a Linear Data Items
ITEMs::PARAM ITEMs::m_LINEAR_A =
{
0, // starting byte
0, // ending byte
0, // starting bit
7, // ending bit
1, // number of bytes
8, // number of bits
{
_T("Long Description Linear A"), // Full description
_T("LINEAR A"), // Abbreviated description
_T("LIN_A"), // acronym
_T("LIN_A"), // acronym
DATA_TYPE_FLOAT, // Data type
0x05, // DATA Itentifier
UOM_TEMP_DEGC, // Raw units to be scaled
UOM_TEMP_DEGF, // Default units of measure for display
-40.0, // Minimum value for numeric types
TRUE, // TRUE if minimum value defined
215.0, // Maximum value for numeric types
TRUE, // TRUE if maximum value defined
-40.0, // Minimum display value
419.0, // Maximum display value
9.0, // Tolerance
TRUE, // TRUE if tolerance defined
0, // Number of digits after decimal pt
NUMERIC_FORMAT_DECIMAL, // Default format for numeric types
1.8, // Scaling per bit
-40.0, // Bias to apply after scaling
FALSE, // TRUE if signed for numeric types
_T(""), // String to display when boolean parameter is ON
_T(""), // String to display when boolean parameter is OFF
_T(""), // String to display when numeric value is not valid
TRUE // Visible to end-user
}
};
// Example of a Boolean Data Item
ITEMs::PARAM ITEMs::m_BOOL_A =
{
1, // starting byte
1, // ending byte
6, // starting bit
6, // ending bit
1, // number of bytes
1, // number of bits
{
_T("Long Description BOOL A"), // Full description
_T("Boolean A"), // Abbreviated description
_T("BOOL_A"), // acronym
_T("BOOL_A"), // acronym
DATA_TYPE_BOOLEAN, // Data type
0x01, // DATA Itentifier
UOM_NONE, // Raw units to be scaled
UOM_NONE, // Default units of measure for display
0.0, // Minimum value for numeric types
FALSE, // TRUE if minimum value defined
0.0, // Maximum value for numeric types
FALSE, // TRUE if maximum value defined
0.0, // Minimum display value
0.0, // Maximum display value
0.0, // Tolerance
FALSE, // TRUE if tolerance defined
0, // Number of digits after decimal pt
NUMERIC_FORMAT_NONE, // Default format for numeric types
1.0, // Scaling per bit
0.0, // Bias to apply after scaling
FALSE, // TRUE if signed for numeric types
_T("NO"), // String to display when boolean parameter is 1
_T("YES"), // String to display when boolean parameter is 0
_T(""), // String to display when numeric value is not valid
TRUE // Visible to end-user
}
};
// This example sets the user display text for enumerated CASE data items (m_CASE_A in example above)
void PARAM1::FormatValue( ULONG value,
CStdString* pstrResult,
PARAMVALUETYPE* pdResult )
{
UINT uID;
UCHAR uValue = value & 0xFF;
*pdResult = (PARAMVALUETYPE)uValue;
if ( pstrResult != NULL )
{
switch ( uValue )
{
case 0x01: uID = CASE_1; break;
case 0x02: uID = CASE_2; break;
case 0x03: uID = CASE_3; break;
default: uID = CASE_INVALID; break;
}
(*pstrResult).LoadString( uID );
}
}
// CAUTION: Items must be in same order as in the enumeration DATA_ENUM.
ITEMs::PARAM* ITEMs::m_PARAMs[] =
{
&m_CASE_A,
&m_LINEAR_A,
&m_BOOL_A,
...
...
...
};
// Enumeration of all Data Parameters
typedef enum
{
CASE_A,
LINEAR_A,
BOOL_A,
...
...
...
} DATA_ENUM;
Well, although I do not understand all the details (for example, how actually this log data is gathered, I believe this is based on cyclic request/response pattern) I have some design thoughts about it:
whether metadata about data items should be stored in database or XML depends how often it will change and what is distribution model of this software. If it is internal application used by a few people in your company, probably storing local configuration (in XML file for example) will be enough. If the number of users is big, synchronization and management through database will be useful. If it is a application sold to users, it would be in place to have some auto-updates system, so here we come back to the file solution again. Personally I would choose to use a configuration file because it covers more scenarios and it would be probably a XML file due to its clearness.
I would choose to represent each data item as a instance of some subclass (EnumDataItem, LinearDataItem, ...) of abstract DataItem class (or just IDataItem interface). They should implement methods like Serialize/Deserialize (for request/response manipulation) and Format (or ToString simply) for GUI/logging. Definitions of those data items should be loaded from XML file. Then you can define a Parameters class that is a list of IDataItems and can be easily Serialized/Deserialized.
I have a feeling (although it might be wrong) that there can be a lot of similar data items, so I would think about possibility of templating or deriving, so I could specify in XML some templates that can be reused in defining concrete data items.
Related
OutputBuffer not working for large c# list
I'm currently using SSIS to do an improvement on a project. need to insert single documents in a MongoDB collection of type Time Series. At some point I want to retrieve rows of data after going through a C# transformation script. I did this: foreach (BsonDocument bson in listBson) { OutputBuffer.AddRow(); OutputBuffer.DatalineX = (string) bson.GetValue("data"); } But this piece of code that works great with small file does not work with a 6 million line file. That is, there are no lines in the output. The other following tasks validate but react as if they had received nothing as input. Where could the problem come from?
Your OuputBuffer has DatalineX defined as a string, either DT_STR or DT_WSTR and a specific length. When you exceed that value, things go bad. In normal strings, you'd have a maximum length of 8k or 4k respectively. Neither of which are useful for your use case of at least 6M characters. To handle that, you'll need to change your data type to DT_TEXT/DT_NTEXT Those data types do not require a length as they are "max" types. There are lots of things to be aware of when using the LOB types. Performance can suck depending on whether SSIS can keep the data in memory (good) or has to write intermediate values to disk (bad) You can't readily manipulate them in a data flow You'll use a different syntax in a Script Component to work with them e.g. // TODO: convert to bytes Output0Buffer.DatalineX.AddBlobData(bytes); Longer example of questionable accuracy with regard to encoding the bytes that you get to solve at https://stackoverflow.com/a/74902194/181965
I want Unity's slider value to handle large numbers precisely (it only works in the format of "xxx e+X")
I've been using Unity's slider to handle large numbers, and it accepts them. The problem is that the value stored is in the format "xxx e+X" (for instance, 9.999998e+08), enter image description here so, if I wanted to subtract one to this value, or add one to it, it doesn't change the value (for instance, the OnValueChanged callback doesn't get called) as it is only sensitive to larger steps (like, for example, changes over 50-100 units/integers and above. Is there a way to make the slider work with long integers (plain number format)? Thank you. What I tried: The slider max value is set to 999999799 (ended in 799). Then it translates into "9.999998e+08". if I convert this float value (9.999998e+08) to "long", I get 999999800, probably because it doesn't take into account small figures and goes by larger steps. The only way that occurs to me to get the correct "long" is to convert the "slider max value" to string (string stringValue = _mySlider.value.ToString("F0")) and then get the "long" (long longValue= Convert.ToInt64(stringValue)). This way, I get the expected long (999999799). However, this is not suitable since I cannot convert this long value into the slider value. If I wanted to subtract one from the long value (longValue--; (999999798)) and then set the slider value to the long value: _mySlider.value = (float) longValue, the value of the slider doesn't change, it keeps being the same: 9.999998e+08. When translating the number, it yields 999999799 instead of 999999798. (Same happens if I did _mySlider.value--; directly) I need the slider to handle large numbers too.
Dynamic type loading in C#
I'm using a protocol named Ethernet Industrial Protocol (EIP) and I use it to send and receive data from a Programmable Logic Controller (PLC). The data is sent as hex values and in 2 byte sizes as the smallest. So when I ask for what is stored in a memory area in the PLC I get a 2 byte hex value back. Currently I'm using hardcoded approach for parsing the data that comes back. What I'm looking at is the ability to use a config file or something instead to tell what the string of bytes should look like. Let's say I have 3 temperature readings and product type, the 3 temperatures are floating point and use 4 bytes per and the product type is an integer. If I want to change it I need to change the program.. What should I read up on to be able to change this in for instance a config file instead of rewriting the application? I want to be able to say that I have x number of instances of this type and the program should then parse it as that. The program saves all the data it reads into a MySql database. This is a snipet of the code that parses the values as the come in from the PLC. Krakk = (BitConverter.ToUInt16(data, bIndex)); bIndex += 2; Small = (BitConverter.ToUInt16(data, bIndex)); bIndex += 2; Medium = (BitConverter.ToUInt16(data, bIndex)); bIndex += 2; Large = (BitConverter.ToUInt16(data, bIndex)); bIndex += 2; If I use a config file I would like to say something in the lines of: name, uint, size and the program should then read that. So for instance -> Krakk, uint16, 2 and then the program would know that it should change that out for this: Krakk = (BitConverter.ToUInt16(data, bIndex)); bIndex += 2;
Even though I think you are already answering yourself, but here is my answer with some details: You may need to create a new custom configuration section (How to Create and Access a Custom Configuration) with four properties as follows: ReadingName (accepts string value and represents the name of the instance you want to read). StartIndex (accepts integer value and represents the starting index from which you start reading the data bytes of the instance.) Length (accepts integer value and represents the number of data bytes of the instance.) Data Type. In the app.config file you should add a section for each instance you want to read, then in your program you should read these values and act accordingly.
MDB protocol (multidrop bus) - C# serialport communication
I am in the process of developing a MDB software in C# as a payment reader media that communicates with a vending machine through MDB protocol. Currently everything works OK and i am able to communicate with the vending machine. The communication is like expected after reading the MDB protocol but i am having trouble understanding some commands/respones... I just have one question regarding a response i should send back to the vending machine that may be really stupid, but i really don't understand how it should be sent. As shown in the MDB protocol, when i get a POLL from the MDB machine and the state of the reader (my computer) is "Session Idle", i can then send a "Begin Session" command to the vending machine. The commands are sent in bytes over serial port and are shown as HEX or Binary in the MDB protocoll. The BEGIN SESSION command should contain the following: Z1 Begin Session Z2-Z3 Funds Available Z4-Z7 Payment media ID Z8 Payment Type Z9-Z10 Payment Data I understand Z1-Z7 because of good examples in the MDB protocol, but i am having trouble understanding Z8-Z10 (payment type and payment data).. The examples are not self explained in my head.. The MDB protocol says the following: """""""""""""""""""""""""""""""""""""""""""""""""""""""" Z8 : Type of payment: 00xxxxxxb = normal vend card (refer EVA-DTS Standard, Appendix A.1.1 Definitions) x1xxxxxxb = test media 1xxxxxxxb = free vend card xx000000b -0 VMC default prices xx000001b -1 User Group (Z9 = EVA-DTS Element DA701) Price list number (Z10 = EVA-DTS Element LA101)* xx000010b -2 User Group (Z9 = EVA-DTS Element DA701) Discount group index (Z10 = EVA-DTS Element MA403) xx000011b -3 Discount percentage factor (Z9=00, Z10 = 0 to 100**, report as positive value in EVA-DTS Element MA404) xx000100b -4 Surcharge percentage factor (Z9=00, Z10 = 0 to 100**, report as negative value in EVA-DTS Element MA404) * User Group is a segmentation of all authorized users. It allows selective cost allocation. A User Group usually has no direct relation to a price list. Price Lists are tables of prices. Each Price List contains an individual price for each product. Discount Group indicates the Price List on which the Percentage Factor will be applied. If the User Group, the Price List or Discount Group is unknown by the VMC, the normal prices are used (Z8 is defaulted to 00h). Minimum value for Z9 and Z10 is 0. ** Percentages are expressed in binary (00 to 64h) Note: These functions may NOT be supported by all VMCs. Z9-Z10 : Payment data as defined above """""""""""""""""""""""""""""""""""""""""""""""""""""""" Can somebody please tell me how Z8 and Z9-Z10 should be sent to the vending machine. Now i have been sending (in hex): "0x02 (Z8), 0x10 (Z9) and 0x10 (Z10)" which is just a wild guess and it is working. Don't really know why but its probably not correct. How should Z8 and Z9-Z10 be sent?
The EVA-DTS standard is its own separate standard. MDB-ICP is a communication protocol. EVA-DTS is a data format standard. MDB optionally (keyword optionally) uses/integrates EVA-DTS data, which is what its asking for here. EVA-DTS data is human readable values in ascii text/numbers separated by asterixes in a defined order and length. Each unit of data between an asterisk, is called a data element. Z9 in options 1&2, refers to data element DA701, where if you look in Appendix A of the EVA-DTS-6.2.2 standard, the DA701 has the element name "Cashless1 user group number", which is of data type N0 meaning a number without any implied decimal points, a minimum length of 1 digit and a maximum of 13. In MDB, isn't sent as an ASCII string of characters like "15", instead, you use hexadeximal numerical representations, so usergroup 15 would be 0x0F. The usergroup if not using it,you can just put 0x01 for everyone. Its used to group people, with different price tables, giving different prices to different people. Not sure if thats a MDB feature, but any VMC could implement it if not. All optional. Z9 you can look up yourself Z8, First two Most Significant bits are used to indicate if it was a vend card paying (as in credit card, or any real form of cashless payment). the rest of the digits it depends on the vend. you use one of the 4 options (read appendix A section 1.1 of the DTS standard to get definitions of what they mean and which ones are appropriate in that situations. Depending on the option used (1, 2, 3, or 4) that decides what Z9 & Z10 are, like option 3 says z9 would be 0x00 and Z10 would become a percentage (since z8 would describe a percentage discount given), while if u pick option one, Z10 instead contains the value of DTS element LA101. Hope that's not too wordy or incoherent. good luck too, we're competitors.
Storing, using custom logic in C#?
I am writing an app where I will give a set of tests/checks to the user through an UI such as: LayerCount ActiveLayerType EffectCount ActiveEffectType CurrentTool HasSelection HasAlpha etc where the user is able to use them to define a custom logic tree such as: if (LayerCount > 1) { if (ActiveLayerType == LayerType.Blend) { // #1 } else if (ActiveEffectType == EffectType.GaussianBlur) { // #2 } } else if (CurrentTool == Tools.QuickSelect) { if (HasSelection) { // #3 } } Basically I am trying to define some sort of value that will return the Current Level in his custom logic in some way (let's say 1, 2, 3, and so on). Because I need to have the actions of the user interpreted differently based on the Current Level in his logic. You could basically say that I am trying to hash a set of values based on the Current Level. But the problem is, I don't know how to get the current level to store it as a hash key. If I did, I could write something like: var currentActionSet = ActionSetTable [GetCurrentLevel()] and I would get a set of actions the user can perform based on the current level. Although it would be more desirable to have these levels have fixed values instead of values starting from 1 to n, based on how they are ordered. So (ActiveEffectType == EffectType.GaussianBlur)'s branch would always return a fixed value no matter where it's in the logic tree. So to sum up, the problems are: How to define a current level value, preferrably fixed? If using this current level value as key is the best approach, or if there is a better way of doing this?
Perhaps you can use bitflags and OR them together to make your hashcode? int hash = ActiveLayerType | ActiveEffectType | CurrentTool Then at some later point you can AND a perticular flag out again: if( hash & LayerType.Blend == LayerType.Blend ) { ... } //will be true if the LayerType.Blend has previously been OR:ed into the hash variable You can OR together enums of diffrent types too if you cast them to ints first, make sure that the enum bit values doesnt clash though. This approach may or may not be practical for you application, but its something to consider :) more info here: http://msdn.microsoft.com/en-us/library/cc138362.aspx