Writing modules tutorial #1: Hello, World!

Started by a1ex, March 16, 2017, 10:24:14 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

a1ex

So far, if you wanted to write your own module, the best sources of documentation were (and probably still are) reading the source code, the forum, the old wiki, and experimenting. As a template for new modules, you probably took one of the existing modules and removed the extra code.

This is one tiny step to improve upon that: I'd like to write a series of guides on how to write your own modules and how to use various APIs provided by Magic Lantern (some of them tightly related to APIs reverse engineered from Canon firmware, such as properties or file I/O, others less so, such as ML menu).

Will start with the simplest possible module:

Hello, World!





Let's start from scratch:

hg clone -u unified https://foss.heptapod.net/magic-lantern/magic-lantern
cd magic-lantern/modules/
mkdir hello
cd hello
touch hello.c


Now edit hello.c in your favorite text editor:

/* A very simple module
* (example for module authors)
*/
#include <dryos.h>
#include <module.h>
#include <menu.h>
#include <config.h>
#include <console.h>

/* Config variables. They are used for persistent variables (usually settings).
*
* In modules, these variables also have to be declared as MODULE_CONFIG.
*/
static CONFIG_INT("hello.counter", hello_counter, 0);


/* This function runs as a new DryOS task, in parallel with everything else.
*
* Tasks started in this way have priority 0x1A (see run_in_separate_task in menu.c).
* They can be interrupted by other tasks with higher priorities (lower values)
* at any time, or by tasks with equal or lower priorities while this task is waiting
* (msleep, take_semaphore, msg_queue_receive etc).
*
* Tasks with equal priorities will never interrupt each other outside the
* "waiting" calls (cooperative multitasking).
*
* Additionally, for tasks started in this way, ML menu will be closed
* and Canon's powersave will be disabled while this task is running.
* Both are done for convenience.
*/
static void hello_task()
{
    /* Open the console. */
    /* Also wait for background tasks to settle after closing ML menu */
    msleep(2000);
    console_clear();
    console_show();

    /* Plain printf goes to console. */
    /* There's very limited stdio support available. */
    printf("Hello, World!\n");
    printf("You have run this demo %d times.\n", ++hello_counter);
    printf("Press the shutter halfway to exit.\n");

    /* note: half-shutter is one of the few keys that can be checked from a regular task */
    /* to hook other keys, you need to use a keypress hook - TBD in hello2 */
    while (!get_halfshutter_pressed())
    {
        /* while waiting for something, we must be nice to other tasks as well and allow them to run */
        /* (this type of waiting is not very power-efficient nor time-accurate, but is simple and works well enough in many cases */
        msleep(100);
    }

    /* Finished. */
    console_hide();
}

static struct menu_entry hello_menu[] =
{
    {
        .name       = "Hello, World!",
        .select     = run_in_separate_task,
        .priv       = hello_task,
        .help       = "Prints 'Hello, World!' on the console.",
    },
};

/* This function is called when the module loads. */
/* All the module init functions are called sequentially,
* in alphabetical order. */
static unsigned int hello_init()
{
    menu_add("Debug", hello_menu, COUNT(hello_menu));
    return 0;
}

/* Note: module unloading is not yet supported;
* this function is provided for future use.
*/
static unsigned int hello_deinit()
{
    return 0;
}

/* All modules have some metadata, specifying init/deinit functions,
* config variables, event hooks, property handlers etc.
*/
MODULE_INFO_START()
    MODULE_INIT(hello_init)
    MODULE_DEINIT(hello_deinit)
MODULE_INFO_END()

MODULE_CONFIGS_START()
    MODULE_CONFIG(hello_counter)
MODULE_CONFIGS_END()


We still need a Makefile; let's copy it from another module:

cp ../ettr/Makefile .
sed -i "s/ettr/hello/" Makefile


Let's compile it:

make


The build process created a file named README.rst. Update it and recompile.


make clean; make


Now you are ready to try your module in your camera. Just copy the .mo file to ML/MODULES on your card.

If your card is already configured for the build system, all you have to do is:

make install


Otherwise, try:

make install CF_CARD=/path/to/your/card


or, if you have such device:

make install WIFI_SD=y


That's it for today.




To decide what to cover in future episodes, I'm looking for feedback from anyone who tried (or wanted to) write a ML module, even if you were successful or not.

Some ideas:
- printing on the screen (bmp_printf, NotifyBox)
- keypress handlers
- more complex menus
- properties (Canon settings)
- file I/O
- status indicators (lvinfo)
- animations (e.g. games)
- capturing images
- GUI modes (menu, play, LiveView, various dialogs)
- semaphores, message queues
- DryOS internals (memory allocation, task creation etc)
- custom hooks in Canon code
- your ideas?

Of course, the advanced topics are not for second or third tutorial.

Meloware

This is great! Thank you for starting this. It is really the level of introduction I need.

Don't local variables normally only need to be defined within the function which uses them? In hello.c you seem to be defining a local(?) variable after the #include statements.

/* Config variables. They are used for persistent variables (usually settings).
*
* In modules, these variables also have to be declared as MODULE_CONFIG.
*/
static CONFIG_INT("hello.count", hello_count, 0);


I guess the first thing the module does when loaded is to execute the commands listed at the end. These are the MODULE_INFO and MODULE_CONFIGS commands. The MODULE_CONFIGS seems to be registering the hello_count declared at the beginning of hello.c .
MODULE_CONFIGS_START()
    MODULE_CONFIG(hello_count)
MODULE_CONFIGS_END()


Could you please explain this some more? Do all variables need to be defined in this way?

