Classe+ActionPlan

Home > Robótica > Bibliotecas **Recursos** > RunTimer > Timer2 > CircularBuffer > SerialLog > **ActionPlan** **Dispositivos** > Ultrassom > Array de sensores > NRF24L01 > Wireless log > RF24Control **Movimento** > StepperMotor > StepperMotorTm > DoubleStepper > DoubleStepperTm

Esta é a classe de mais alto nível entre as bibliotecas de uso geral. Foi desenvolvida pra ser a estrutura de decisão de um robô, mas pode ser aproveitada para qualquer aplicação que use microcontroladores. É uma classe bem enxuta que ocupa pouca memória e não exige muito poder de processamento. É especialmente indicada para ambientes como o do arduino.

Assim como qualquer microcontrolador, um programa no arduino funciona em //loop// infinito. As instruções dentro da função //loop// são executadas até o final da função e depois a execução volta ao início e executa tudo novamente. Isso continua indefinidamente até que o microcontrolador seja desligado. Esse esquema de funcionamento obriga a um tipo de programação que não é familiar para quem está acostumado a programar computadores, mas que é bastante comum em programação de jogos. É algo como programação orientada a eventos, mas vai além disso, porque inclui também o monitoramento. O programa precisa ficar o tempo todo monitorando dispositivos variados, reagir a determinadas condições ou leituras e também responder a eventos internos ou externos que ocorrem frequentemente. Por isso é extremamente importante que as funções implementadas no programa não sejam bloqueantes, ou seja, não obriguem que o processamento fique parado esperando que a tarefa seja completada. É claro que tecnicamente todas as funções são bloqueantes já que é o mesmo processador que irá executar todas elas. Não existe processamento paralelo no arduino ou na maioria dos microcontroladores, mas uma coisa é esperar alguns microssegundos pela leitura de um sensor de distância e outra muito diferente é esperar vários segundos até que o robô se mova de um ponto a outro. É por essa razão que a função //delay// deve ser evitada a todo custo. Enquanto o processador está esperando o tempo passar, não está fazendo mais nada e o //loop// fica bloqueado até a função retornar.

A classe //ActionPlan// cria um mecanismo de execução de ações em sequencia que roda com um mínimo de interferência com o //loop// principal. As ações ficam armazenadas em um buffer circular e vão sendo executadas uma a uma e permitindo a reavaliação da sequencia a cada ação executada. Mas como a classe sabe executar essas ações? Ela tem conhecimento de todos os dispositivos incluídos no robô? A resposta é não, ela não sabe executar as ações, e é por isso que ela deve ser derivada em uma classe descendente que saiba não só executar as ações, mas também identificar quando uma ação foi completada para que a próxima seja iniciada. Vamos supor um algoritmo bem simples em um robô também simples. Este robô tem apenas os motores de tração e um sensor de distancia apontado diretamente para a frente. O algoritmo que o robô irá executar será bem simples:
 * 1) Leia o sensor de distância.
 * 2) Se a distância for maior ou igual a 50cm, ande 40 cm e volte para o passo 1, senão vá para o passo 3.
 * 3) Gire 25 graus para a direita e volte para o passo 1.

Em resumo o que temos que fazer é ler o sensor e decidir se vamos para a frente ou se giramos para a direita. Para identificar estas ações no programa criaremos //#define's// para as ações:

code format="java5"
 * 1) define act_readAndGo  1
 * 2) define act_moveAndBack 2
 * 3) define act_move       3

code

Agora derivamos a classe //ActionPlan// e implementamos o método virtual //executeAction//:

