Topic: tech dcc pc src prev next

tech dcc pc src > manual.c

#include "manual.h"

#include <stdio.h>
#include <util/delay.h>

#include "cfg.h"
#include "controller.h"
#include "dcc.h"
#include "lcd.h"
#include "led.h"
#include "menu.h"
#include "millis.h"
#include "packet.h"
#include "pins.h"

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

    settings_state context = {
        .index = 0,
        .selected = CFG_NULL_ADDR
    };

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

    load_addresses(&context);

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

    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)
        {
            context_->selected = context_->addresses[context_->index];
            Menu_Finish();
        }
    }

    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);
        LCD_PutI((int)context_->addresses[context_->index]);
    }

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

    return context.selected;
}

void manual_mode(bool show_current)
{
    enum manual_mode
    {
        FUNCTION,
        ACCESSORY,
        END_MANUAL_MODE
    };

    typedef enum
    {
        SEQ_POM,
        SEQ_SPEED1,
        SEQ_F1,
        SEQ_F2,
        SEQ_LP1,
        SEQ_SPEED2,
        SEQ_F3,
        SEQ_F4,
        SEQ_LP2,
        SEQ_F5,
        END_PACKET_SEQUENCE_TYPE
    } packet_sequence_type;

    typedef enum
    {
        LP_SEQ_SPEED,
        LP_SEQ_F1,
        LP_SEQ_F2,
        LP_SEQ_F3,
        LP_SEQ_F4,
        LP_SEQ_F5,
        END_LP_SEQUENCE_TYPE
    } lp_sequence_type;

    typedef struct
    {
        // State of each of the locomotive's functions.
        functions_type functions;
        // Current speed.
        unsigned char speed;
        // Locomotive speed steps (for transmitting speed and direction packets).
        unsigned char speed_steps;
        // Current direction.
        bool direction;
        // Train is stopped.  Setting this overrides the speed temporarily.
        bool stopped;
        // Locomotive address.  Set to CFG_NULL_ADDR if this block is not in
        // use.
        cfg_address_type address;
    } loco_control_block;

    typedef struct
    {
        // Locomotives in memory.
        cfg_address_type addresses[CFG_LOCO_LIST_LENGTH + 1];
        // State of each locomotive's functions.  The index is the locomotive's
        // index in 'addresses'.
        loco_control_block loco_controls[CFG_LOCO_LIST_LENGTH];
        // The current index in the locomotive control blocks.
        cfg_loco_index_type loco_control_index;
        // The current locomotive in the low priority queue (index into
        // loco_controls).
        cfg_loco_index_type low_priority_index;
        // The position in the low priority queue sequence.
        lp_sequence_type lp_seq;
        // Current locomotive.
        cfg_loco loco;
        // Number of times the update function has been called.
        unsigned long int update_count;
        // The next packet type to transmit in sequence.
        packet_sequence_type packet_sequence;
        // A POM packet to transmit as a high priority.
        packet pom_packet;
        // Number of times to transmit the POM packet.
        unsigned int pom_retransmit;
        // Current current.
        adc_type current;
        // Display current on the LCD.
        bool show_current;
        // Current display mode (changed by pressing buttons 10-12).
        int mode;
        // Should the accessory list option be shown?
        bool show_accessory_list;
        // Accessories assigned to function keys.
        cfg_address_type accessories[8];
        // Accessories in the 'on' state.
        cfg_address_type on_accessories[CFG_ACCESSORY_LIST_LENGTH + 1];
    } manual_state;

    manual_state context = {
        .loco_control_index = 0,
        .low_priority_index = 0,
        .lp_seq = LP_SEQ_SPEED,
        .update_count = 0,
        .packet_sequence = (END_PACKET_SEQUENCE_TYPE + 1) % END_PACKET_SEQUENCE_TYPE,
        .pom_retransmit = 0,
        .current = 0,
        .show_current = show_current,
        .mode = FUNCTION,
        .show_accessory_list = false
    };

    for(unsigned char i = 0; i < 8; ++i)
        context.accessories[i] = CFG_NULL_ADDR;

    // Indicate forward direction.
    Led_Sel2();

    Cfg_Addresses((cfg_address_type*)&(context.addresses));

    for(int i = 0; i < CFG_LOCO_LIST_LENGTH; ++i)
    {
        if(context.addresses[i] == CFG_NULL_ADDR)
        {
            context.loco_controls[i].address = CFG_NULL_ADDR;
            break;
        }

        context.loco_controls[i].address = context.addresses[i];

        // Copy the locomotive's default function states to the control block's
        // functions.
        cfg_loco loco = Cfg_ReadLoco(context.addresses[i]);
        context.loco_controls[i].functions = loco.f_default;

        context.loco_controls[i].speed_steps = loco.speed_steps;

        // Set the default direction (forward).
        context.loco_controls[i].direction = true;
        // And the speed (stopped).
        context.loco_controls[i].speed = 0;
        context.loco_controls[i].stopped = false;
    }

    context.loco = Cfg_ReadLoco(context.addresses[0]);

    // TODO: is show_accessory_list used anywhere?  If not, remove this.
    {
        cfg_address_type accessories[CFG_ACCESSORY_LIST_LENGTH + 1];
        Cfg_Accessories(accessories);
        if(Cfg_AddressCount(accessories) > 0)
            context.show_accessory_list = true;
    }

    Cfg_ReadAccessoryOn(context.on_accessories);
    Cfg_ReadAccessoryHotlist(context.accessories);

    if(Cfg_AddressCount((cfg_address_type*)&(context.addresses)) == 0)
    {
        Menu_Notice("No locomotives");
        return;
    }

    void set_direction_led(manual_state *context_)
    {
        if(context_->loco_controls[context_->loco_control_index].direction)
            Led_Sel2();
        else
            Led_Sel1();
    }

    void load_loco(manual_state *context_, cfg_address_type addr)
    {
        if(context_->loco.address == addr)
            return;

        // Locomotive configuration can be changed in manual mode.
        Cfg_WriteLoco(context_->loco);

        context_->loco_control_index = Cfg_IndexInList(
                addr,
                context_->addresses
                );

        context_->loco = Cfg_ReadLoco(addr);

        set_direction_led(context_);
    }

    unsigned char max_speed(manual_state *context_)
    {
        return (
            (context_->loco.speed_steps == SPEED_STEPS_128) ? 126 : 26
            );
    }

    void reverse(manual_state *context_)
    {
        context_->loco_controls[context_->loco_control_index].direction =
            !(context_->loco_controls[context_->loco_control_index].direction);
        set_direction_led(context_);
    }

    void accessory_message(cfg_address_type addr, bool state)
    {
        char buf[17];
        snprintf(buf, sizeof(buf), "Acc. %d (%s)", addr, state ? "ON" : "OFF");
        Menu_FakeButton(
                Menu_TimedNotice(buf, HERMES_ACTIVATION_DISPLAY_MS)
                );
    }

    void momentary_function_message(unsigned char fn_no)
    {
        char buf[17];
        snprintf(buf, sizeof(buf), "Fn. %d (once)", fn_no);
        Menu_FakeButton(
                Menu_TimedNotice(buf, HERMES_ACTIVATION_DISPLAY_MS)
                );
    }

    void function_message(unsigned char fn_no, bool state)
    {
        char buf[17];
        snprintf(buf, sizeof(buf), "Fn. %d (%s)", fn_no, state ? "ON" : "OFF");
        Menu_TimedNotice(buf, HERMES_ACTIVATION_DISPLAY_MS);
    }

    void activate_accessory(
            manual_state *context_,
            cfg_address_type acc_no
            )
    {
        if(acc_no == CFG_NULL_ADDR)
        {
            Menu_Notice("No Accessory");
        }
        else
        {
            // Current accessory state.
            const bool accessory_state = (
                Cfg_IndexInList(acc_no, context_->on_accessories) !=
                    CFG_NULL_INDEX
                );

            // Change the accessory state.
            if(accessory_state)
                Cfg_RemoveFromList(acc_no, context_->on_accessories);
            else
                Cfg_AddToList(acc_no, context_->on_accessories);

            const bool new_accessory_state = !accessory_state;

            // Get the correct physical accessory address and output index.
            const cfg_address_type accessory_addr =
                (cfg_address_type)( ((acc_no - 1) / 4) + 1);
            const unsigned char output_index = (unsigned char)(
                    ( ((acc_no - 1) % 4) * 2) +
                    (new_accessory_state ? 1 : 0)
                    );
            DCC_TransmitRepeat(
                Packet_CreateAccessoryBasic(accessory_addr, output_index, true),
                HERMES_ACCESSORY_TRANSMISSION_COUNT
                );
            accessory_message(acc_no, new_accessory_state);
        }
    }

    void activate_function(
            manual_state *context_,
            unsigned char f_no
            )
    {
        if(f_no == CFG_NULL_FN)
        {
            /*Menu_Notice("No Function");*/
        }
        else
        {
            // Activate this function.
            if(context_->loco.f_momentary & (functions_type)(1UL << f_no))
            {
                // Transmit the 'on' part.
                context_->loco_controls[context_->loco_control_index].functions |=
                    (functions_type)(1UL << f_no);
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        Controller_FunctionGroup(f_no),
                        HERMES_MOMENTARY_FUNCTION_ACTIVATION_COUNT
                        );
                // Display a message which may be cancelled early by the user.
                momentary_function_message(f_no);
                // Transmit the 'off' part.
                context_->loco_controls[context_->loco_control_index].functions &= (functions_type)~(1UL << f_no);
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        Controller_FunctionGroup(f_no),
                        HERMES_MOMENTARY_FUNCTION_DEACTIVATION_COUNT
                        );
            }
            else
            {
                context_->loco_controls[context_->loco_control_index].functions ^=
                    (functions_type)(1UL << f_no);
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        Controller_FunctionGroup(f_no),
                        HERMES_MOMENTARY_FUNCTION_ACTIVATION_COUNT
                        );
                function_message(
                        f_no,
                        context_->loco_controls[context_->loco_control_index].functions &
                            (functions_type)(1UL << f_no)
                        );
            }
        }
    }

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

        // Stop control and exit.
        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_BACK)
            Menu_Finish();

        // Choose a locomotive.
        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_LOCO && HERMES_SWITCH_IS_SHORT(sw))
        {
            cfg_address_type next_loco = Cfg_NextInList(
                    context_->addresses,
                    context_->loco.address
                    );
            load_loco(context_, next_loco);
            Menu_Redraw();
        }
        if(HERMES_SWITCH_NO(sw) == HERMES_SWITCH_LOCO && HERMES_SWITCH_IS_LONG(sw))
        {
            cfg_address_type prev_loco = Cfg_PrevInList(
                    context_->addresses,
                    context_->loco.address
                    );
            load_loco(context_, prev_loco);
            Menu_Redraw();
        }

        // The rotary encoder always controls the speed.

        // Actually adjust the speed.
        if(HERMES_SWITCH_NO(sw) == HERMES_RE_ACW)
        {
            if(context_->loco_controls[context_->loco_control_index].direction)
            {
                if(context_->loco_controls[context_->loco_control_index].speed > 0)
                    --context_->loco_controls[context_->loco_control_index].speed;
                else
                    reverse(context_);
            }
            else
            {
                if(context_->loco_controls[context_->loco_control_index].speed < max_speed(context_))
                    ++context_->loco_controls[context_->loco_control_index].speed;
            }
            Menu_Redraw();
        }

        if(HERMES_SWITCH_NO(sw) == HERMES_RE_CW)
        {
            if(context_->loco_controls[context_->loco_control_index].direction)
            {
                if(context_->loco_controls[context_->loco_control_index].speed < max_speed(context_))
                    ++context_->loco_controls[context_->loco_control_index].speed;
            }
            else
            {
                if(context_->loco_controls[context_->loco_control_index].speed > 0)
                    --context_->loco_controls[context_->loco_control_index].speed;
                else
                    reverse(context_);
            }
            Menu_Redraw();
        }

        // Pressing the rotary encoder stops the locomotive if it is moving or
        // reverses the locomotive if it is stationary.
        if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL && HERMES_SWITCH_IS_SHORT(sw))
        {
            if(context_->loco_controls[context_->loco_control_index].speed == 0)
                reverse(context_);
            else
                context_->loco_controls[context_->loco_control_index].speed = 0;
            Menu_Redraw();
        }

        // Holding the rotary encoder stops all locomotives.
        if(HERMES_SWITCH_NO(sw) == HERMES_RE_SEL && HERMES_SWITCH_IS_LONG(sw))
        {
            for(int i = 0; i < CFG_LOCO_LIST_LENGTH; ++i)
                context_->loco_controls[i].speed = 0;
            Menu_Redraw();
        }

        // Long press function button - locomotive POM.
        if(
                HERMES_SWITCH_NO(sw) == HERMES_SWITCH_FUNCTION &&
                HERMES_SWITCH_IS_LONG(sw)
                )
        {
            const cfg_address_type loco_no = choose_locomotive();
            if(loco_no == CFG_NULL_ADDR)
                return;
            const int cv_no = Menu_GetInt("CV No.", -1, HERMES_MAX_CV_NO);
            if(cv_no == -1)
                return;
            const int value = Menu_GetInt("Value", -1, HERMES_MAX_CV_VALUE);
            if(value == -1)
                return;
            context_->pom_packet = Packet_CreatePomLocoWrite(
                    loco_no,
                    (cfg_address_type)cv_no,
                    (unsigned char)value
                    );
            context_->pom_retransmit = 5;
        }

        // Short press function button - go to function mode, or choose a
        // function if already in function mode.
        if(
                HERMES_SWITCH_NO(sw) == HERMES_SWITCH_FUNCTION &&
                HERMES_SWITCH_IS_SHORT(sw)
                )
        {
            if(context_->mode == FUNCTION)
            {
                // Choose any function to activate now.
                const unsigned char fn_no = Controller_FunctionMenu(
                        &(context_->loco),
                        &(context_->loco_controls[context_->loco_control_index].functions)
                        );
                activate_function(context_, fn_no);
            }
            else
            {
                context_->mode = FUNCTION;
                Menu_Redraw();
            }
        }

        // Long press accessory button - offer to program an accessory decoder
        // CV on main.
        if(
                HERMES_SWITCH_NO(sw) == HERMES_SWITCH_ACCESSORY &&
                HERMES_SWITCH_IS_LONG(sw)
          )
        {
            const cfg_address_type acc_no =
                Controller_AccessoryMenu(context_->on_accessories);
            if(acc_no == CFG_NULL_ADDR)
                return;
            const int cv_no = Menu_GetInt("CV No.", -1, HERMES_MAX_CV_NO);
            if(cv_no == -1)
                return;
            const int value = Menu_GetInt("Value", -1, HERMES_MAX_CV_VALUE);
            if(value == -1)
                return;
            context_->pom_packet = Packet_CreatePomAccWrite(
                    (cfg_address_type)(((acc_no - 1) / 4) + 1),
                    (cfg_address_type)cv_no,
                    (unsigned char)value
                    );
            context_->pom_retransmit = 5;
        }

        // Short press accessory button - switch to accessory mode or choose an
        // accessory to activate if already in accessory mode.
        if(
                HERMES_SWITCH_NO(sw) == HERMES_SWITCH_ACCESSORY &&
                HERMES_SWITCH_IS_SHORT(sw)
                )
        {
            if(context_->mode == ACCESSORY)
            {
                // Choose any accessory to activate now.
                const cfg_address_type acc_no =
                    Controller_AccessoryMenu(context_->on_accessories);
                activate_accessory(context_, acc_no);
            }
            else
            {
                // Switch to accessory hotkey mode.
                context_->mode = ACCESSORY;
                Menu_Redraw();
            }
        }

        // Reprogram one of the buttons 1-8 in function or accessory mode.
        if(
                HERMES_SWITCH_IS_LONG(sw) &&
                HERMES_SWITCH_NO(sw) >= 1 &&
                HERMES_SWITCH_NO(sw) <= 8
                )
        {
            if(context_->mode == FUNCTION)
            {
                if(context_->loco.f_list[HERMES_SWITCH_NO(sw) - 1] == CFG_NULL_FN)
                {
                    const unsigned char fn_no = Controller_FunctionMenu(
                            &(context_->loco),
                            &(context_->loco_controls[context_->loco_control_index].functions)
                            );
                    if(fn_no != CFG_NULL_INDEX)
                        // Assign this function to the switch.
                        context_->loco.f_list[HERMES_SWITCH_NO(sw) - 1] = fn_no;
                }
                else
                {
                    context_->loco.f_list[HERMES_SWITCH_NO(sw) - 1] = CFG_NULL_FN;
                }
            }

            if(context_->mode == ACCESSORY)
            {
                if(context_->accessories[HERMES_SWITCH_NO(sw) - 1] == CFG_NULL_ADDR)
                {
                    const cfg_address_type acc_no =
                        Controller_AccessoryMenu(context_->on_accessories);
                    context_->accessories[HERMES_SWITCH_NO(sw) - 1] = acc_no;
                }
                else
                {
                    context_->accessories[HERMES_SWITCH_NO(sw) - 1] = CFG_NULL_ADDR;
                }
            }

            Menu_Redraw();
        }

        // Activate one of the functions or accessories on buttons 1-8.
        if(
                HERMES_SWITCH_IS_SHORT(sw) &&
                HERMES_SWITCH_NO(sw) >= 1 &&
                HERMES_SWITCH_NO(sw) <= 8
                )
        {
            if(context_->mode == FUNCTION)
                activate_function(
                        context_,
                        context_->loco.f_list[HERMES_SWITCH_NO(sw) - 1]
                        );

            if(context_->mode == ACCESSORY)
                activate_accessory(
                        context_,
                        context_->accessories[HERMES_SWITCH_NO(sw) - 1]
                        );
        }
    }

    unsigned char function_button_assigned(manual_state *context_)
    {
        unsigned char out = 0;
        switch(context_->mode)
        {
            case ACCESSORY:
                for(unsigned char i = 0; i < 8; ++i)
                {
                    if(context_->accessories[i] != CFG_NULL_ADDR)
                        out = (unsigned char)(out | (1 << i));
                }
                break;
            case FUNCTION:
                for(unsigned char i = 0; i < 8; ++i)
                {
                    if(context_->loco.f_list[i] != CFG_NULL_FN)
                        out = (unsigned char)(out | (1 << i));
                }
                break;
        }
        return out;
    }

    void draw(void *v)
    {
        manual_state *context_ = (manual_state*)v;
        LCD_DefineHermesChars(HERMES_LCD_CUSTOM_MODE_OPS);
        LCD_Clear();

        char function_button_char(unsigned char assigned)
        {
            switch(assigned)
            {
                case 0b01:
                    return HERMES_LCD_CHAR_BUTTON_TOP;
                case 0b10:
                    return HERMES_LCD_CHAR_BUTTON_BOTTOM;
                case 0b11:
                    return HERMES_LCD_CHAR_BUTTON_BOTH;
            }
            return ' ';
        }

        const unsigned char button_assigned = function_button_assigned(context_);

        const char buttons_tl = function_button_char(button_assigned & 0b11);
        const char buttons_bl = function_button_char((button_assigned >> 2) & 0b11);
        const char buttons_tr = function_button_char((button_assigned >> 4) & 0b11);
        const char buttons_br = function_button_char((button_assigned >> 6) & 0b11);

        LCD_PutC(buttons_tl);
        LCD_PutS("Loco ");
        LCD_PutI((int)context_->loco.address);
        switch(context_->mode)
        {
            case ACCESSORY:
                LCD_Move(11, 0);
                LCD_PutS("Ac");
                break;
            case FUNCTION:
                {
                    const int f_count =
                        Cfg_LocoFunctions(&context_->loco);
                    if(f_count > 0)
                    {
                        LCD_Move((unsigned char)(12 - LCD_DigitCount(f_count)), 0);
                        LCD_PutS("Fn ");
                        LCD_PutI(f_count);
                    }
                }
                break;
        };

        LCD_Move(15, 0);
        LCD_PutC(buttons_tr);

        if(context_->show_current)
        {
            LCD_Move(9, 0);
            LCD_PutI((int)(context_->current));
        }

        LCD_Move(0, 1);
        LCD_PutC(buttons_bl);

        // Print the direction and speed in 'large' format (16 characters).
        LCD_PutC((context_->loco_controls[context_->loco_control_index].direction) ? 'F' : 'R');
        LCD_PutC(' ');

        unsigned char hb = 0;
        switch(context_->loco_controls[context_->loco_control_index].speed_steps)
        {
            case SPEED_STEPS_128:
                hb = (unsigned char)(context_->loco_controls[context_->loco_control_index].speed / 6);
                break;
            case SPEED_STEPS_28:
                hb = (unsigned char)(context_->loco_controls[context_->loco_control_index].speed);
                break;
        }

        for(unsigned char i = 0; i < 8; ++i)
        {
            if(hb / 2 > i)
                LCD_PutC((char)255);
            else if(hb / 2 == i && hb % 2)
                LCD_PutC(HERMES_LCD_CHAR_HB);
            else
                LCD_PutC(' ');
        }

        LCD_PutC(' ');

        LCD_PutI((int)context_->loco_controls[context_->loco_control_index].speed);

        LCD_Move(15, 1);
        LCD_PutC(buttons_br);
    }

    // Transmit a single packet from the low priority queue.
    // Low priority queue packets are on a per locomotive basis.  Once the
    // sequence has finished, the next locomotive in the locomotive list will
    // be chosen as the low priority queue target before the sequence restarts.
    void transmit_lp(manual_state *context_)
    {
        if(
            context_->addresses[context_->low_priority_index] == context_->loco.address ||
            context_->lp_seq == LP_SEQ_SPEED
            )
            // Advance to the next locomotive in the low priority sequence.
            context_->low_priority_index = (cfg_loco_index_type)(
                    (context_->low_priority_index + 1) % Cfg_AddressCount(context_->addresses)
                    );

        // Skip past the current locomotive if it was selected.
        if(context_->addresses[context_->low_priority_index] == context_->loco.address)
            context_->low_priority_index = (cfg_loco_index_type)(
                    (context_->low_priority_index + 1) % Cfg_AddressCount(context_->addresses)
                    );

        switch(context_->lp_seq)
        {
            case LP_SEQ_SPEED:
                // All locomotives in the low priority queue should be stopped.
                DCC_Transmit(
                        (context_->loco_controls[context_->low_priority_index].speed_steps == SPEED_STEPS_128) ?
                            Packet_CreateSpeed128(
                                context_->addresses[context_->low_priority_index],
                                context_->loco_controls[context_->low_priority_index].direction,
                                context_->loco_controls[context_->low_priority_index].speed
                                ) :
                            Packet_CreateSpeed28(
                                context_->addresses[context_->low_priority_index],
                                context_->loco_controls[context_->low_priority_index].direction,
                                context_->loco_controls[context_->low_priority_index].speed
                                )
                        );
                break;
            case LP_SEQ_F1:
                Controller_TransmitFunctionGroup(
                        context_->addresses[context_->low_priority_index],
                        context_->loco_controls[context_->low_priority_index].functions,
                        1,
                        1
                        );
                break;
            case LP_SEQ_F2:
                Controller_TransmitFunctionGroup(
                        context_->addresses[context_->low_priority_index],
                        context_->loco_controls[context_->low_priority_index].functions,
                        2,
                        1
                        );
                break;
            case LP_SEQ_F3:
                Controller_TransmitFunctionGroup(
                        context_->addresses[context_->low_priority_index],
                        context_->loco_controls[context_->low_priority_index].functions,
                        3,
                        1
                        );
                break;
            case LP_SEQ_F4:
                Controller_TransmitFunctionGroup(
                        context_->addresses[context_->low_priority_index],
                        context_->loco_controls[context_->low_priority_index].functions,
                        4,
                        1
                        );
                break;
            case LP_SEQ_F5:
                Controller_TransmitFunctionGroup(
                        context_->addresses[context_->low_priority_index],
                        context_->loco_controls[context_->low_priority_index].functions,
                        5,
                        1
                        );
                break;
            case END_LP_SEQUENCE_TYPE:
                break;
        }

        context_->lp_seq = (context_->lp_seq + 1) % END_LP_SEQUENCE_TYPE;
    }

    void update(void *v)
    {
        manual_state *context_ = (manual_state*)v;

        if(Menu_Current(&(context_->current)) && context_->show_current)
            Menu_Redraw();

        Controller_CheckOverCurrent(context_->current);

        // Transmit the next packet in the queue.
        switch(context_->packet_sequence)
        {
            case SEQ_POM:
                if(context_->pom_retransmit > 0)
                {
                    DCC_Transmit(context_->pom_packet);
                    --context_->pom_retransmit;
                }
                break;
            case SEQ_SPEED1:
            case SEQ_SPEED2:
                transmit_speed(
                        &(context_->loco),
                        (context_->loco_controls[context_->loco_control_index].stopped) ? 0 : (unsigned char)(context_->loco_controls[context_->loco_control_index].speed),
                        context_->loco_controls[context_->loco_control_index].direction
                        );
                break;
            case SEQ_F1:
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        1,
                        1
                        );
                break;
            case SEQ_F2:
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        2,
                        1
                        );
                break;
            case SEQ_F3:
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        3,
                        1
                        );
                break;
            case SEQ_F4:
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        4,
                        1
                        );
                break;
            case SEQ_F5:
                Controller_TransmitFunctionGroup(
                        context_->loco.address,
                        context_->loco_controls[context_->loco_control_index].functions,
                        5,
                        1
                        );
                break;
            case SEQ_LP1:
            case SEQ_LP2:
                if(Cfg_AddressCount(context_->addresses) > 1)
                    transmit_lp(context_);
                break;
            case END_PACKET_SEQUENCE_TYPE:
                break;
        }

        context_->packet_sequence = (context_->packet_sequence + 1) % END_PACKET_SEQUENCE_TYPE;
    }

    DCC_Start();
    Menu_EventLoop(button, draw, update, &context);
    DCC_Stop();

    // Save the ordered list of accessories that are 'on'.
    Cfg_SaveAccessoryOn(context.on_accessories);
    Cfg_SaveAccessoryHotlist(context.accessories);
    // Locomotive configuration can be changed in manual mode.
    Cfg_WriteLoco(context.loco);

    Led_Off();
}