Topic: tech dcc pc src prev next

tech dcc pc src > settings.c

#include "settings.h"

#include <string.h>

#include "cfg.h"
#include "dcc.h"
#include "lcd.h"
#include "menu.h"
#include "packet.h"
#include "pins.h"

#define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(a[0]))

typedef unsigned char index_type;

void Settings_Menu()
{
    static const char *items[] = {
        "Locomotives",
        "Accessories",
        0
    };

    int option = 0;
    while(
        (option = Menu_GetOption("Settings", items, (unsigned char)option)) > -1
        )
    {
        switch(option)
        {
            case 0:
                Settings_Locomotives();
                break;
            case 1:
                Settings_Accessories();
                break;
        }
    }
}

void Settings_Accessories()
{
    typedef struct
    {
        unsigned char index;
        cfg_address_type addresses[CFG_ACCESSORY_LIST_LENGTH+1];
    } accessories_state;

    accessories_state context = {
        .index = 0
    };

    void load_addresses(accessories_state *context_)
    {
        Cfg_Accessories(context_->addresses);
    }

    load_addresses(&context);

    int count(const accessories_state *context_)
    {
        return (Cfg_AddressCount(context_->addresses) + 1);
    }

    void button(void *v, const switch_type sw)
    {
        accessories_state *context_ = (accessories_state*)v;

        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
            Menu_Finish();

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
        {
            context_->index =
                (unsigned char)((count(context_) + context_->index - 1) % count(context_));
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
        {
            context_->index =
                (unsigned char)((context_->index + 1) % count(context_));
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
        {
            if(context_->index == 0)
            {
                // Add a new accessory.
                if(Cfg_AddressCount(context_->addresses) == CFG_ACCESSORY_LIST_LENGTH)
                {
                    Menu_Notice("List full");
                }
                else
                {
                    int address =
                        Menu_GetInt("Number", -1, HERMES_MAX_ACC_ADDRESS);
                    if(address == -1)
                    {
                        // The user cancelled the address input.
                    }
                    else if(Cfg_AccessoryAddressValid((cfg_address_type)address))
                    {
                        Cfg_SaveAccessory((cfg_address_type)address);
                        load_addresses(context_);
                        Menu_Redraw();
                    }
                    else
                        Menu_Notice("Invalid address");
                }
            }
            else
            {
                // Confirm, then delete the accessory.
                if(Menu_Question("Delete?"))
                {
                    Cfg_DeleteAccessory(context_->addresses[context_->index - 1]);
                    load_addresses(context_);
                    --context_->index;
                    Menu_Redraw();
                }
            }
        }
    }

    void draw(void *v)
    {
        accessories_state *context_ = (accessories_state*)v;
        LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
        LCD_Clear();
        LCD_PutS("Acc.");
        LCD_PrintListIndex(context_->index + 1, count(context_));
        LCD_Move(0, 1);
        if(context_->index == 0)
            LCD_PutS("New");
        else
            LCD_PutI((int)context_->addresses[context_->index - 1]);
    }

    Menu_EventLoop(button, draw, 0, &context);
}


void Settings_Locomotives()
{
    typedef struct
    {
        unsigned char index;
        cfg_address_type addresses[CFG_LOCO_LIST_LENGTH+1];
    } settings_state;

    settings_state context = {
        .index = 0
    };

    void load_addresses(settings_state *context_)
    {
        Cfg_Addresses(context_->addresses);
    }

    load_addresses(&context);

    int count(const settings_state *context_)
    {
        return (Cfg_AddressCount(context_->addresses) + 1);
    }

    void button(void *v, const switch_type sw)
    {
        settings_state *context_ = (settings_state*)v;

        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
            Menu_Finish();

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
        {
            context_->index =
                (unsigned char)((count(context_) + context_->index - 1) % count(context_));
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
        {
            context_->index =
                (unsigned char)((context_->index + 1) % count(context_));
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
        {
            if(context_->index == 0)
            {
                if(Cfg_NextFreeLoco() == CFG_NULL_INDEX)
                {
                    // Creating a new locomotive: all slots are taken.
                    Menu_Notice("List full");
                }
                else
                {
                    // Create a new locomotive.
                    int address =
                        Menu_GetInt("Address", -1, HERMES_MAX_LOCO_ADDRESS);
                    if(!Cfg_LocoAddressValid((cfg_address_type)address))
                        Menu_Notice("Invalid address");
                    else if(
                            Cfg_AddrToIndex((cfg_address_type)address) != CFG_NULL_INDEX
                            )
                        // New address is taken.
                        Menu_Notice("Address taken");
                    else
                        Settings_LocomotiveList((cfg_address_type)address);
                }
            }
            else
            {
                // Display settings for a locomotive in EEPROM.
                Settings_LocomotiveList(context_->addresses[context_->index - 1]);
            }
            unsigned char old_address_count = Cfg_AddressCount(context_->addresses);
            load_addresses(context_);
            // In case this locomotive was deleted.
            if(
                    context_->index > 0 &&
                    old_address_count > Cfg_AddressCount(context_->addresses)
                    )
                context_->index = (unsigned char)(context_->index - 1);
        }
    }

    void draw(void *v)
    {
        settings_state *context_ = (settings_state*)v;
        LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
        LCD_Clear();
        LCD_PutS("Locos");
        LCD_PrintListIndex(context_->index + 1, count(context_));
        LCD_Move(0, 1);
        if(context_->index == 0)
            LCD_PutS("New");
        else
            LCD_PutI((int)context_->addresses[context_->index - 1]);
    }

    Menu_EventLoop(button, draw, 0, &context);
}

void Settings_LocomotiveList(cfg_address_type address)
{
    typedef struct
    {
        cfg_loco loco;
        index_type index;
        cfg_loco_index_type loco_index;
    } state;
    state context;

    cfg_loco_index_type loco_index = Cfg_AddrToIndex(address);

    if(loco_index == CFG_NULL_INDEX)
    {
        // New locomotive.
        loco_index = Cfg_NextFreeLoco();
        context.loco = Cfg_DefaultLoco();
        context.loco.address = address;
        context.index = 0;
        context.loco_index = loco_index;
    }
    else
    {
        // Existing locomotive.
        context.loco = Cfg_ReadLocoFromIndex(loco_index);
        context.index = 0;
        context.loco_index = loco_index;
    }

    enum items {
        ADDRESS,
        FUNCTIONS,
        SPEED_STEPS,
        DELETE,
        END_ITEMS
    };

    void write_loco(state *context_)
    {
        Cfg_WriteLocoToIndex(context_->loco_index, context_->loco);
    }

    write_loco(&context);

    void button(void *v, const switch_type sw)
    {
        state *context_ = (state*)v;

        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
            Menu_Finish();

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
        {
            context_->index = (unsigned char)((END_ITEMS + context_->index - 1) % END_ITEMS);
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
        {
            context_->index = (unsigned char)((context_->index + 1) % END_ITEMS);
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
        {
            switch(context_->index)
            {
                /*case ADDRESS:*/
                    /*{*/
                    /*int addr_in =*/
                        /*Menu_GetInt("Address", -1, HERMES_MAX_LOCO_ADDRESS);*/
                    /*if(addr_in == -1)*/
                        /*// Cancelled.*/
                        /*break;*/
                    /*cfg_address_type addr = (cfg_address_type)addr_in;*/
                    /*if(!Cfg_LocoAddressValid(addr))*/
                        /*Menu_Notice("Invalid address");*/
                    /*else if(*/
                        /*Cfg_AddrToIndex(addr) !=*/
                            /*context_->loco_index &&*/
                        /*Cfg_AddrToIndex(addr) != CFG_NULL_INDEX*/
                        /*)*/
                    /*{*/
                        /*Menu_Notice("Address taken");*/
                    /*}*/
                    /*else*/
                    /*{*/
                        /*context_->loco.address = addr;*/
                        /*write_loco(context_);*/
                    /*}*/
                    /*}*/
                    /*break;*/
                case SPEED_STEPS:
                    {
                        static const char *s[] =
                            { "128 (default)", "28 (older locos)", 0 };
                        int n = Menu_GetOption("Speed Steps", s, 0);
                        if(n > -1)
                        {
                            static const unsigned char modes[] = {
                                SPEED_STEPS_128,
                                SPEED_STEPS_28
                            };
                            context_->loco.speed_steps = modes[n];
                            write_loco(context_);
                        }
                    }
                    break;
                case DELETE:
                    if(Menu_Question("Delete loco?"))
                    {
                        context_->loco = Cfg_DefaultLoco();
                        write_loco(context_);
                        Menu_Finish();
                    }
                    break;
                case FUNCTIONS:
                    Settings_FunctionList(&(context_->loco));
                    write_loco(context_);
                    break;
            }
        }
    }

    const char *speed_step_str(unsigned char steps)
    {
        switch(steps)
        {
            case SPEED_STEPS_128:
                return "128 (default)";
            case SPEED_STEPS_28:
                return "28 (older locos)";
            default:
                return "Unknown";
        }
    }

    void draw(void *v)
    {
        state *context_ = (state*)v;
        LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
        LCD_Clear();
        LCD_PutS("Loco ");
        LCD_PutI((int)context_->loco.address);
        LCD_PrintListIndex(context_->index + 1, END_ITEMS);
        LCD_Move(0, 1);

        switch(context_->index)
        {
            case ADDRESS:
                LCD_PutS("Address (");
                LCD_PutI((int)context_->loco.address);
                LCD_PutC(')');
                break;
            case SPEED_STEPS:
                LCD_PutS(speed_step_str(context_->loco.speed_steps));
                break;
            case FUNCTIONS:
                LCD_PutS("Functions");
                break;
            case DELETE:
                LCD_PutS("Delete");
                break;
        }
    }

    Menu_EventLoop(button, draw, 0, &context);
}

void Settings_FunctionList(cfg_loco *loco)
{
    typedef struct
    {
        unsigned char index;
        cfg_loco *loco;
    } state;

    state context = {
        .index = 0,
        .loco = loco
    };

    int count(const state *context_)
    {
        // + 1 for the 'New' entry.
        return (Cfg_LocoFunctions(context_->loco) + 1);
    }

    // Get the currently selected function number.
    unsigned char selected_function(const state *context_)
    {
        unsigned char index = context_->index;
        functions_type f = context_->loco->f_enable;
        for(unsigned char i = 0; i < 32; ++i)
        {
            if(f & 1)
                --index;

            if(index == 0)
                return (unsigned char)i;

            f >>= 1;
        }
        return 0;
    }

    void button(void *v, const switch_type sw)
    {
        state *context_ = (state*)v;

        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
            Menu_Finish();

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
        {
            context_->index =
                (unsigned char)((count(context_) + context_->index - 1) % count(context_));
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
        {
            context_->index =
                (unsigned char)((context_->index + 1) % count(context_));
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL)
        {
            if(context_->index == 0)
            {
                // Enable a function.
                int function =
                    Menu_GetInt("Function No.", -1, HERMES_MAX_FUNCTION);
                if(function == -1)
                {
                    // The user cancelled the function number input.
                }
                else if(function > 28)
                {
                    Menu_Notice("Invalid function");
                }
                else
                {
                    bool momentary = Menu_Question("Momentary?");

                    context_->loco->f_enable = (context_->loco->f_enable | (1UL << function));

                    if(momentary)
                    {
                        // Momentary functions cannot be on by default.
                        context_->loco->f_default = (context_->loco->f_default & ~(1UL << function));
                        context_->loco->f_momentary = (context_->loco->f_momentary | (1UL << function));
                    }
                    else
                    {
                        // For toggle functions, ask whether the function is on by
                        // default.
                        bool enable_default = Menu_Question("Default on?");
                        if(enable_default)
                            context_->loco->f_default = (context_->loco->f_default | (1UL << function));
                        else
                            context_->loco->f_default = (context_->loco->f_default & ~(1UL << function));
                        context_->loco->f_momentary = (context_->loco->f_momentary & ~(1UL << function));
                    }

                    Menu_Redraw();
                }
            }
            else
            {
                // Confirm, then delete the function.
                if(Menu_Question("Delete?"))
                {
                    unsigned char f_no = selected_function(context_);
                    context_->loco->f_default = (context_->loco->f_default & ~(1UL << f_no));
                    context_->loco->f_enable = (context_->loco->f_enable & ~(1UL << f_no));
                    context_->loco->f_momentary = (context_->loco->f_momentary & ~(1UL << f_no));

                    --context_->index;
                    Menu_Redraw();
                }
            }
        }
    }

    void draw(void *v)
    {
        state *context_ = (state*)v;
        LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_MENU);
        LCD_Clear();
        LCD_PutS("Function");
        LCD_PrintListIndex(context_->index + 1, count(context_));
        LCD_Move(0, 1);
        if(context_->index == 0)
            LCD_PutS("New");
        else
        {
            unsigned char f_no = selected_function(context_);
            bool momentary = context_->loco->f_momentary & (functions_type)(1 << f_no);
            bool enable_default = context_->loco->f_default & (functions_type)(1 << f_no);
            LCD_PutI(selected_function(context_));
            LCD_PutS(
                    momentary ?
                        " (momentary)" :
                        (enable_default ? " (toggle, on)" : " (toggle, off)")
                    );
        }
    }

    Menu_EventLoop(button, draw, 0, &context);
}