Defining custom types examples


#1

Hi Victor,
I have been playing around with defining custom types, but, like Sergeant Schultz in Hogan’s Heroes “I know nothing!!!”
Would you be able to post some examples that use arrays, structs, and/or classes? How to create, how to access elements in an array or struct or class? How to refer to class methods in a node?
Thanks,
Ian


#2

Hello, @mogplus8!

Complex examples are coming in the next version. Meanwhile, have you seen the guide article about creating a custom type?

If you can provide a specific example of a problem you want to solve, great, it will help to better understand what articles are necessary. The more concrete the problems will be, the higher the chances to understand it right :wink:


#3

Hi Victor,

Yes I did see that article, and I managed to get a node with a struct (with an array in it) defined, and could be output, and I got it to compile. But I couldn’t get a node to read it, so I have no idea if the first node actually worked or not. Hence this post.

My project is an encoder for an R/C transmitter for model aircraft. So I need to do some analog reads (up to seven different analog inputs (or eleven for an old style transmitter with analog trims, of which I have several), then apply modifiers to each input, such as for stick calibration (as no two pots give the same output), apply different rates based on switch input (so the same stick input will cause different servo throw), same for exponential and curves, and of course mixes. A where a channel, i.e. aileron, is modified by what another channel, i.e. rudder input is at the time. There can be multiple mixes for one output, and multiple outputs for one input.

For things like rates, the switch is typically a three position switch with centre off. So I need to provide two switch ids (i.e. D22 and D23) which correspond to high and low rate, and both off corresponds to mid rate. I also need to provide the actual rates, i.e. 100%, 80% and 70%. So while the transmitter is being used to fly something, those rates will be effective based on the position of the switches.

Of course I need a whole separate function to set the switch ids and rates. In R/C circles setting up all the rates, expo, mixes, servo end points and direction etc. is called “programming” the transmitter. So it would be useful for me to be able to pass around a struct with the variables in it. It may be even more useful to be able to pass around a class, that had methods to test the switches and apply the rates while in flight mode, (and a different one for expo, and a third for curves) and also to set the switch ids and rate values in programming mode. Mixes is a whole other can of worms.

There is an awful lot of stuff that can be programmed (just have a look at OpenTX Companion some time…) but I am not trying to replicate all that, just a (very) small subset.

So I am thinking it would be good if the system could instantiate a Rate class called Aileron in the programming function and set its variables, then be able to refer to that class in the the flight control function and use it to apply the rates to the aileron channel. Not entirely sure how it would all work, I’m still very much on the learning curve, but that’s my general thinking.

The short term plan is to get it working for calibration, as that is fixed for the transmitter (and doesn’t rely on what model is being programmed) so should be fairly easy to set up. Then move on to rates, which does rely on switches and on what model is being programmed. Did I mention that the encoder also needs to be able to manage multiple models, all with different setups? Have I bitten off more than I can chew? Quite possibly…

Thanks, Ian


#4

This will be as much to see if I understand XOD as much as to help you. I’m sure developers will correct any errors, so you may want to wait until later Monday to see if I’m right before spending too much time understanding what I say :slight_smile:

OK…now that I’ve finished typing, I have to come back and say this is probably a lot more rambling ideas than it is helpful guidelines. Maybe I shouldn’t be answering this late at night…

Graphical programming is a little different than “normal” programming. You don’t just create data and refer to it later. You create a data-type, and pass that data to where it is needed via wires between nodes. For example, you could have a node that creates aileron data; maybe it assumes joystick is centered on boot & stores the needed offset. This data can then be wired to other nodes. In your case, it might make sense to wire it to the “rate” node that senses switch position and determines the rate to use and adds that to the aileron structure.

More likely, since rate is shared by many objects, you might create a generic rate node to determine rate to use and pass it to all nodes that need it. If you want to program the 3 rates to select between, you will need some sort of input (POT?) to specify a rate, and some way to signal that it is time to store the rate (3 buttons for 3 rates? A menu to step through the rates?). The 3 rates would then get saved to buffers that only change when a pulse is sent to them and the rate node would select between them based on 2 digital inputs (wired to 3-position switch). Any node needing to use this rate would need an input pin for it wired back to the rate node output. If you save the rate as a number between 1 and 0 (which just happens to be what a pot node outputs), you can just multiply it by the “current” value to get the rate-adjusted value. If you are using a menu system and want to be able to adjust the rate up/down using up/down buttons, that creates additional challenges and requires a display to show menu items and current value…

If you want to save this data when Arduino is reset, then you need to use EEPROM or SD card to save the data to. If you use SD card, you have advantage that you could save configs from a computer & program Arduino to step through files & load the one you want instead of (or in addition to) using buttons & knobs connected to Arduino to program the values (each file could represent a different model; maybe a separate file for shared radio parameters like stick center values).

One of the problems with mixing is determining how to do the mixes. Should rudder be adjusted by the cube of throttle? square? log? directly proportional? Are there additional factors to consider? You will need quite the interface to be able to input this data if you want to change it live while running on the Arduino.

Trying to program this in XOD is likely to result in visual spaghetti code with wires running everywhere. Probably not the best choice for a 1st project :slight_smile: In my experience, just adding a single buffer to store current servo position so I could adjust time to travel to new position based on current position created a pretty good mess. I needed to be able to set it from 4 different locations, so it needed 4 inputs (plus 4 pulse inputs to tell it which numeric input to use and when to update) and had to feed the stored value back to all 4 locations for future use.

There are some downsides to visual programming and it may not be the best choice for complex projects, but it is a LOT easier for non-programmers to pick up and start doing simple things. I’ve seen a lot of people show interest in robots or programming in general, only to see them quit in frustration when they couldn’t figure out or remember all the steps needed to program it in C or some other procedural language. XOD hides a lot of the initialization like pin configuration for you and you don’t have to remember if the call you need was writeDigital, or WriteDigital, or digital_write, or… (my son with reading disability had a LOT of trouble with this.)

I’ve done lots of programming, but I still have trouble adjusting to some of the oddities of visual programming. Sometimes it requires approaching a problem from a different angle than you might be used to.


#5

Hi @gweimer, thanks for all the feedback.

Most modern transmitters use an lcd screen and a set of buttons (usually 6) to do all the programming. Usually a menu system operated by buttons to locate the function you want (like aileron rate), select that then navigate to the switch selector, select the switch used to select the rate, then navigate to the rates inputs and set the actual rates (three of them) with a +/- button combo, then exit. It takes a lot of writing down but to actually do it is quite easy. That’s the programming part. Then while actually flying, it’s just a matter of flicking the switch to change the rate.

I agree that it takes a different way of thinking to use XOD. I was attracted to it because I thought it would simplify the design process, breaking down the big functionality blocks into smaller blocks, and breaking those down again, until you end up with a simple function that can be coded in a few lines of code or that can use the inbuilt functions (analog read, nth-input, select etc.). That seems to work quite well, but the stumbling block for me is how to pass the data around, particularly between the “transmitter programming” function and the “flight mode” function, which are entirely separate. When I say “pass the data around” I mean big chunks of data, not just a single value.

I have managed to program an apply-rates node that looks good although I haven’t tested it. I think logically it should work. But I will need to add some more pins, and some more logic, to set the values for the switch ids and rate values and I think this is where it would be good to be able to pass a struct to the node containing the two switch ids, the three rate values and the channel, all in one pin instead of six (additional) input pins. Then the node would update its values (all stored in the State presumably). Or maybe it would be easier to set up a node that calculates rate, and then create a node for Aileron that contains the rate node with the required values. Dunno, have to try it.

But my main problem is that the PPM generator (that generates the pulse stream that is fed to the RF module) is driven by an eight element int array, and this array is defined in the myinitialisation node as it is read by the ISR that controls the hardware clock. So how to update that array from within a XOD node??? Putting in global scope seems to work (but bearing in mind Victor’s comment about shooting myself in the leg) but maybe there’s a better way.

Just had another thought. Have to go and try something.

:wink: Ian


#6

One option might be to have a setup node to handle all the menu options and output all the values it can change. It could output 3 rate values, but it would be simpler if it took switch inputs and output current selected rate.

If groups if pins are passed to a single node, a new data type could be used to reduce pin count.

If data is shared by nodes (like rate), it can be kept as a separate pin, or it could be copied to each data type passed to the different nodes.


#7

Hi @gweimer,

your option one is part of my grand plan for world domination. Your second point is the reason I started this thread to begin with. I started experimenting with an array but I have not been able to get anything to compile with the array as input. I keep hitting type mismatches.

I had a lucid moment yesterday, and remembered that the AVR hardware timer sets an interrupt flag when the counter hits its max value. This triggers the ISR, but it can also be used inside the program. So I put together a new node that tests for an interrupt and when true does the same thing as the ISR used to do. So now I don’t need an ISR, or global scope. The array that drives the PPM generator is now driven by my new node, and is defined in that node. My only issue now is how to ensure that the node is continuously firing and checking the overflow bit. I don’t know if this is a better way than using an ISR and #global.

This gave rise to a couple more questions. How does XOD handle interrupts? I couldn’t find much information in the docs, and I couldn’t find anything on the net about using the AVR hardware interrupt flag. Guess I’ll just have to make it up as I go along…

Q2. If I define a node that keeps some info in the state part, then use that node in two different patches, will both the nodes reference the same state information? Or will each one maintain its own state information? I suspect the latter, but maybe there’s a case for the former, i.e. so the node will behave like an object. Maybe I should just try it.

:wink: Ian


#8

The delay node uses timer interrupts if you want an interrupt example.

Each copy of a node has its own state. It pretty much has to be this way, or you could not use a generic LED node for multiple LEDs. There might be a way to create an extension to indicate that two nodes are the same, but it would be like “goto”; it might make some things easier, but you are breaking too many good practices to do it (for example, you would now have multiple inputs for a single pin).


#9

Oops. Sorry for joining the discussion late. I see you’ve shared good ideas and questions. I’ll try to answer about C++ classes and objects.

As @gweimer said, that could be not the best idea, but it is possible technically. We’re still trying different patterns of xoding to find simple and effective ways to perform common tasks.

Maybe I did not understand you correctly, but what if you define a custom type patch channel with the following Type:

struct Type {
    uint8_t switch1;
    uint8_t switch2;
    Number rateHigh;
    Number rateMid;
    Number rateLow;

    // This can be a class with methods as well, not a plain struct
};

Then, you can make a node write-rates with inputs for the channel (of input-channel type), all three rates, and a write pulse. Its implementation roughly can be:

void evaluate(Context ctx) {
    // update only on an explicit pulse (such as “Save” button)
    if (!isInputDirty<input_W>(ctx)) return;

    // get the input object
    auto channel = getValue<input_CH>(ctx);

    // mutate the object
    channel.rateHigh = getValue<input_HIGH>(ctx);
    channel.rateMid = getValue<input_MID>(ctx);
    channel.rateLow = getValue<input_LOW>(ctx);
}

You can do something similar for the switches. And then make yet another node which takes the channel object (already setup) and applies its data to real-world devices.

Am I understood your desire correctly?


#10

Gentlemen, thank you for your helpful responses.

@gweimer. Yeah I thought it wouldn’t be that simple. I’m thinking there must be a away around it by structuring the program differently, but I’m still working through that.

Dunno if I’m missing something but I couldn’t see any reference to interrupts in the delay node. It uses the timeout functions, so does that have timer interrupts hidden behind it?
And how about pin interrupts. Is there a way for a pin going high (or low) to trigger a node? So it doesn’t have to be constantly polled?

Ideally, from my perspective, I’d like to have some more options in the UPD pin of the “button” node. It currently has Never, OnBoot and Continuously, How about having OnRIse, OnFall and onChange as well? I understand this is not a trivial change, not all pins (on the 8 bit Arduinos anyway) support interrupts, so you’d have to check for target board, and then ensure that the options only appeared for the pins (input ports) that can support interrupts. And that’s the easy part… I’m sure that are other even bigger challenges with this option.

@victor, yes this is exactly what I would like to do. So, to do that I need to define three patches, one that just contains the data definition, then two more that each include the data definition patch? So both the patches will refer to the same piece of data?

Thanks,
Ian


#11

I figured timer was just aspecific interrupt. Seeing how it was done should help with general interrupts, but I haven’t used general interrupts yet, so I might be wrong.

I don’t think adding interrupt options to button (digital-read) makes much sense. Button outputs true/false, but interrupt should probably output pulse. A separate interrupt node to send pulse would probably make sense.


#12

Exactly. Three patches: channel (the constructor), write-rates, define-switches (the methods), or something like this.