code format="java5" class MyRobot: public ActionPlan { public: MyRobot: ActionPlan{ }

TExecResult executeAction(TAction *action) {     switch (action->actionID) {       case act_readAndGo: {            int dist= sensor.read; if( dist >= 50 ) addActionI( act_moveAndBack, mvAhead, 40 ); else addActionI( act_moveAndBack, mvSpinR, 25 ); }         return ear_actionDone;

case act_moveAndBack: addActionI( act_move, action->par.iparam1, action->par.iparam2); addActionI( act_readAndGo, 0 ); return ear_actionDone;

case act_move: move(action->par.iparam1, action->par.iparam2); return ear_actionStarted;

default: return ear_nothingDone; }   }

code

O método //executeAction// recebe como parâmetro a estrutura //TAction// que contém o ID da ação, que corresponde aos defines criados e os parâmetros para a ação. Cada ação pode ter até 8 bytes de parâmetros, que podem ser formatados em 4 inteiros, ou 2 longos ou 2 //floats//. Para incluir ações na lista de ações use //addActionI// para fornecer até 4 inteiros como parâmetros, //addActionL// para até dois longos, ou //addActionF// para até dois parâmetros //float//.

O método deve retornar um dos seguintes valores //enum//:
 * ear_nothingDone: ** Significa que nada foi feito, será tentado novamente no próximo //loop//.
 * ear_actionDone: ** A ação foi completada, a classe pode executar a próxima ação da lista. Isso será feito no próximo loop.
 * ear_actionStarted: ** A ação foi iniciada, mas ainda não completada. A classe irá esperar uma notificação de que a ação foi concluída para executar a próxima ação da lista. Em geral são as ações de movimento do robô. Essas ações demoram um bom tempo para serem executadas.

A notificação de conclusão de uma ação deve ser feita chamando o método //actionDoneReport//. Esta chamada informa à classe que a ação corrente foi concluída. No caso de ações de movimento, a notificação poderia ser incluída no evento //onAfterMove// que é disparado sempre que um movimento é completado, como no exemplo abaixo:

code void MyRobot::onAfterMove(char movType, float param) { if (planRunning) {   TAction* cr = currentAction; if ((cr->actionID == act_move) && (cr->par.iparam1 == movType)) actionDoneReport; } }

code Finalmente é preciso citar que o método //run// da classe deve ser chamado frequentemente, pelo menos uma vez a cada //loop//. É nesse método que a classe verifica as ações que precisam ser executadas.

Conclusão
Esse foi um exemplo bem simples, feito para mostrar o funcionamento básico da classe //ActionPlan//. Em geral os algoritmos implementados para o comportamento do robô são muito mais complexos e podem crescer muito rapidamente na fase de testes quando são identificadas situações que não foram previstas inicialmente. Por exemplo, o que acontece se o teu bichano resolver entrar na frente do robô no momento em que ele está se movendo pra frente? O ideal seria que o robô identifique um obstáculo inesperado no caminho e pare antes de atropelar o gatinho, mas esse comportamento não está previsto no algoritmo acima.

Abaixo a declaração pública da classe //TAction//:

code format="java5" // Até o momento nunca usei mais do que 5 ou 6 ações enfileiradas.
 * 1) define PlanBufLen 16 // Tamanho do buffer circular. Pode ser reduzido para economizar memória.

class ActionPlan{ public: ActionPlan; // construtor void run;   // chamar pelo menos uma vez a cada loop

// métodos para incluir ações na lista boolean addAction(int actionID, TParam64 *par, int tag=0); boolean addActionL(int actionID, long param1, long  param2=0, int tag=0); boolean addActionF(int actionID, float param1, float param2=0, int tag=0); boolean addActionI(int actionID, int param1, int param2=0, int param3=0, int param4=0, int tag=0);

// método para facilitar a inclusão de ações de movimento, só funciona se o método setMoveActionID for // chamado antes para informar qual o ID da ação associada ao movimento. boolean addMoveAction(int movType, float param, int tag=0); void setMoveActionID(int moveActionID);

// Deve ser reescrito para implementar a execução das ações. virtual TExecResult executeAction(TAction *action);

// eventos virtual void onActionDone(TAction* action){}; virtual void onAllActionsDone(int tag){};

TAction *currentAction; // Retorna a estrutura completa da ação corrente int currentActionID;   // Retorna o ID da ação corrente int currentActionTag;  // Retorna o tag da ação corrente void startPlan;        // inicia a execução das ações void resetPlan;        // cancela e limpa a lista de ações

void actionDoneReport; // informa que a ação corrente foi concluída

// verificação de status boolean actionWaiting; boolean planRunning; boolean actionRunning; };

code Abaixo um exemplo de como poderiam ser as funções setup e loop:

code format="java5" MyRobot robot;

void setup { ...  robot.init; ... }

void loop { ... robot.run;

if (!robot.isMoving&& !robot.planRunning) {   robot.addActionI(act_readAndGo, 0); robot.startPlan; } ... }

code