Façon correcte d'utiliser l'architecture de subsomption avec Robot C

11

J'ai lu beaucoup de choses récemment sur l' architecture de subsomption et il y a plusieurs façons dont les gens semblent préconiser.

Par exemple, certaines personnes utilisent une variable globale "flag" pour qu'une tâche prenne le contrôle. D'autres utilisent le endTimeSlice()et permettent à l'arbitre de vraiment choisir. Et je pense que c'est correct.

J'ai cette petite section de code RobotC sur laquelle je travaille pour un robot de suivi de ligne, mais je ne suis pas sûr de le faire correctement car actuellement, la méthode de suivi prendra toujours le pas sur la méthode de recherche. Le flux correct doit être que la recherche guide le robot vers la ligne en utilisant un chemin en spirale pour trouver la ligne. Une fois la ligne trouvée, la piste devrait prendre le relais.

task evade(){
    if(SensorValue(forwardSonarSensor) > threshold){
            //box the obstruction
    }
}

task find(){
    if(SensorValue(lightSensor) > threshold){
            //spiral the robot
    }
}

task track(){

    if(SensorValue(lightSensor) < threshold){
            //go straight
    }else{
                //execute turns to follow the line
    }
}

task main(){
    while(true){
        StartTask(evade,9);
        StartTask(track,8);
        StartTask(find,7);
        wait1Msec(250);
    }
}

Je viens d'utiliser quelques commentaires ici plutôt que le code réel pour être bref. Mes déclarations if ne sont-elles pas assez bonnes comme conditions car lorsque le robot est hors ligne, il track()prend le relais. Est-ce dû à la déclaration else dans la piste? Si oui, comment avoir track()effectué des virages quand il perd la ligne sans prendre le relais du fourrage au début du programme?

James MV
la source
1
Les chiffres que vous donnez StartTasksont-ils la priorité de la tâche? Est-ce que 9 va être la plus haute priorité? Dans ce cas, ne devrait pas findavoir plus de priorité que track? En fait, la condition de findet la elsecondition de tracksont les mêmes. Donc, en tant qu'être humain, si la valeur du capteur est supérieure au seuil, que feriez-vous? Aller en spirale ou tourner pour ajuster la ligne?
Shahbaz
Je sais ce qu'est l'architecture de subsomption et je suis sûr que vos robots devraient avoir différents modules matériels prenant en charge des tâches de calcul de bas niveau. Et que le code devrait en fait absorber le signal plutôt que de simplement écouter un état conditionnel. Ne devriez-vous donc pas transmettre le signal à un autre matériel? Maintenant, mon problème est que j'ai deux Arduinos et je veux que l'Arduino A subsume ou inhibe un signal de l'Arduino B. Quelqu'un a-t-il une idée de comment cela se fait? Comme je peux câbler l'Arduino A pour envoyer un signal à l'Arduino B mais je ne sais pas comment subsumer ou inhiber la signature
Ambient Soda

Réponses:

7

Avec l'architecture de subsomption, vous devez soigneusement concevoir vos comportements de telle sorte que si vous attribuez une Tpriorité de tâche n, ce Tdevrait être ce que le robot devrait faire si toutes les tâches de priorité supérieure à celles qui nsont ignorées.

Ordonnons vos exemples de tâches, puis trouvons un moyen de les implémenter. Vos tâches sont evade, findet track.

En général, vous voudriez que le robot suive une ligne. Cependant, s'il ne pouvait pas détecter la ligne, il devrait essayer de la trouver. Surtout, elle doit échapper aux obstacles. Cela nous donne l'ordre suivant:

  • Priorité la plus élevée: evade
  • Puis: find
  • Puis: track

La raison finda une priorité plus élevée que trackcelle que, comme je l'ai mentionné ci-dessus, vous trackne le feriez que si evadeet ne findsont pas nécessaires. Si vous mettez findci track- dessous , cela signifie que vous commencez à suivre s'il n'y a pas d'obstacle, même si vous n'êtes pas sur la ligne.

Voyons maintenant votre implémentation:

task find(){
    if(SensorValue(lightSensor) > threshold){
            //spiral the robot
    }
}

task track(){

    if(SensorValue(lightSensor) < threshold){
            //go straight
    }else{
                //execute turns to follow the line
    }
}

N'oubliez pas que nous avons accordé findune priorité plus élevée. Par conséquent, si le robot ne peut pas détecter le lightSensor, il ira en spirale en essayant de trouver la ligne. Une fois qu'il le fait, trackintervient. Comme vous pouvez le voir, la elsecondition de trackne se produit jamais.

Pendant que cela fonctionne, le robot se déplacerait très maladroitement. En fait, vous ne pouvez pas y faire grand-chose, compte tenu de la construction actuelle de votre robot.


Bien que j'aie déjà répondu à votre question, voici une simple amélioration de votre suivi de ligne:

Au lieu d'un capteur de lumière, utilisez-en deux; ls_leftet ls_right. En utilisant (au moins) deux capteurs, vous pouvez comprendre si vous êtes totalement hors de la piste ou sur le point de sortir de la piste. Dans le deuxième cas, vous pouvez facilement vous tourner dans la bonne direction et vous remettre sur la bonne voie.

Votre findtâche est similaire:

task find(){
    if (SensorValue(ls_left) > threshold
        && Sensorvalue(ls_right) > threshold){
            //spiral the robot
    }
}

Autrement dit, vous allez en spirale uniquement si vous ne ressentez rien du tout

Votre tracktâche devient maintenant plus efficace:

task track(){

    if (SensorValue(ls_left) < threshold
        && SensorValue(ls_right) < threshold){
            //go straight
    } else if (SensorValue(ls_left) < threshold
        && SensorValue(ls_right) > threshold){
            //turn left
    } else if (SensorValue(ls_left) > threshold
        && SensorValue(ls_right) < threshold){
            //turn right
    } else {
            // shouldn't happen, but go on spiral anyway
    }
}

Évidemment, avec une matrice de capteurs de lumière, vous pouvez mieux juger à quel point vous sortez de la piste (c'est-à-dire avec quel angle) et mieux décider comment vous remettre sur la bonne voie (c'est-à-dire avec quelle vitesse angulaire).

Shahbaz
la source
4

réponse courte; non, vous devez vraiment faire les choses un peu différemment.

réponse longue et incomplète; Permettez-moi de vous donner un code pseudo approprié pour robotC, qui vous met sur une meilleure voie. Tout d'abord, n'utilisez pas de tâches - ce n'est PAS à cela que servent les tâches robotC. Ils pourraient être faits pour fonctionner, peut-être, peut-être pas (et vous avez besoin de quelques changements pour même essayer).

// global variables
int distance;
int light;

main() {
   while (true) {
   distance = read_distance;
   light = read_light;
   if (task1_wantsToRun())
     task1_run();
   if (task2_wantsToRun())
     task2_run();   
   }
}

il y a deux ou trois choses ici; la priorité devient inutile. Aussi agréable qu'il semble avoir des tâches dans robotC avec des priorités, elles ne sont pas un bon choix pour la mise en œuvre de subsomption selon mon expérience. Pour des raisons comme, les priorités ne sont pas toujours respectées, les tâches ne peuvent pas être interrompues (parfois), donc lorsqu'un événement de priorité plus élevée se produit, il ne réagira pas comme vous vous y attendez, robotC n'est que récemment rentré, donc des choses comme l'accès à un capteur de plus d'une tâche peut être risqué (problèmes de synchronisation I2C), et dans certains cas, ce n'est pas le cas (capteurs interrogés automatiquement).

Vous pouvez ajouter votre propre implémentation prioritaire à la boucle ci-dessus au fur et à mesure que les choses fonctionnent, mais elle n'est vraiment pas nécessaire pour les démarrages.

Votre commentaire "// box the obstruction" décrit un comportement balistique. Celles-ci sont un peu délicates à mettre en œuvre en utilisant le multitâche. La boucle simple que j'ai utilisée le rend beaucoup plus facile et meilleur pour les débutants / l'apprentissage.

L'autre chose que je vais vous laisser, c'est que la subsomption tout en étant soignée et appropriée pour beaucoup de choses, n'est pas un bon moyen de mettre en œuvre ce qui est mieux fait traditionnellement. En effet, la partie «éluder» peut être un bon candidat pour la subsomption, mais honnêtement, votre autre tâche devrait être appelée «GoOnAboutYourBusiness». Je dis cela parce que vous ne voulez probablement pas passer de la recherche au suivi avec subsomption. Gérez ceux avec des boucles de programmation traditionnelles. Avec un seul capteur, - la lumière est-elle plus sombre ou plus claire que la dernière boucle? s'il est devenu plus sombre (en supposant une ligne noire) continuez à tourner dans la même direction, s'il est devenu plus clair, tournez dans le sens inverse, s'il est resté le même, continuez tout droit. Vous devrez probablement ajouter un PID et utiliser une courbe de direction au lieu de simplement tourner à gauche et à droite pour être plus fluide.

Et oui, plusieurs capteurs aident. http://www.mindsensors.com/ - ouais, c'est moi dans le film actuellement (11/10/2012)

Mise à jour: code réel

Je vais essayer cela dans un petit moment, mais il compile et illustre ce que j'ai écrit ci-dessus:

#pragma config(Sensor, S1,     S_LIGHT,        sensorLightActive)
#pragma config(Sensor, S2,     S_DISTANCE,     sensorSONAR)
#pragma config(Motor,  motorB,          LEFT,          tmotorNXT, PIDControl, encoder)
#pragma config(Motor,  motorC,          RIGHT,         tmotorNXT, PIDControl, encoder)
//*!!Code automatically generated by 'ROBOTC' configuration wizard               !!*//

int distance_value, light_value;

bool evade_wantsToRun()
{
    return distance_value < 30;
}

void evade_task()
{
    // full stop
    motor[LEFT] = 0;        
    // evade the object ballistically (ie in full control)  
    // turn left, drive
    nSyncedTurnRatio = 0;
    motor[LEFT] = -20;
    Sleep(500);
    nSyncedTurnRatio = 100;
    Sleep(1000);
    // turn right, drive
    nSyncedTurnRatio = 0;
    motor[LEFT] = 20;
    Sleep(500);
    nSyncedTurnRatio = 100;
    Sleep(1000);
    // turn right, drive
    nSyncedTurnRatio = 0;
    motor[LEFT] = 20;
    Sleep(500);
    nSyncedTurnRatio = 100;
    Sleep(1000);
    // turn left, resume
    nSyncedTurnRatio = 0;
    motor[LEFT] = 20;
    Sleep(500);
    motor[LEFT] = 0;
}

///////////////////////////////

void TurnBySteer(int d)
{
    // normalize -100 100 to 0 200
    nSyncedTurnRatio = d + 100; 
}
///////////////////////////////

typedef enum programPhase { starting, searching, following, finished };
programPhase phase = starting;

// these 'tasks' are called from a loop, thus do not need to loop themselves

void initialize()
{
    nSyncedTurnRatio = 50;
    nSyncedMotors = synchBC;
    motor[LEFT] = 30;       // start a spiral drive
    phase = searching;
}

void search()
{
    if (light_value < 24)
    {
        nSyncedTurnRatio = 100;
        phase = following;
    }
}

int lastLight = -1;
int currentSteer = 0;
void follow()
{
    // if it is solid white we have lost the line and must stop
    // if lightSensors detects dark, we are on line
    // if it got lighter, we are going more off line
    // if it got darker we are headed in a good direction, slow down turn in anticipation
    // +++PID will be even smoother
    if (light_value > 64)
    {
        motor[LEFT] = 0;
        phase = finished;
        return;
    }
    if (light_value < 24)
        currentSteer = 0;
    else if (light_value > lastLight)
        currentSteer += sgn(currentSteer) * 1;
    else    // implied (light_value < lastLight)
        currentSteer -= sgn(currentSteer) * 1;      

    TurnBySteer(currentSteer);
}

bool regularProcessing_wantsToRun()
{
    return phase != finished;
}

void regularProcessing_task()
{
    switch (phase)
    {
    case starting:
        initialize();
        break;
    case searching:
        search();
        break;
    case following:
        follow();
    }
}

task main()
{
    // subsumption tasks in priority oder
    while (true)
    {
        // read sensors once per loop
        distance_value = SensorValue[S_DISTANCE];
        light_value = SensorValue[S_LIGHT];
        if (evade_wantsToRun())
            evade_task();
        if (regularProcessing_wantsToRun())
            regularProcessing_task();
        else
            StopAllTasks();
        EndTimeSlice();     // give others a chance, but make it as short as possible
    }
}
Spiked3
la source
Je suis d'accord que ce problème est plus facilement résolu avec une simple boucle. Je ne comprends pas pourquoi quelqu'un voterait contre cela.
Shahbaz
Je ne veux pas laisser l'impression qu'il est plus facile de résoudre avec une boucle simple, mais plutôt l'impression que c'est l'utilisation correcte de la subsomption pour utiliser une boucle simple comme l'une des tâches. Celui qui a déclassé a des points de mod et aucune compréhension de la subsomption. Vous ne constaterez pas qu'il n'y a pas beaucoup de gens qui font de la subsomption sur un LEGO NXT (implicite en utilisant robotC), alors ne vous attendez pas à ce que le code soit facilement disponible pour être collé.
Spiked3
Oui, je me demande aussi pourquoi le PO utilisait des tâches pour quelque chose d'aussi simple que la subsomption.
Rocketmagnet
Parce que c'est une erreur très très très très très courante pour les débutants avec robotC - essayer d'utiliser des tâches pour tout. Je souhaite qu'ils le déplacent vers une zone avancée uniquement.
Spiked3