Flagging nodes as persistent

Another one of those “Is this even possible?” questions…

While trying to use the LCD for debugging, I modified the code to NOT clear a line if a null string was fed in. Then, I attempted to have one patch write to line1 and another patch write to line2; however, each invocation of a patch created a new instance of LCD which needed initialized, which blanks the screen. If it was possible to tell the compiler to use the same instance of the object each time, it would not need re-initialized each call. Having a way to match instances between patches would also be a bonus (you would need some sort of instance label since there might be 2 LCDs with difference I2C addresses, h-bridge for left/right motors, etc.). Another place this would be useful is the servo node that would now be able to save the last position it was moved to and latches & counters could maintain state. It might also help resolve the issue of needing gate-number to feed node inputs (depending on how it is implemented).

An immediate problem I see (especially if nodes on different patches are tied to the same instance) is that you basically end up with multiple wires to each input of the node. I don’t know if adding an enable or pulse pin to the object could help resolve the conflict by indicating which set of wires should be active.

BTW: Now that I understand XOD well enough, I love how it all works together. The ability to look at all available nodes on the left, the quick search option for when I know what I want (or want to see what is available for manipulating strings), the way nodes & pins can be documented and have the descriptions on the right, the simplicity of tying a pot to an LED, etc. About the only thing I’ve REALLY missed in an undo option…especially when I have a group of nodes selected, click on one of the nodes thinking I’m selecting just it, and then hit delete, but sometimes I just want to undo a bunch of moves.

Does anything stop you from IoC (inversion of control)? I mean, could these two patches instead of putting their data to text-lcd nodes output it via terminals? If so, there could be a single “hardware” patch which contains these two patches and the LCD as nodes. The output of the first node is fed to L1 and the second is to L2

Well, it is here, but it has a bug of “dummy” actions. It could happen (oftenly) that pressing Ctrl+Z undoes something that has no effect. Ctrl+Z again and… you’ll undo another no-op :slight_smile: Keep trying. Sometimes you have to hit Z ten times to make it work, but eventually it always work.

Sorry for that, we’ll fix it soon.

I can take a look, but in this case, it was speed for motor and angle/range for the range-finder…the two actions are separated by logic and the logic between changes for different parts of the program, so it would basically require pulling most of the code into main and create a mess.

It would still be nice to have a way to remember last position of servo (persistent object or global variable, so we don’t have to waste time waiting for full travel if it is already close), and I haven’t come up with a way to control servo in a state-machine without using detach and letting it free-wheel (not ideal for something like a robotic arm).

I see. I’ve got a simple idea for tweaking the node so that it would process output on different lines fine. A change from:

    printLine(lcd, 0, getValue<input_L1>(ctx));
    printLine(lcd, 1, getValue<input_L2>(ctx));

to:

    if (isInputDirty<input_L1>(ctx))
        printLine(lcd, 0, getValue<input_L1>(ctx));

    if (isInputDirty<input_L2>(ctx))
        printLine(lcd, 1, getValue<input_L2>(ctx));

In the text-lcd-* implementation should leave a particular string alone if it does not linked to the current node instance. Need to check this out.

Am I understand you right? You want a servo to rotate to the desired angle and wait for the exact amount (ideally) of time until its travel complete regardless where it was started. If so, you can try to build the system using the buffer node.

Nice case. Something we’ll consider while trying to unroll the state management mess.

That might work (assuming it doesn’t see change to null string as dirty input). I just changed the printLine to not print spaces if whitespace still = length of line (meaning we had not printed any text; you would need to print “<at_least_one_space>” to explicitly clear the line), but neither solution helps if the LCD is re-initialized and clears the screen. Hmmm…I wonder if I could add an “init” pin to lcd-i2c and skip lcd->begin if it is pulled low…

FYI: loop to print text in printLine should probably add “whitespace>0” to for loop condition so you truncate string and are not printing beyond the end of line. At least on my LCD printing beyond the end just creates a mess (unless I’m just not giving it time to scroll). If other screens handle longer lines gracefully, then code should not be changed to truncate.

I realized last night that putting LCD on main patch does not mean all code using it needs to be on main patch, it just means nodes using it need to return data to put on the screen. The same goes for using buffer to remember last servo position, but the buffer needs to be able to accept input from multiple locations, so it will need to implement a select to know which line value to store on pulse. I’ll have to see if there is enough variable space left to implement the buffer in my project, but I’m worried all the wire connections are going to create a mess…

If you ever add right-click menu to nodes, maybe one of the options could be to hide its wire connections and grey-out the node (“I’m here & connected, but hiding those connections”; connection end-points would still highlight to show connections, but wires would not be displayed). Then somebody is going to want a “hide all other wires” to see just this nodes connections…

Init pin on LCD I2C object to call lcd->begin does work. You just need to be sure there is an LCD object that still does the full initialization. I put an LCD with Init True (which should be the default for the pin) on main patch with no inputs & debug LCDs on sub-nodes write to L1 or L2 with Init changed to False. You could just as easily have LCD on main patch write to one line & another instance write to the other line. I used the “don’t blank line if nothing printed” rather than checking if input for each Line was dirty…

The 2 new if statements:

    // Clear the rest of the line
    if (whitespace < 16) {
        while (whitespace--)
            lcd->write(' ');
    }

and

        if (getValue<input_Init>(ctx)) lcd->begin();

This is useful for my debugging…I don’t know if it would be appropriate to make these changes to the standard library…

The other update I thought of for the library was truncating strings to fit on LCD:

    for (auto it = str.iterate(); it; ++it, --whitespace)

becomes

    for (auto it = str.iterate(); it && whitespace; ++it, --whitespace)

If there is a reason NOT to truncate for some displays (auto-scroll?), then this would be a bad idea… (If you do write more than 16 chars & it auto-scrolls, what happens when you only blank 16 chars later?)

Well, I was able to configure a buffer to remember previous servo position. Since it is used in 4 places, I needed a 4-input buffer:

Here is the mess it creates when used:

will-crash & escape pass the values onto range-check, which is able to use servo-pulse as a delay timer so servo is done moving before trying to ping:
image

Code for servo-pulse:

{{#global}}
#include <Servo.h>
{{/global}}

struct State {
    Servo servo;
};

{{ GENERATED_CODE }}

void evaluate(Context ctx) {
    State* state = getState(ctx);
    auto port = (int)getValue<input_PORT>(ctx);

    if (isInputDirty<input_Start>(ctx)) {

        auto port = (int)getValue<input_PORT>(ctx);
        auto mint = getValue<input_MinT>(ctx);
        auto maxt = getValue<input_MaxT>(ctx);
        auto pval = getValue<input_pVal>(ctx);
        auto nval = getValue<input_nVal>(ctx);
        emitValue<output_Val>(ctx, nval);
        
        // If we don't know previous position, assume the worst
        TimeMs dt = maxt * 1000;
        if (pval >= 0) {
            // servo position must be between 0 & 1
            pval = pval > 1 ? 1 : pval;
            nval = nval > 1 ? 1 : (nval < 0 ? 0 : nval);
            dt = ((maxt - mint) * abs(pval-nval) + mint) * 1000;
        }
 
        state->servo.attach(port);
        state->servo.write(nval * 180);
        setTimeout(ctx, dt);
        emitValue<output_ACT>(ctx, true);
    } else if (isTimedOut(ctx)) {
        state->servo.detach();
        emitValue<output_ACT>(ctx, false);
        emitValue<output_Done>(ctx, true);
    }

}

Not sure all this is worth it just to be able to ping 0.4 seconds quicker while moving forward, but it was an interesting challenge. Being able to create some sort of persistent storage would certainly reduce the wiring complexity and needing the multi-select buffer.

1 Like

Yeah, I’m thinking about something similar. A feature to organize many outgoing links would be nice. But currently no solution appeared that would be a :100: benefit. Everything considered at the moment solve one UX problem, creating two or three other.

I think xoders who come to a similar problem would say thank you if you’ll publish the tweaked library.

Seems like a bug. We should have the check in the code. Thank you for reporting!

Cool :+1: A good addition to the collection of non-abstract cases which we should consider when trying to make improvements.

Ugh…I finally find a hobby to occupy my spare time, and it becomes an obsession that keeps me awake at night. Why can’t I just enjoy something in moderation???

After getting the 4-input buffer working (which would probably be needed for a more complex state-machine), I realized that this one is VERY simplistic. The 1st will-crash we either don’t know where the servo is on boot, or we know it is at it’s extreme 180 degrees. Either way, we are moving to 90 degrees, so we never need to move more than 1/2 way, so we can assume 0 or 180 degrees for previous value. For the will-crash in the forward loop, the servo will always already be pointing forward; the same for the escape routine when we exit the forward loop.

Pretty sure I saw information somewhere about contributing back to XOD. I’ll take a look and officially submit changes for select (pulse out), ultrasonic-time (return max time when no ping received), and lcd-i2c (init pin, string truncate, and don’t clear line on null string; probably all should be made to the other lcd library also). servo-enable & servo-pulse might be good additions to the standard library.

1 Like