static CONFIG_INT("hello.count", hello_count, 0);
has 3 elements within the parenthesis and MODULE_CONFIG has only a reference to hello_count. What are these three elements?
Let's say that I needed an integer variable, named "temp" in this module. How would this new variable be setup in the same module?

***
As far as your feedback questions, my answer is "yes, I want all of that". Once I learn more, I may become more specific. For my movie transfer, I would be very interested in how an image is saved or a frame is added to an MLV video. How might the code know when the camera is ready for another exposure? What is known about the DMA channel? Of course, one must first learn to crawl, before they may be expected to walk.

a1ex

The above config declaration expands to (see config.h)


static int hello_count = 0;
[...]


That means, a variable local to the module (not exported to other modules), but usable in all functions from your module. It's not possible to declare config variables local to functions.

The parts are: name (as it appears in the config file), C variable name, default value.

Only config variables have to be registered with MODULE_CONFIG. Other special things also have to be registered with MODULE macros; will cover them in another tutorial.

As for why the were declared in this way - historical reasons. Pretty sure one can find a way to simplify the definitions (e.g. figure out the entry name from variable name, or register them automatically without requiring a MODULE_CONFIG), but so far there wasn't a pressing need to do so.

The "static" keyword from all other functions has the same meaning. If you don't write it, functions or top-level variables are exported to other modules (or to ML core, in some cases). That's nothing specific to ML; it's how the C language behaves when your program has more than one source file.

More about the static keyword in C here and here.

Note: ML build system shows the exported symbols, as (per C standard) you have to tag those functions/variables that you don't have to export (and do nothing for those that you do).

reddeercity

Thanks a1ex , this is great ! it filling gapes in my knowledge . I generally kind of understood how modules work , now this give me some ideas that now I can
develop , (Mjpeg 4.2.2 , it's a dream :D)  , this will certainty help me in debugging .
Can't wait for more .

okodoko

Here's an idea.  I would find this very useful. I do multi cam and I'd like to arrange the clips on the timeline easily.  If I could have the system time in the file name, it would be easy (assuming the cameras' system times are in sync).  In addition to system time [hour/min/sec] it would be nice to have a field in the file name as [Acam, Bcam, Ccam, ...etc] and a date field [year, month, day]. I would still have to sync them by either audio or slate, but that would be simple enough.  If you surround the date field such as [ML20170527V], then searching for all the media associated with one project would be easy using that string.

scrax

I'm tring to use this code in a module to override button press but don't know how to make it work, it was working when int he old 2.3 unified in shoot.c I'm tring to make a module out of it. so far I can save value and restore from menu and show the GUI.
This part is to change setting and load or save values from the canon screens instead of the menu to have it easy to use.

int cmodes_control(struct event * event)
{
    if(cmodes_mode_check())
    {
        //When override picstyle dialog, SET closes it
        if(CURRENT_GUI_MODE == dialog_override )
        {
            if(!IS_FAKE(event))
            {
                // SET force closing Q menu
                if (event->param == BGMT_PRESS_SET)
                {
                    if (picstyle_override_enabled)
                    {
                        SetGUIRequestMode(0);
                        return 0;
                    }
                }
// Custom modes button controls
                if(cmodes_selected)
                {
                    printf("%s\n", "cmodes control started");
                    if (event->param == BGMT_PLAY)
                    {
                        cmodes_load_shoot_settings();
                        SetGUIRequestMode(0);
                        return 0;
                    }
                }
            }
        return 1;
        }
    }
    return 1;
}



I can post the full module if needed.

BTW i've posted here because I really like to see #2 of those tutorial without it i'll never managed to start this port, now I'm stuck because of more advanced thing.

Can I have some info about what are CBR used for? maybe what I need too?
I'm using ML2.3 for photography with:
EOS 600DML | EOS 400Dplus | EOS 5D MLbeta5- EF 100mm f/2.8 USM Macro  - EF-S 17-85mm f4-5.6 IS USM - EF 70-200mm f/4 L USM - 580EXII - OsX, PS, LR, RawTherapee, LightZone -no video experience-

scrax

I've started diggin more into thi cbr thing and seems it's what i need, made this cbr to get button code in modules bu still not working:

EDIT: fount the problem!...
was default:  break; instead of return 1;

Fixed code:

static unsigned int cmodes_keypress_cbr(unsigned int key)
{
    if( (CURRENT_GUI_MODE == dialog_override) && (cmodes_selected >= 1) )
    {
        switch(key)
        {
            // Load selected C mode
            case MODULE_KEY_PRESS_UP:
                if(cmodes_selected <= 4 )
                {
                    cmodes_selected++;           
                }
                else
                {
                    cmodes_selected = 5;
                }
                break;
            // decrease mode selected   
            case MODULE_KEY_PRESS_DOWN:
                if(cmodes_selected <= 5 && cmodes_selected >= 2 )
                {
                    cmodes_selected--;           
                }
                else
                {
                    cmodes_selected = 5;
                }
                break;
            default: return 1;
        }
        return 1;
    }
}


it starts because can return some notifybox, but then buttons became unusable...
I'm using ML2.3 for photography with:
EOS 600DML | EOS 400Dplus | EOS 5D MLbeta5- EF 100mm f/2.8 USM Macro  - EF-S 17-85mm f4-5.6 IS USM - EF 70-200mm f/4 L USM - 580EXII - OsX, PS, LR, RawTherapee, LightZone -no video experience-

Damiano

Hello,
where can I find a keypress hook list?? Is it possible to write a module that hooks the moment when a picture is taken?
Thanks for help.
Damiano