Author Topic: How to efficietly code user selectable logic?  (Read 9056 times)

0 Members and 1 Guest are viewing this topic.

Offline jnzTopic starter

  • Frequent Contributor
  • **
  • Posts: 593
How to efficietly code user selectable logic?
« on: August 05, 2015, 06:21:01 pm »
So I have project coming up that I'd like the user to be able to select some options from a drop down style list, but it gets pretty complex quickly. For example:

[When][Signal#1] [==] [5V] [Send Off Signal]
could be replaced with
[If][Signal#1] [>] [3V] [Toggle State]
or
[When][Signal#1] [==][5V]
[AND][Signal#2][==][0V]
[THEN][Send ON Signal]

The issue I'm seeing is I would normal just cleanly set the logic with something like like:
Code: [Select]
If (signal1.voltage==5) Transmit(off);

But when you start allowing the user to select the logic... I see something more like
Code: [Select]
if (USER_LOGIC_IS_EQUALS)
 if (signal.voltage == USER_LOGIC_VOLTAGE_VALUE)
  if (USER_LOGIC_WANTS_MESSAGE_SENT)
   Transmit(USER_LOGIC_MESSAGE_STATUS)
if (USER_LOGIC_IS_LESS_THAN)
 if (signal.voltage <= USER_LOGIC_VOLTAGE_VALUE)
  if (USER_LOGIC_WANTS_MESSAGE_SENT)
   Transmit(USER_LOGIC_MESSAGE_STATUS)... etc
The issue I have with this, it is seems that with the number of options I'm giving the user for how the system works, it'll get extremely hard to read/debug and complex/spaghetti very quickly.

Are there some rules are or way to lay things like this out in a way that keeps readability? I see systems that have large settings and options in XML type formats that seem very complex, so I'm sure where are tricks to this. I had briefly considered dynamically assembling code to run in ram, but that seems like a really bad idea! I had wondered if combining as many options as I could like USER_LOGIC_WANTS_EQUALS_AND_TRANSMIT_A_MESSAGE could simplify the readability but make the code writing/storage a lot higher.
 

Offline piranha32

  • Supporter
  • ****
  • Posts: 151
  • Country: us
    • random ramblings of an engineer
Re: How to efficietly code user selectable logic?
« Reply #1 on: August 05, 2015, 10:24:45 pm »
So I have project coming up that I'd like the user to be able to select some options from a drop down style list, but it gets pretty complex quickly. For example:

[When][Signal#1] [==] [5V] [Send Off Signal]
could be replaced with
[If][Signal#1] [>] [3V] [Toggle State]
or
[When][Signal#1] [==][5V]
[AND][Signal#2][==][0V]
[THEN][Send ON Signal]

The issue I'm seeing is I would normal just cleanly set the logic with something like like:Code: [Select]If (signal1.voltage==5) Transmit(off);

But when you start allowing the user to select the logic... I see something more likeCode: [Select]if (USER_LOGIC_IS_EQUALS)
 if (signal.voltage == USER_LOGIC_VOLTAGE_VALUE)
  if (USER_LOGIC_WANTS_MESSAGE_SENT)
   Transmit(USER_LOGIC_MESSAGE_STATUS)
if (USER_LOGIC_IS_LESS_THAN)
 if (signal.voltage <= USER_LOGIC_VOLTAGE_VALUE)
  if (USER_LOGIC_WANTS_MESSAGE_SENT)
   Transmit(USER_LOGIC_MESSAGE_STATUS)... etc
The issue I have with this, it is seems that with the number of options I'm giving the user for how the system works, it'll get extremely hard to read/debug and complex/spaghetti very quickly.

Are there some rules are or way to lay things like this out in a way that keeps readability? I see systems that have large settings and options in XML type formats that seem very complex, so I'm sure where are tricks to this. I had briefly considered dynamically assembling code to run in ram, but that seems like a really bad idea! I had wondered if combining as many options as I could like USER_LOGIC_WANTS_EQUALS_AND_TRANSMIT_A_MESSAGE could simplify the readability but make the code writing/storage a lot higher.

Separate data from the logic. You can encode states of input signals as integer variables, and then describe each condition as a line in an array, together with associated action. The details will depends on your implementation, but code algorithm may look like this (the code has never been tested, most likely is full of bugs, will not compile with any known compiler, but should give you a starting point to write your own implementation):
1. Support functions:
Code: [Select]
//Function tests if variable state equals required state.
//Value -1 of the required state of the variable means that the state of the variable should not be tested
//Input:
//  required - required state
//  variable - value of the tested variable
//Returns:
//  false - state of the variable does not equal to the requested state
//  true - value of the variable equals to the requested state, or variable not tested.
int testCondition(int required, int variable)
{
  if(required<0 || variable<0)
    return true;
  return required==variable
}

//Function translates continuous values of a variable into discrete state identifiers.
//Input:
//  state_info: an array of triplets describing upped and lower limit of the values associated with the state, and the identifier of the state
//  value - value of the tested variable
//Returns
// -1 - Input value not within limits
// state - value of the "state" field in the array for matching entry
//
//The "state_info" input is an array of value2state_s structs  describing state assignments:
//  value[0] -  lower limit of variable values
//  value[1] - upper limit of variable values
//  state - state ID
//State ID with value <0 indicates end of the array
struct {
  float value[2];
  int state;
} value2state_s;

int value2State(struct value2state_s  state_info[], float value)
{
  state=-1;
  for(int i=0;state_info[i].state>0;i++)
  {
      if(value >= state_info[i].value[0] && value < state_info[i].value[1])
      {
           state=state_info[i]state;
           break;
      }
  }
  return state;
}

//Function finds in the input array associating variable states and the action, the first entry corresponding to the current state of input variables
//Input:
//  state2action: - pointer to the translation array
//  variables - an array with states of the variables
//  variables_nb - length of he array with variables
//Returns:
//  NULL if no matching action found
//  pointer to the action callback if match found
//

//"state2action" is an array of structures describing variable state and pointer to the callback function.
//callback set to NULL indicates the end of the array
struct {
      int states[];
      function *callback;
} state2action_s;

function* findCallback(struct state2action_s state2action[], int variables[], int variable_nb)
{
  for(int i=0;state2action[i].callback!=NULL;i++)
  {
    found=true;
    for(int j=0;j<variable_nb;j++)
      found=found && testCondition(state2action[i][.states[j], variables[j])
    if(found)
      return state2action[i].callback;
  }
  //if we got until here, no action was found
  return NULL;
}

2. Assuming that you have 2 input variables, and two actions you ant to invoke under the following conditions:
Code: [Select]
action1: variable1 < 3 and (variable 2 > 0 and variable2 < 1)
action2: variable1 >=3
your code might look like this:
Code: [Select]
void action1()
{
 Send(MESSAGE1)
}

void action2()
{
 Send(MESSAGE2)
}

#define VARIABLE_NB=2
float variables[VARIABLE_NB]=[variable1, variable2];
int states[VARIABLE_NB];

//translate values of variables into variable state IDs
struct value2state_s variables2states[][]=[
  //Values for variable 1
  [
    {values:[-9999,3], state:1}, // variable1 <3
    {values:[3,9999], state:2},  // variable1 >=3
    {values:[0,0],state:-1}
  ],
  //Values for variable 2
  [
    {values:[0,1], state:1}, //variable2 >=0 and <1
    {values:[0,0],state:-1}
  ]
];

for(int i=0;i<VARIABLE_NB;i++)
  states[i]=value2State(variables2states[i],variables[i])


//translation from variable state to callbacks:
struct state2action_s actions[]=[
  {states:[1,1], callback: &action1}, //variable1 <3 and variable2 between 0 and 1
  {states:[2,-1], callback: &action2} // variable1 >=3, variable 2 not important
];

//find callback
callback=findCallback(actions,variables,VARIABLE_NB);
//call it if found
if(callback!=NULL)
  *callback;

That's a lot of code, but consider it a tool, which will allow you to build clean and easy to understand programs.
You can expand the definition of the state-action structure to add parameters to the callbacks, but it will add more complexity to the code.

Offline jnzTopic starter

  • Frequent Contributor
  • **
  • Posts: 593
Re: How to efficietly code user selectable logic?
« Reply #2 on: August 05, 2015, 10:28:22 pm »
whoa.

I'm going to need to process that! You have some interesting ideas tho!
 

Offline piranha32

  • Supporter
  • ****
  • Posts: 151
  • Country: us
    • random ramblings of an engineer
Re: How to efficietly code user selectable logic?
« Reply #3 on: August 05, 2015, 10:30:52 pm »
whoa.

I'm going to need to process that! You have some interesting ideas tho!

I know, it's a lot of code, but the point is that the entire spaghetti-code with hard-coded conditions can be reduced to two, fairly simple, and easy to modify arrays.

Offline HackedFridgeMagnet

  • Super Contributor
  • ***
  • Posts: 2028
  • Country: au
Re: How to efficietly code user selectable logic?
« Reply #4 on: August 06, 2015, 12:07:59 am »
You're right its that type of code that can hide a silly bug. Such as == instead of >=

To efficiently code the logic you may to able to simplify the logic first using Boolean Algebra.
Also probably use a State Diagram. With transitions and states. Enums for the states.

depending on the resultant complexity you could implement as arrays of states or functions or as switch as statements.

 

Offline piranha32

  • Supporter
  • ****
  • Posts: 151
  • Country: us
    • random ramblings of an engineer
Re: How to efficietly code user selectable logic?
« Reply #5 on: August 06, 2015, 12:15:34 am »
You're right its that type of code that can hide a silly bug. Such as == instead of >=

To efficiently code the logic you may to able to simplify the logic first using Boolean Algebra.
Also probably use a State Diagram. With transitions and states. Enums for the states.

depending on the resultant complexity you could implement as arrays of states or functions or as switch as statements.

State machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.

Online Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: How to efficietly code user selectable logic?
« Reply #6 on: August 06, 2015, 10:52:08 am »
Someone somewhere must have done this before. You could save weeks in programming if you can get/buy a library for this.
 

Offline amyk

  • Super Contributor
  • ***
  • Posts: 8264
Re: How to efficietly code user selectable logic?
« Reply #7 on: August 06, 2015, 02:25:19 pm »
I had briefly considered dynamically assembling code to run in ram, but that seems like a really bad idea!
That could be a great way to do it, probably the fastest too. Effectively a little expression compiler.
 

Offline jnzTopic starter

  • Frequent Contributor
  • **
  • Posts: 593
Re: How to efficietly code user selectable logic?
« Reply #8 on: August 06, 2015, 04:10:54 pm »
whoa.

I'm going to need to process that! You have some interesting ideas tho!

I know, it's a lot of code, but the point is that the entire spaghetti-code with hard-coded conditions can be reduced to two, fairly simple, and easy to modify arrays.

Yea, its not bad. It's just something I'll need to examine. I've always coded state machines with polling and superloops when given the choice, so I never really messed around with callbacks. I'm in an RTOS now but I think I'm following the basic initial idea you have laid out. Sort of. I'll need to carefully read a few more times!



I had briefly considered dynamically assembling code to run in ram, but that seems like a really bad idea!
That could be a great way to do it, probably the fastest too. Effectively a little expression compiler.

Oh man, just seems to very dangerous! Plus I'd have the issue of the micro having to sanitize the user's input code. I considered it for like a minute then figured I have other options that seem easier to debug.
 

Offline jnzTopic starter

  • Frequent Contributor
  • **
  • Posts: 593
Re: How to efficietly code user selectable logic?
« Reply #9 on: August 06, 2015, 04:24:11 pm »
State machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.


To efficiently code the logic you may to able to simplify the logic first using Boolean Algebra.
Also probably use a State Diagram. With transitions and states. Enums for the states.

depending on the resultant complexity you could implement as arrays of states or functions or as switch as statements.

Can either of you guys maybe elaborate on state diagram / state tables, in terms of an example like this? Even really rough. I'm just in the information gathering stage right now. Eventually, the configuration tool will be web based, and I'll have to hire out a developer to implement that, I want to be sure of what I want from him before I ever talk to anyone about it...

Whatever I come up with will have to be read in over a serial stream. If it's array based, that seems pretty easy to get into rom from serial. But I suppose I'll be on my own to define the data structure of Function / Variables / Conditions / States / Additional Logic... That alone seems complicated enough just in text file to eventual execution! As for security, I think I'd likely just either encrypt the data on the web / decrypt on chip to ensure it's valid, or sign the data and check sig on the chip.

Wondering... As to an array of functions, I'd have something like *myFunc(var1, var2) as an element inside myFuncArray[5], calling with something like myFuncArray[5](v1, v2) ? I haven't used pointers to functions like that before, but I understand it can be done in a way that's likely not what I just wrote. I know my knowledge is basic, I haven't pushed limits of conventional programming because my job is usually: get-it-done-asap.

The one thing I'm super happy about here is I was right to not consider switches and ifs. I had a suspicion but now I'm fairly certain of it!  ^-^
 

Offline piranha32

  • Supporter
  • ****
  • Posts: 151
  • Country: us
    • random ramblings of an engineer
Re: How to efficietly code user selectable logic?
« Reply #10 on: August 06, 2015, 04:27:41 pm »
whoa.

I'm going to need to process that! You have some interesting ideas tho!

I know, it's a lot of code, but the point is that the entire spaghetti-code with hard-coded conditions can be reduced to two, fairly simple, and easy to modify arrays.

Yea, its not bad. It's just something I'll need to examine. I've always coded state machines with polling and superloops when given the choice, so I never really messed around with callbacks. I'm in an RTOS now but I think I'm following the basic initial idea you have laid out. Sort of. I'll need to carefully read a few more times!

Callback is is just another way to call a function. As long as you don't do dynamic linking, it should not be much different from calling the function explicitly (modulo few compile-time optimizations, and maybe a bit more mess with caching during runtime). If you don't like calling functions through pointers, you can modify findCallback to return an ID of detected action and use switch statement to call hard-coded actions. A bit less flexibility, but should not have huge impact on code complexity.
The code as sketched should be pretty deterministic and run in bounded time, but for reliability and safety I would add error checks and change stop conditions in loops to add hard upper limit on number of iterations (instead of relying on specific value in one of the fields).

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19469
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: How to efficietly code user selectable logic?
« Reply #11 on: August 06, 2015, 09:43:47 pm »
You're right its that type of code that can hide a silly bug. Such as == instead of >=

To efficiently code the logic you may to able to simplify the logic first using Boolean Algebra.
Also probably use a State Diagram. With transitions and states. Enums for the states.

depending on the resultant complexity you could implement as arrays of states or functions or as switch as statements.

State machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.

Most definitely.

There can be other advantages: easy transition between logic and software as a system evolves, easy to keep a log of the history of what happened, can have many many copies of the same state machine with each its own state vector stored in RAM, etc.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline HackedFridgeMagnet

  • Super Contributor
  • ***
  • Posts: 2028
  • Country: au
Re: How to efficietly code user selectable logic?
« Reply #12 on: August 06, 2015, 11:43:43 pm »
Quote
State machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.
Accepting that you have done this before, multiple times and it works for you.
I think the code can be kept quite sensible and easy to manage just as switch statements for smaller systems.
All transitions should be defined in the design of the diagram and the implementation just follows. What you need to consider is all the possible inputs and states, to make sure you don't miss a transition. I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions. Maybe this is what tggzzz means.

Quote
There can be other advantages: easy transition between logic and software as a system evolves, easy to keep a log of the history of what happened, can have many many copies of the same state machine with each its own state vector stored in RAM, etc.
I either don't understand what you mean here. Or don't see a huge advantage. 

I guess what the OP wants is some example code to compare and to work from. I haven't got any easily available.
 

Offline piranha32

  • Supporter
  • ****
  • Posts: 151
  • Country: us
    • random ramblings of an engineer
Re: How to efficietly code user selectable logic?
« Reply #13 on: August 07, 2015, 02:06:51 am »
One small request: if you reply to several people in one message, please add attributions to the quotations. Otherwise it's difficult to follow the discussion.
Quote
State machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.
Accepting that you have done this before, multiple times and it works for you.
I think the code can be kept quite sensible and easy to manage just as switch statements for smaller systems.
All transitions should be defined in the design of the diagram and the implementation just follows. What you need to consider is all the possible inputs and states, to make sure you don't miss a transition. I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions. Maybe this is what tggzzz means.
Implementation of sequential machine as a maze of ifs and switches will be maintainable only for trivial cases. Cyclomatic complexity of such code can be as high as (# of states)*(# of possible inputs). For reference: complexity of code modules should be kept below 10, module with complexity between 20 and 50 is considered very risky, anything with complexity higher than 50 is unmaintainable and untestable.
Take for example simple case, which almost everybody has done at least once: quadrature encoder. 2 input lines, what gives 4 input values, at least 4 states. Cyclomatic complexity of such code, with full implementation of all transitions in the graph, would be around 16 (or more). Very close to the high risk zone. Now, take unoptimized implementation (trivial implementation as Mealy machine, without any minimization of state diagram), which I wrote in 15 minutes after I got tired of looking for a bug in code written by someone who thought that ifs and switches make easily readable code (this is just part of the code I worked on):
Code: [Select]
unsigned char PROGMEM transitionTable[]=
        {     //00 01 10  11
                 0, 5, 2,  1,      //0
                 0, 3, 4,  1,      //1
                 0, 6, 2,  1,      //2
                 0, 3, 6,  1,      //3
                 0, 6, 4,  1,      //4
                 0, 5, 6,  1,      //5
                 0, 6, 6,  1       //6
        };

unsigned char PROGMEM outputTable[]=
{     //      00                   01                  10                  11
        0,                  0,                  0,                  QENCODER_DIR_ERROR,  //0
        QENCODER_DIR_ERROR, 0,                  0,                  0,                   //1
        QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, 0,                  QENCODER_DIR_LEFT,   //2
        QENCODER_DIR_LEFT,  0,                  QENCODER_DIR_ERROR, QENCODER_DIR_ERROR,  //3
        QENCODER_DIR_RIGHT, QENCODER_DIR_ERROR, 0,                  QENCODER_DIR_ERROR,  //4
        QENCODER_DIR_ERROR, 0,                  QENCODER_DIR_ERROR, QENCODER_DIR_RIGHT,  //5
        0,                  QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, 0                    //6
};

unsigned char QEncoderDecode(unsigned char switches,QEncDecoderState *state)
{
    switches&=QENCODER_SW_MASK;
    unsigned char idx=state->currentState*4+switches;
    state->currentState=pgm_read_byte(&transitionTable[idx]);
    return pgm_read_byte(&outputTable[idx]);
}
Handles properly all possible situations, including contact bounce and "impossible" transitions. Complexity is close to 1. Equivalent implementation as conditional statements would be several pages long and completely unreadable.

BTW. I have no idea about what binary files you're talking about.

Online Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: How to efficietly code user selectable logic?
« Reply #14 on: August 07, 2015, 07:36:25 am »
If you're not very experienced with these more complicated dynamic object oriented programming designs. You can do a practice run in plain C. C++ is easier, since object oriented is more in the purpose of c++.

Think of a routine that read a finite amount of inputs, with a finite amount of conditions, and a finite amount of outputs.
Now take all the variables used to execute that function, and put them in a 3 typedef structures or classes.
Input_t, Expression_t and Output_t.
Where Input describes the type of input (current/voltage/boolean/time),
Expression contains some operation and a parameter (equals to, is greater than or is within) the Input to use and the Output to perform.
And Output defines the hardware to use as output, a GPIO, dac or adc or timer. Generally, Input_t and Output_t are const because they're hardware specific, and are only referenced by pointers. They'll use a generic approach, for example a word defining the status and an enum defining the type. Or with C++, an ADC output derives from an virtual Output interface class.

Now you can use 1 piece of code to run on multiple pieces of datasets.

Next, you'll need to write smarts that parses a line like:
[If][Signal#1][>][1.34V][then][output1][true];
set expression to greaterthan.
set input to signal1.
set expression parameter to 1.34.
set output to output1.
set output parameter to boolean.
Into the structures you're using in your logic table.

Basically you run the array of Expression_t's through your routine, and you're done.
Not the most beautiful solution, but it works.

Aren't there any related topic or excellent examples on this kind of software architecture in C and C++.
Because in unitversity, you don't learn to program like this. They only skip over the language features. You'll have to be creative yourself to build fancy code with it.
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19469
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: How to efficietly code user selectable logic?
« Reply #15 on: August 07, 2015, 08:36:59 am »
One small request: if you reply to several people in one message, please add attributions to the quotations. Otherwise it's difficult to follow the discussion.
Yes indeed; simply use the "quote" link at the top right of the message!
Quote
Take for example simple case, which almost everybody has done at least once: quadrature encoder. 2 input lines, what gives 4 input values, at least 4 states. Cyclomatic complexity of such code, with full implementation of all transitions in the graph, would be around 16 (or more). Very close to the high risk zone. Now, take unoptimized implementation (trivial implementation as Mealy machine, without any minimization of state diagram), which I wrote in 15 minutes after I got tired of looking for a bug in code written by someone who thought that ifs and switches make easily readable code (this is just part of the code I worked on):
Code: [Select]
omitted for brevity
Handles properly all possible situations, including contact bounce and "impossible" transitions. Complexity is close to 1. Equivalent implementation as conditional statements would be several pages long and completely unreadable.
As a schoolkid writing my first machine code program (on a 39(!) bit machine) to convert one 5 channel paper tape code to another, I triumphally re-invented FSMs without realising it. My ~70 word program worked first time whereas someone else's 3ft long (teleprinter roll of paper) Algol-60 program never worked.
Quote
BTW. I have no idea about what binary files you're talking about.
Neither have I.

There's an additional twist you can easily add in C, which is highly beneficial if you have to do any processing on entering a state. All you do is have another 2D event/state table which contains pointers to functions that do the processing. I first used that 30 years ago when reading in floating point numbers.

I've seen too many "computer science graduates" that vaguely remember FSMs, but only in the context of compilers.

There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19469
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: How to efficietly code user selectable logic?
« Reply #16 on: August 07, 2015, 08:45:29 am »
I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions.

Diagrams don't have to be binary files. It is possible to have a textual definition that is processed into a pretty picture. You could even use C as the textual description if you wanted to be peverse.

See http://smc.sourceforge.net/ for one example. You could easily add a VHDL/Verilog backend if you are targeting FPGAs. There are many descriptions of suitable Verilog/VHDL design patterns.

Quote
Maybe this is what tggzzz means.
Quote
There can be other advantages: easy transition between logic and software as a system evolves, easy to keep a log of the history of what happened, can have many many copies of the same state machine with each its own state vector stored in RAM, etc.
I either don't understand what you mean here. Or don't see a huge advantage. 

Which of the three concepts don't you understand?

Note the word "can", not "will in all cases".
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
Re: How to efficietly code user selectable logic?
« Reply #17 on: August 07, 2015, 09:09:04 am »
A small tokenizer/parser and RPN interpreter is not too difficult to create.

Edit: In the spirit of RPN and Forth-like language and just to show the idea what the tokenizer/parser might produce and which is quite simple to be exectued by a small Forth/RPN-like interpreter:

[When][Signal#1] [==] [5V] [Send Off Signal]:

PUSH 1
SIGNAL_GET
PUSH 5
PUSH EQ
COMPARE
IF
CALL SEND_OFF_SIGNAL
ENDIF

[If][Signal#1] [>] [3V] [Toggle State]:

PUSH 1
SIGNAL_GET
PUSH 3
PUSH GT
COMPARE
IF
CALL TOGGLE_STATE
ENDIF

[When][Signal#1] [==][5V] [AND][Signal#2][==][0V] [THEN][Send ON Signal]:

PUSH 1
SIGNAL_GET
PUSH 5
PUSH EQ
COMPARE
PUSH 2
SIGNAL_GET
PUSH 0
PUSH EQ
COMPARE
AND
IF
CALL SEND_ON_SIGNAL
ENDIF
« Last Edit: August 07, 2015, 01:22:30 pm by Kalvin »
 

Offline Chris C

  • Frequent Contributor
  • **
  • Posts: 259
  • Country: us
Re: How to efficietly code user selectable logic?
« Reply #18 on: August 07, 2015, 11:48:09 pm »
There was some mention of processing this over a serial stream, which might preclude the possibility of preparsing and arranging operators according to correct precedence.  This can be solved by using two stacks, one for operands and one for operators.  Instead of immediately executing operators, push them onto their stack; but before doing so, check for and execute any pre-existing operators of equal or higher precedence.

[Signal#1] [==][5V] [AND][Signal#2][==][0V] THEN

SIGNAL_GET (pushed to operator stack with priority 3)
1 (pushed to operand stack)
== (priority 2, 'SIGNAL_GET' on stack has higher priority so is evaluated before pushing '==')
5 (pushed to operand stack)
AND (priority 1, '==' on stack has higher priority so is evaluated before pushing 'AND')
SIGNAL_GET (pushed to operator stack with priority 3)
2 (pushed to operand stack)
== (priority 2, 'SIGNAL_GET' on stack has higher priority so is evaluated before pushing '==')
0 (pushed to operand stack)
THEN (priority 0, '==' then 'AND' is evaluated)
« Last Edit: August 07, 2015, 11:58:11 pm by Chris C »
 

Offline HackedFridgeMagnet

  • Super Contributor
  • ***
  • Posts: 2028
  • Country: au
Re: How to efficietly code user selectable logic?
« Reply #19 on: August 08, 2015, 01:13:30 am »
One small request: if you reply to several people in one message, please add attributions to the quotations. Otherwise it's difficult to follow the discussion.
Quote
State machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.
Accepting that you have done this before, multiple times and it works for you.
I think the code can be kept quite sensible and easy to manage just as switch statements for smaller systems.
All transitions should be defined in the design of the diagram and the implementation just follows. What you need to consider is all the possible inputs and states, to make sure you don't miss a transition. I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions. Maybe this is what tggzzz means.
Implementation of sequential machine as a maze of ifs and switches will be maintainable only for trivial cases. Cyclomatic complexity of such code can be as high as (# of states)*(# of possible inputs). For reference: complexity of code modules should be kept below 10, module with complexity between 20 and 50 is considered very risky, anything with complexity higher than 50 is unmaintainable and untestable.
Take for example simple case, which almost everybody has done at least once: quadrature encoder. 2 input lines, what gives 4 input values, at least 4 states. Cyclomatic complexity of such code, with full implementation of all transitions in the graph, would be around 16 (or more). Very close to the high risk zone. Now, take unoptimized implementation (trivial implementation as Mealy machine, without any minimization of state diagram), which I wrote in 15 minutes after I got tired of looking for a bug in code written by someone who thought that ifs and switches make easily readable code (this is just part of the code I worked on):
Code: [Select]
unsigned char PROGMEM transitionTable[]=
        {     //00 01 10  11
                 0, 5, 2,  1,      //0
                 0, 3, 4,  1,      //1
                 0, 6, 2,  1,      //2
                 0, 3, 6,  1,      //3
                 0, 6, 4,  1,      //4
                 0, 5, 6,  1,      //5
                 0, 6, 6,  1       //6
        };

unsigned char PROGMEM outputTable[]=
{     //      00                   01                  10                  11
        0,                  0,                  0,                  QENCODER_DIR_ERROR,  //0
        QENCODER_DIR_ERROR, 0,                  0,                  0,                   //1
        QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, 0,                  QENCODER_DIR_LEFT,   //2
        QENCODER_DIR_LEFT,  0,                  QENCODER_DIR_ERROR, QENCODER_DIR_ERROR,  //3
        QENCODER_DIR_RIGHT, QENCODER_DIR_ERROR, 0,                  QENCODER_DIR_ERROR,  //4
        QENCODER_DIR_ERROR, 0,                  QENCODER_DIR_ERROR, QENCODER_DIR_RIGHT,  //5
        0,                  QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, 0                    //6
};

unsigned char QEncoderDecode(unsigned char switches,QEncDecoderState *state)
{
    switches&=QENCODER_SW_MASK;
    unsigned char idx=state->currentState*4+switches;
    state->currentState=pgm_read_byte(&transitionTable[idx]);
    return pgm_read_byte(&outputTable[idx]);
}
Handles properly all possible situations, including contact bounce and "impossible" transitions. Complexity is close to 1. Equivalent implementation as conditional statements would be several pages long and completely unreadable.

BTW. I have no idea about what binary files you're talking about.
If that's what you want.
 

Offline HackedFridgeMagnet

  • Super Contributor
  • ***
  • Posts: 2028
  • Country: au
Re: How to efficietly code user selectable logic?
« Reply #20 on: August 08, 2015, 01:15:24 am »
I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions.

Diagrams don't have to be binary files. It is possible to have a textual definition that is processed into a pretty picture. You could even use C as the textual description if you wanted to be peverse.

See http://smc.sourceforge.net/ for one example. You could easily add a VHDL/Verilog backend if you are targeting FPGAs. There are many descriptions of suitable Verilog/VHDL design patterns.

Quote
Maybe this is what tggzzz means.
Quote
There can be other advantages: easy transition between logic and software as a system evolves, easy to keep a log of the history of what happened, can have many many copies of the same state machine with each its own state vector stored in RAM, etc.
I either don't understand what you mean here. Or don't see a huge advantage. 

Which of the three concepts don't you understand?

Note the word "can", not "will in all cases".

You answered yourself about the binary files.
 

Offline HackedFridgeMagnet

  • Super Contributor
  • ***
  • Posts: 2028
  • Country: au
Re: How to efficietly code user selectable logic?
« Reply #21 on: August 08, 2015, 01:27:14 am »
I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions.

Diagrams don't have to be binary files. It is possible to have a textual definition that is processed into a pretty picture. You could even use C as the textual description if you wanted to be peverse.

See http://smc.sourceforge.net/ for one example. You could easily add a VHDL/Verilog backend if you are targeting FPGAs. There are many descriptions of suitable Verilog/VHDL design patterns.

Quote
Maybe this is what tggzzz means.
Quote
There can be other advantages: easy transition between logic and software as a system evolves, easy to keep a log of the history of what happened, can have many many copies of the same state machine with each its own state vector stored in RAM, etc.
I either don't understand what you mean here. Or don't see a huge advantage. 

Which of the three concepts don't you understand?

Note the word "can", not "will in all cases".
Note the word "either". I was meaning either in all three cases. As I am sure you are right in what you say, but for now I don't see it. Anyway don't want to look into it further now as it is not my thread.

Thanks for posting the code Piranha, that does help clarify what you guys are talking about.


 

Offline piranha32

  • Supporter
  • ****
  • Posts: 151
  • Country: us
    • random ramblings of an engineer
Re: How to efficietly code user selectable logic?
« Reply #22 on: August 08, 2015, 01:38:36 am »
Thanks for posting the code Piranha, that does help clarify what you guys are talking about.

I'm glad you found it helpful. Repeating what tggzzz already said, the machine description does not to be in binary format. However it should not be parsed during runtime. Especially in RT system, where it is important to have bounded execution time. For code riddled with branches, it may be impossible to determine.

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19469
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: How to efficietly code user selectable logic?
« Reply #23 on: August 08, 2015, 06:15:09 am »
Thanks for posting the code Piranha, that does help clarify what you guys are talking about.

I'm glad you found it helpful. Repeating what tggzzz already said, the machine description does not to be in binary format. However it should not be parsed during runtime. Especially in RT system, where it is important to have bounded execution time. For code riddled with branches, it may be impossible to determine.

There's another consideration. Be very wary about creating your own "domain specific language" (DSL). Almost without exception these start out well and with good intentions, but
  • over time they expand like cancer until nobody including the originator understands them
  • there are no tools for them, e.g. a decent editor or debugger
  • trying to get anyone else to work on/with/in them is difficult, since nobody in their right mind wants to limit their career by specialising in poor non-transferrable skills
  • in the worst case, I've seen a DSL significantly contribute to a company closing a division.
I once made the mistake of creating my own DSL, but it was for a throwaway job with a lifetime of 3 weeks!

OTOH, domain specific libraries are essential and invaluable, since they have none of the above disadvantages
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline jnzTopic starter

  • Frequent Contributor
  • **
  • Posts: 593
Re: How to efficietly code user selectable logic?
« Reply #24 on: August 10, 2015, 04:47:31 pm »
I'm concerned you guys are going slightly over my head on some of this - as well as outside the scope of what I want ;)

The key issues are:

  • I'll have a text file with the config, it'll be in a format that's entirely up to me. I'll need to bring it in over network or USB, store it somewhere and access during the time I'll make output decisions. Storage format will entirely rely on how I code the output section. Could be anything from XML like to encrypted/signed ARM instructions, although of those two I'm more inclined to the former
  • No, I definitely don't want a specific language I create. Or a complex method of my own design to compile the options. Seems like a horribly difficult thing to ever be certain of. As Simple As Possible is desired
  • I'm interested in the array based options to store the config, but I'm still not entirely clear as to this example how the works, you guys keep talking about drawing out the states but as to this example I'm not seeing that.
  • I can severely cut complexity at the cost of user-options, but in the long run, this is probably fine. The user doesn't need infinite options and I could make my life easier by giving them a push on certain things, but that doesn't really solve anything. I'll still need a system that works for 5 options as it does 50 I suppose.

Taking a step back, what are some variations if I wanted to keep it slightly more simple than writing my own interpreter - using arrays and points to the functions?
« Last Edit: August 10, 2015, 04:50:00 pm by jnz »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf