Atividade #1223
AbertaMeta #1230: Ter bons filtro de posição e estimatidor de velocidades
Tratamento de recebimento de pacotes no while Read UDP
Descrição
SENDO FEITO NA BRANCH readudp CRIADA A PARTIR DA dev.
Após o final da larc 2019, em conversa com o major Renault, percebemos um problema no jeito que fizemos o while para recebimento de pacotes.
Esse while foi criado pra permitir o recebimento de pacotes independente do resto de processamento da visão (decodificação de pacotes, Kalman, Cluster to Game e escrever na referência) a fim de impedir acúmulo e consequentemente processamento de pacotes defasados.
Da maneira que foi implementado, quando o while da visão pega o vetor de bytes (pacote) da referência, ele pega o último recebido. Mas chega uma câmera em cada pacote, com isso, pode estar acontecendo de o while da visão estar pegando mais vezes umas câmeras que outras já que o while Read UDP tem rodado mais rápido que o while da visão.
Em resumo, na larc de 2019, o while Read UDP rodava com praticamente o dobro de fps que o while da visão, ou seja, metade dos pacotes que chegavam não eram processados (feito kalman e utilizado para o resto do código). Não tem tanto problema se chegar dois pacotes de uma mesma câmera e só processarmos o mais atual deles (isso é o correto); o problema é chegar duas câmeras diferentes e só processarmos uma delas (a última a chegar).
O objetivo dessa tarefa é encontrar uma solução para que consigam ser sempre processados os pacotes mais atuais de cada uma das 4 câmeras.
Arquivos
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Data prevista ajustado para 21/02/2020
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Criei a branch readudp a partir da dev pra fazer essa tarefa.
O que pensei até agora foi passar a parte de decodificação de pacotes do while da visão para o while read udp, pois dessa forma saberemos de qual câmera se trata o pacote. Daí pensei em escrever as câmeras em 4 referências diferentes (a câmera que chegou será escrita na referência reservada pra ela). No cluster da câmera seria adicionado um inteiro sequence_number e todos os pacotes decodificados seriam numerados em ordem crescente de acordo com a ordem de chegada.
Daí, no while da visão, sempre leríamos as 4 referências, ou seja, as informações mais atuais de cada uma das 4 câmeras. Mas seria conferido, individualmente pra cada câmera, se o seu sequence_number já foi processado pelo while da visão. Se não, vamos processar e caso contrário, não.
Lembrando que no while da visão temos o vetor com as 4 câmeras e podemos processar quantas quisermos.
Quando digo processar no while da visão, basicamente é aplicar o Kalman.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
O efeito que isso teria é que no while Read UDP, além de receber o pacote da rede, ele seria decodificado, o que produz maior tempo de execução do while. Mas acredito que esse processamento a mais não deva causar atrasos e nem chegada de pacotes sem que possam ser lidos imediatamente, pois decodificar os pacotes não é uma tarefa custosa. Mas só testando no PIRF pra saber.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Já comecei a implementação!
Percebi que no Cluster Detection Frame já tem um atributo frame number, então não vou precisar criar nada relacionado à identificação dos frames, como tinha dito (sequence number).
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Já terminei a implementação descrita, vou postar o que foi feito e aguardar feedback de possíveis erros, sugestões de melhoria de implementação etc.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Arquivo NewUpdate.png NewUpdate.png adicionado
- Arquivo PreKalman.png PreKalman.png adicionado
- Arquivo SomentoKalman.png SomentoKalman.png adicionado
- Arquivo getCurrentCameras.png getCurrentCameras.png adicionado
- Arquivo ModifyVision.png ModifyVision.png adicionado
- Arquivo NewReadUDP.png NewReadUDP.png adicionado
O while Read UDP ficou assim:
![](NewReadUDP.png)
Criei 4 referências do cluster SSL Detection Frame.ctl, uma para cada câmera, além de uma para o Field Size.
A decodificação do vetor de bytes em cluster Detection Frame (método decodeCameras da Vision) saiu do while da visão e veio para esse while. Da VI de decodificação, agora há a saída "camera_id", que indica qual câmera chegou no pacote de agora. Usamos essa informação para escolher qual referência devemos abrir para escrever a câmera que chegou. Sai também um booleano indicando se chegou algum pacote; se chegou, abrimos a referência. Isso é feito mais para ajudar em testes. Se desligarmos o grsim, por exemplo, o código continua trabalhando com a última informação recebida.
As informações do campo (cluster Field Size) não vêm em todos os pacotes. Da VI de decodificação sai um booleano que indica se há informação de Field Size ou não. Se houver, abrimos a referência para escrever. Se não vier, não fazemos nada (case vazio).
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Mudanças no while da visão:
![](ModifyVision.png)
Agora, antes de entrar no método que contém o Kalman, tem um método "getCurrentCameras". Essa VI lê as 4 referências de câmeras e de field size e analisa quais câmeras já foram pegas pela visão anteriormente, como pode ser visto abaixo:
![](getCurrentCameras.png)
Há um vetor de inteiros que armazena o último frame_number pego pela visão de cada câmera. Verifiquei e, pelo menos no grSim, o primeiro frame_number é 0. Por isso, inicializei o vetor com [-1,-1,-1,-1].
Para cada uma das 4 câmeras pegas da referência verificamos se o frame_number já foi pego pela visão. Ou seja, pode ser que entre a iteração de while de visão passada e essa de agora, alguma referência não tenha dado novo, então não precisamos pegar esse dado novamente pois já está no nosso vetor de câmeras com Kalman já aplicado (esse vetor fica na parte do Kalman).
Caso esse frame_number ainda não tenha sido pego, adicionamos ele ao vetor e o atributo New é setado como True (o default é False). Eu criei esse atributo no cluster para sabermos se aplicamos o Kalman a essa câmera ou não. Pois nosso vetor de câmeras que entra na VI do Kalman tem câmeras repetidas (a menos que a gente pegue da referência 4 câmeras novas na VI getCurrentCameras). Além disso, atualizamos o último frame_number dessa câmera no vetor.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Outra diferença é que no método receiveDecodeAndDoKalman agora tem só o Kalman. A parte de recebimento de pacotes foi tirada daqui quando criamos o while Read UDP e agora, nesta tarefa, também tirei a decodificação. O nome desse método será mudado posteriormente.
![](SomentoKalman.png)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Arquivo NewFalseDpsDoKalman.png NewFalseDpsDoKalman.png adicionado
Outras diferenças:
![](PreKalman.png)
Agora, na VI acima, entra um vetor de câmeras e não só apenas uma. Além disso, só aplicamos o Kalman às câmeras com atributo New True. Pois as que estiverem False, significa que já estavam no vetor de câmeras e não é uma informação nova.
![](NewFalseDpsDoKalman.png)
Como visto na imagem acima, depois do Kalman, setamos o atributo New como False para sabermos que essa câmera já foi analisada. Se, na próxima iteração, tivermos informação nova dessa câmera, seu atributo New virá True como foi mostrado acima na VI getCurrentCameras
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Também foi necessária modificação na VI Update Frame Array (VI branca logo depois do cluster NewCameras das fotos acima):
![](NewUpdate.png)
Antes, essa VI atualizava no vetor de 4 câmeras a câmera que estava chegando. Mas, como agora podem chegar de 0 a 4 câmeras novas, coloquei um loop em volta da VI que atualiza os dados da câmera nova.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Conversando com o Leo, ele mencionou dois detalhes:
Pode ser que não tenha nada escrito na referência de determinada câmera ainda (acontece nas primeiras iterações). Tenho que tratar isso ainda.
Além disso, ele também sugeriu analisar analisar se, na VI getCurretCameras, vale a pena fazer a análise de forma paralela e não iterativa. Isso por causa de complexidade computacional, tempo de execução; esses cluesters de câmera são pesados.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Tudo feito até agora está atualizado na branch readudp.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
Pensei em duas coisas para os casos de referência sem nada escrito.
Uma delas foi escrever vetor de uma câmera na referência porque dessa forma, quando formos ler da referência no getCurrentCameras, basta conferir se é um empty array.
Outra coisa que pensei foi criar uma flag como atributo das câmeras e setar para True quando for escrever na referência (default False) e verificar essa flag quando for pegar da referência.
Vou analisar o que é melhor.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Arquivo initializedNoGetCurrentCameras.png initializedNoGetCurrentCameras.png adicionado
- Arquivo CameraInitialized.png CameraInitialized.png adicionado
Para resolver o problema de pegar referência vazia nas primeiras iterações fiz o seguinte:
![](CameraInitialized.png)
Criei o atributo Initialized no Detection Frame e coloco ele True quando a camera chega (default é False). Dessa forma, quando eu for pegar da referência uma câmera que ainda não tenha sido escrita, vai vir com tudo default e esse booleano False.
Fiz a seguinte alteração no getCurrentCameras:
![](initializedNoGetCurrentCameras.png)
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Tarefa mãe ajustado para #1230
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Arquivo FaltavaPassarONew.png FaltavaPassarONew.png adicionado
Percebi um erro no que eu tinha feito:
![](FaltavaPassarONew.png)
Como foi dito na tarefa, criei um atributo New na câmera para conseguir identificar quais câmeras tiveram seus dados atualizados e quais não tiveram.
Mas a parte circulada na foto não estava sendo feita, então, todas as câmeras estavam chegando com New False no Kalman.
Atualizado por Gabriel Borges da Conceição há quase 5 anos
- Descrição atualizado(a) (diff)
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
Testamos o que foi feito nesta tarefa com um robô fazendo zigzag no PIRF e já causou delay visível. Então, com a ajuda da tarefa http://redmine.roboime.com.br/issues/1232 vamos analisar esse delay e propôr nova solução.
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
- Arquivo CamerasFrequencyJoinPass.jpeg CamerasFrequencyJoinPass.jpeg adicionado
- Arquivo CamerasFrequencyWhileUDPSemTratamento.jpeg CamerasFrequencyWhileUDPSemTratamento.jpeg adicionado
- Arquivo CamerasFrequencyreadudpComCase.jpeg CamerasFrequencyreadudpComCase.jpeg adicionado
Como dito, o principal objetivo desta tarefa é fazer um tratamento no recebimento das câmeras para garantir que estamos pegando todas à mesma taxa.
Na semana passada fomos ao PIRF e, em diferentes versões código, contabilizamos quantas vezes cada câmera era processada pela visão (Kalman e etc):
Código sem separação de while para leitura dos pacotes udp:
![](CamerasFrequencyJoinPass.jpeg)
Código com separação de while para leitura dos pacotes udp mas sem o tratamento proposto na tarefa (código que jogamos a LARC):
![](CamerasFrequencyWhileUDPSemTratamento.jpeg)
Código com separação de while para leitura dos pacotes udp mas com o tratamento proposto na tarefa:
![](CamerasFrequencyreadudpComCase.jpeg)
Deixamos o código rodando sem nada se movimentando por cerca de 10-20s em cada teste e fizemos vários testes pra cada versão de código e todas tinham o mesmo padrão para o mesmo código, por isso estou colocando aqui apenas um dos resultados pra cada um.
Em resumo, dos resultados acima pudemos ver que o código com o qual jogamos a LARC (visão pega apenas última câmera escrita na referência) realmente processa muito mais umas câmeras que outras e a solução que eu propus aqui na tarefa resolveu isso, mas lembrando que já sabemos que minha solução produziu delay na visão e isso será tratado/corrigido juntamente com a tarefa http://redmine.roboime.com.br/issues/1232. Detalhe é que percebemos que o ssl-vision mandava as câmeras 0, 2 e 3 com taxas parecidas e a câmera 1 com taxa menor.
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
- Data prevista alterado de 21/02/2020 para 28/02/2020
Atualizado por Nicolas Oliveira há mais de 4 anos
Em qual tarefa ficou o problema que essa implementação causou (lentidão na leitura dos pacotes)?
Atualizado por Lucas Germano há mais de 4 anos
Essa branch resultou num delay absurdo no código, cerca de 10 a 15 segundos de delay do que estava se passando no campo e o que estávamos vendo na tela. Vou tentar achar o erro de implementação, mas a princípio não vou perder muito tempo com isso para tentar criar uma branch nova de uma forma mais organizada (montando diagramas etc) para não termos que refazer essa tarefa mais uma vez.
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
- Arquivo devSugando.png devSugando.png adicionado
- Arquivo readudpSugando.png readudpSugando.png adicionado
- Arquivo readudpSugandoComWait.png readudpSugandoComWait.png adicionado
- Arquivo ColocandoWaitReadUdp.png ColocandoWaitReadUdp.png adicionado
Consegui identificar o que possivelmente era o erro.
Basicamente, no while da visão, a VI getCurrentCameras estava rodando sem wait e ficava com fps absurdo de alto e o processamento da CPU ia pra 80% e prejudicava a execução dos outros whiles. A gente não tinha percebido isso porque estávamos colocando contador de FPS apenas na parte que processava kalman e etc, e essa parte estava dentro de um case que só rodava se a VI getCurrentCameras dissesse que chegaram novas câmeras e isso rodava a mesma taxa de recebimento de pacotes.
Outro fator pra não ter percebido antes foi que no meu PC aparentemente nada de errado acontecia, mas o Antonio me avisou sobre problemas de processamento com o PC dele, então fiz os testes no PC da Intel e pude identificar o problema.
Estado do PC na readudp sem corrigir:
![](readudpSugando.png)
Estado do PC na readudp colocando wait no while da visão:
![](readudpSugandoComWait.png)
Estado do PC na dev (para comparação):
![](devSugando.png)
Mudança no código:
![](ColocandoWaitReadUdp.png)
Não que isso corrija tudo e não tenhamos mais delay em testes no PIRF, mas esse é um erro bem grave e não tem como avaliar nada com esse código errado. Já coloquei o conserto na branch readudp e sugiro testarmos novamente no PIRF.
Atualizado por Lucas Germano há mais de 4 anos
O ideal seria fazer o teste no grsim de 4 cameras, pra evitar uma viagem perdida ao pirf
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
- Situação alterado de Em andamento para Feedback
Atualizado por Nicolas Oliveira há mais de 4 anos
Outro fator pra não ter percebido antes foi que no meu PC aparentemente nada de errado acontecia, mas o Antonio me avisou sobre problemas de processamento com o PC dele, então fiz os testes no PC da Intel e pude identificar o problema.
Famoso pc da xuxa.
Mas assim, esse loop n deveria acontecer apenas quando algo chegasse na porta serial? Isso n já garantia que o problema descrito n fosse acontecer?
O fato de ser referência q ta causando isso né?
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
Nicolas Oliveira escreveu:
Outro fator pra não ter percebido antes foi que no meu PC aparentemente nada de errado acontecia, mas o Antonio me avisou sobre problemas de processamento com o PC dele, então fiz os testes no PC da Intel e pude identificar o problema.
Famoso pc da xuxa.
Mas assim, esse loop n deveria acontecer apenas quando algo chegasse na porta serial? Isso n já garantia que o problema descrito n fosse acontecer?
O fato de ser referência q ta causando isso né?
A VI de recebimento de pacotes da câmera pela porta serial está em outro while junto com a decodificação (while readUDP). While esse que escreve cada câmera que chega (decodificada) em sua respectiva referência.
Essa última foto que coloquei na tarefa é do while da visão (que faz o kalman e etc). Essa VI getCurrentCameras lê as 4 referências e, baseado no frame number das câmeras, analisa quais das 4 referências têm dados novos (pode ir de 0 a 4). O que sai dessa VI getCurrentCameras é um vetor com as câmeras novas (pode ir de 0 a 4). O resto do while da visão a gente só roda se esse vetor não for vazio (ou seja, se tiver chegado pelo menos câmera nova desde a última vez que esse while rodou).
Então essa parte de fazer Kalman e etc rodava só quando chegava dado novo (por consequência rodava praticamente à mesma taxa de recebimento de pacotes), mas essa VI getCurrentCameras rodava indefinidamente (a única coisa que trava seu processamento é qunado chega dado novo e ela tem que esperar o resto while terminar de ser executado). Mas enquanto não chegava nada novo, esse while inteiro tinha só essa VI executando e rodava a 2000fps, daí comprometia o funcionamento dos outros whiles e fazia, por exemplo, o while readUDP (de recebimento de pacotes da porta serial) rodar muito mais devagar do que deveria (devido ao processamento do pc estar sugado por esse while da visão que rodava indefinidamente a getCurrentCameras) e consequentemente acumulava pacotes. A solução que eu dei foi colocar um Wait pra fazer essa o while da visão rodar a 300 fps (porque com essa taxa, teoricamente, a VI getCurrentCameras sempre vai conseguir pegar as câmeras atualizadas (sem deixar passar nenhuma), seja no campo ou no grsim. Mas lembrando que a parte do while que tem o Kalman está dentro do um case e não roda todas as iterações e seu fps fica muito justo à taxa de recebimento de pacotes.
E sim, por usarmos a referência, tivemos que criar essa VI getCurrentCameras e analisar "na mão" quando tem um dado novo em cada referência ou não. O ideal seria usarmos outra estrutura (como a fila de substituição sugerida pelo major).
Não comentei no texto acima, mas o while readUDP, além de 4 referências para câmeras, também tem uma referência para escrever o Field Size decodificado. E a VI getCurrentCameras no while da visão também, além de ler 4 referências de câmeras, lê a referência do Field Size.
Atualizado por Nicolas Oliveira há mais de 4 anos
Entendi. Essa solução é razoável. Mas o ideal seria pensar/usar alguma estrutura com notificação. Q avisasse caso houvesse dado novo. Assim vcs n precisavam chegar na mão. É equivalente a um interrupção, que é bem melhor q o polling (que é o que vcs estão fazendo).
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
Vamos correr atrás de um jeito melhor.
Atualizado por Gabriel Borges da Conceição há mais de 4 anos
- Situação alterado de Feedback para Em andamento