A cada poucos meses alguém pergunta qual biblioteca Python usar com o Wialon. A resposta honesta é desconfortável: as duas bibliotecas com os nomes mais conhecidos — python-wialon e php-wialon, na organização wialon do GitHub — não são mexidas desde cerca de 2013. Elas ainda funcionam para chamadas básicas, mas são anteriores a metade da superfície da API e a todo o conhecimento operacional. Este guia entrega um cliente mínimo que você controla, em cerca de setenta linhas; depois, os cinco comportamentos que decidem se ele sobrevive ao contato com uma conta real; e por fim um mapa honesto do cenário de bibliotecas.
O protocolo, para o cliente fazer sentido
O Wialon usa POST com formulário codificado, não REST com JSON: toda chamada vai para wialon/ajax.html com um campo svc que nomeia o serviço, um campo params com JSON e um campo sid assim que você tem uma sessão. Não há caminhos, verbos nem códigos de status para ler — um HTTP 200 com {"error":7} no corpo é a forma como a negação de acesso chega. (A mecânica completa está na página de configuração do Postman.) Esse é o protocolo inteiro, e é por isso que um wrapper é um objeto de sessão em volta do requests e nada mais.
import requests
class Wialon:
def __init__(self, host="https://hst-api.wialon.com", token=None):
self.host = host
self.base = host + "/wialon/ajax.html"
self.sid = None
self.token = token
def call(self, svc, params=None, sid=True):
data = {"svc": svc, "params": params or {}}
if sid:
data["sid"] = self.sid
r = requests.post(self.base, data=data, timeout=30)
r.raise_for_status()
return r.json()
def login(self):
res = self.call("token/login", {"token": self.token}, sid=False)
if "error" in res:
raise RuntimeError(f"login failed: {res}")
self.sid = res["eid"]
return self.sid
def events(self):
# avl_evts is its own path, not an svc against ajax.html.
r = requests.post(f"{self.host}/avl_evts", data={"sid": self.sid}, timeout=30)
r.raise_for_status()
return r.json()Setenta linhas é uma qualidade, não uma limitação: não há mágica para depurar às 3 da manhã quando uma sincronização trava. Gere o token na interface do Wialon com as flags de acesso mais estreitas que cubram suas leituras — a página do token tem a tabela de flags — e mantenha-o fora do código, em uma variável de ambiente.
Com qual host você está de fato falando
hst-api.wialon.com é o endpoint do Hosting, e só é o padrão correto se a sua conta vive no Wialon Hosting. Uma instalação do Wialon Local responde no domínio do próprio parceiro, e um token gerado em um host não significa nada no outro — a sessão é emitida pelo servidor que guarda a conta. Por isso host é um argumento do construtor acima e não uma constante do módulo: no dia em que você adicionar um segundo cliente em outro deployment, a única coisa que deve mudar é o valor que você passa.
Vale nomear desde já dois erros relacionados. O primeiro é deixar hst-api.wialon.com fixo em um cliente que você pretende vender a parceiros, o que limita silenciosamente o seu produto a um único deployment. O segundo é tratar o host da interface e o host da API como a mesma string; eles são relacionados, mas não intercambiáveis, e copiar uma URL do navegador para a configuração do cliente produz um 404 que parece uma falha de rede.
Primeiro buscar, depois mensagens
Duas chamadas carregam a maior parte das integrações. core/search_items encontra coisas; messages/load_interval lê o histórico delas:
w = Wialon(token="...")
w.login()
units = w.call("core/search_items", {
"spec": {
"itemsType": "avl_unit",
"propName": "sys_name",
"propValueMask": "*",
"sortType": "sys_name",
},
"force": 1,
"flags": 1,
"from": 0,
"to": 0,
})
unit_id = units["items"][0]["id"]
msgs = w.call("messages/load_interval", {
"itemId": unit_id,
"timeFrom": 1726000000,
"timeTo": 1726086400,
"flags": 0,
"flagsMask": 0xFF00,
"loadCount": 10000,
})Quatro detalhes dessa primeira chamada decidem o que você recebe. itemsType escolhe o catálogo: avl_unit para unidades, avl_resource para recursos, avl_unit_group para grupos, user para usuários. propName e propValueMask são o filtro, e sys_name com * é o idioma para “tudo o que tenho permissão de ver”. sortType dá uma ordem estável, o que importa mais do que parece: sem ele, ao paginar uma conta grande você pode ver a mesma unidade duas vezes e perder outra. E flags não é um booleano, e sim uma máscara de bits de data-flags: 1 retorna o conjunto base (id, nome, classe, unidades de medida), enquanto sensores, contadores e última posição são bits separados, cada um com custo no tamanho da resposta. A referência de unidades traz a tabela completa; a regra a internalizar é pedir a máscara mais estreita que responda à sua pergunta, porque, em uma conta de três mil unidades, a diferença entre a flag 1 e uma máscara gulosa se mede em megabytes por consulta.
A paginação é a segunda coisa a acertar. from e to são índices de linha, não timestamps, e a resposta diz onde você está: totalItemsCount junto com indexFrom e indexTo. Pedir de 0 a 0 retorna uma única linha, o que serve para o exemplo acima e está errado para produção; percorra o índice em blocos de algumas centenas e pare quando indexTo chegar a totalItemsCount.
Duas armadilhas moram na segunda chamada. Primeiro, flagsMask seleciona quais tipos de mensagem voltam e flags diz quais deles você quer: o par documentado para mensagens de dados é a máscara 0xFF00 com flags 0x0000, e pedir tudo em uma unidade movimentada devolve megabytes que você não queria. Segundo, loadCount limita o lote: itere avançando timeFrom em vez de aumentá-lo, ou uma única unidade de longa data estourará o orçamento de cinco minutos dos relatórios e devolverá zero linhas que parecem uma semana tranquila.
Eventos ao vivo sem se matar de polling
Para quase tempo real, não consulte load_interval num temporizador. Inscreva a sessão com core/update_data_flags e depois faça polling de avl_evts, o canal de eventos do próprio Wialon. Atenção ao endereço: avl_evts não é um svc contra wialon/ajax.html, e sim um caminho próprio, {host}/avl_evts?sid=<sid>, e é por isso que o cliente precisa do segundo método acima em vez de outro call():
import time
w.call("core/update_data_flags", {
"spec": [{"type": "type", "data": "avl_unit", "flags": 1, "mode": 0}],
})
while True:
for e in w.events().get("events", []):
handle(e) # your code: queue it, do not process inline
time.sleep(1)Vale ler com atenção a entrada de spec. type diz como você seleciona os itens: "type" inscreve em uma classe inteira, e a alternativa seleciona uma coleção específica de ids, que é o que você vai querer quando a conta for grande o bastante para “todas as unidades” ser mais do que você precisa. flags é a mesma máscara de data-flags da busca, então os eventos que você recebe trazem apenas os campos que pediu. mode 0 substitui o que a sessão tinha inscrito, o que transforma a reinscrição após uma reconexão em uma única chamada idempotente, e não em uma diferença que você precise calcular.
Cada evento traz um id de item, um tipo e uma carga: uma mensagem nova, uma atualização do item, uma exclusão. A resposta também traz o relógio do próprio servidor, e usá-lo em vez do relógio do seu processo elimina toda uma classe de bugs do tipo “o evento chegou antes de acontecer” em máquinas com a hora desajustada.
Dois limites, ambos publicados, dão forma a esse laço: no máximo 10 requisições a avl_evts a cada 10 segundos por sessão, e dez requisições simultâneas à API por sessão no total. Um laço de um segundo já gasta todo o orçamento de eventos, então essa sessão não deve fazer mais nada — rode as extrações em uma segunda sessão. E o laço acima é enxuto de propósito: ele move os eventos para uma fila e volta ao polling. Processar em linha é como as integrações ficam para trás nos horários de pico e depois martelam a API para se recuperar, que é exatamente o modo de falha de que trata o artigo sobre design de sync resiliente. Se você precisa de push de verdade em vez de polling, leia por que o Wialon não tem webhooks antes de desenhar a arquitetura em torno dessa ausência.
A sessão morre em silêncio, com relógio
Uma sessão do Wialon não é um bearer token de vida longa. Ela é encerrada após cinco minutos sem requisições, o que significa que uma integração que roda um job por hora mantém uma sessão morta por cinquenta e cinco minutos de cada sessenta e só descobre isso no pior momento. O laço de eventos acima nunca percebe, porque faz polling a cada segundo; um lote noturno percebe toda noite.
Há três defesas e você quer as três. Mantenha a sessão aquecida se o processo for de longa duração — qualquer chamada barata dentro da janela serve, e avl_evts é a convencional porque também é o que você já estava consultando. Trate o erro 1 como uma instrução para fazer login de novo exatamente uma vez e repetir a chamada, nunca como algo para tentar em laço. E renove o próprio token bem dentro da regra de cem dias de inatividade descrita na página do token, porque uma sessão expirada e um token excluído aparecem ambos como erro 1, mas exigem correções completamente diferentes.
O padrão que falha em produção é a sessão compartilhada de longa duração: um login no início do processo, um sid entregue a todos os workers e nenhum caminho de reautenticação. Funciona até a primeira oscilação de rede, e então todos os workers falham ao mesmo tempo com o mesmo código, sem que nada na sua aplicação tenha mudado para explicar.
Erros, classificados antes de repetir
O Wialon devolve erros como um HTTP 200 com um código no corpo, então o seu cliente precisa procurá-los explicitamente — raise_for_status() nunca vai disparar. Os códigos caem em quatro grupos, e o grupo decide o comportamento:
| Códigos | Classe | O que o cliente deve fazer |
|---|---|---|
| 1, 1011 | Sessão perdida | Fazer login de novo uma vez, repetir a chamada uma vez e então desistir |
| 4 | Entrada inválida | Não repetir: é um bug nos seus parâmetros |
| 7 | Acesso negado | Não repetir: falta uma flag no token, ou o item não é seu |
| 10 | Limite de concorrência | Aguardar e repetir; você excedeu o teto por sessão |
| 1001 | Sem mensagens no intervalo | Não é um erro: é um resultado vazio para aquela janela |
| 1005 | Tempo de execução excedido | Dividir o intervalo e tentar as metades |
A distinção que mais poupa tempo é a entre 4 e 7. O erro 4 significa que a requisição estava malformada para o serviço: um campo ausente, uma string onde deveria haver um número, um id que não pode ser interpretado. O erro 7 significa que a requisição estava bem formada e você não tem permissão para fazê-la, o que quase sempre se deve às flags de acesso do token e não a algo do item. Repetir qualquer um dos dois é puro desperdício, e repetir o erro 1 em laço é como um endereço de saída acaba bloqueado. A referência de erros traz a tabela completa e as strings de motivo que qualificam os códigos.
Uma requisição em vez de quarenta
Quando você precisa da mesma chamada para muitos itens — ler um contador em cada unidade, atualizar um campo em uma frota inteira — core/batch as envia juntas:
res = w.call("core/batch", {
"params": [
{"svc": "core/search_item", "params": {"id": uid, "flags": 1}}
for uid in unit_ids
],
"flags": 0,
})flags 0 executa todos os comandos independentemente de falhas e devolve um resultado por comando; flags 1 para no primeiro erro, e depois disso os comandos restantes devolvem o erro 10. Prefira 0 para leituras, para que um id ruim não custe os outros trinta e nove resultados, e 1 para escritas que não podem ser aplicadas pela metade.
Agrupar também é a correção mais barata para o teto de concorrência a seguir: quarenta chamadas em uma requisição ocupam uma vaga, não quarenta.
A concorrência pertence à sessão, não ao seu pool
Três limites publicados governam o quanto você pode empurrar, e todos os três são por sessão, não por conta nem por IP: dez requisições simultâneas à API, três requisições pesadas simultâneas (o carregamento de mensagens e a execução de relatórios são os exemplos dados na documentação) e os dez avl_evts a cada dez segundos já mencionados. Um pool de vinte threads compartilhando uma sessão não vai duas vezes mais rápido que um de dez: ele começa a colecionar o erro 10.
O formato que funciona é uma sessão por worker, com um token por worker e não um token por execução. Os tokens têm um teto por usuário, então gerar um novo a cada execução acaba esgotando a cota e o job passa a falhar até para se autenticar. E mantenha o laço de eventos em uma sessão própria: ele é barato, é constante, e compartilhá-lo com uma extração é como um backfill noturno deixa o seu feed em tempo real sem recursos.
O que registrar em log, desde o primeiro dia
Quatro campos tornam qualquer incidente posterior tratável: o nome do serviço, os milissegundos decorridos, o código de erro, se houver, e os limites do intervalo para qualquer coisa que leia histórico. Adicione uma impressão digital curta do id da sessão — nunca o id em si — e você consegue distinguir “a sessão morreu” de “a chamada estava errada” com uma busca nos logs, e não com uma sessão de depuração. A única coisa que não se deve registrar é o token.
O cenário de bibliotecas, mapeado com honestidade
python-wialon/php-wialon(organização wialon, ~2013). Nomes conhecidos, projetos mortos. Servem como referência do formato das chamadas, não como dependências.py-aiowialon,wialon,wialon-sdk(PyPI, de terceiros). Wrappers assíncronos ou de conveniência, de atualização variável. Bons para scripts; audite o tratamento de sessão antes de confiar um deles à sincronização de produção: a renovação do token e o novo login nos erros 1/4 é onde costumam economizar.- Suas próprias setenta linhas (acima). O que rodamos em produção atrás de uma fila. Você é dono da política de novas tentativas, do backoff e do log, que são as partes que de fato quebram.
O padrão nas três: o HTTP é trivial, a operação não. Sessões expiram, tokens são revogados após 100 dias de inatividade, chamadas pesadas são limitadas a três por vez por sessão. Quem é dono do seu código de cliente é dono desses comportamentos — garanta que seja você, não um pacote abandonado.
Próximos passos
Com um cliente e uma fila em mãos, as próximas perguntas são de arquitetura: um sync que sobrevive aos limites de taxa, levar os dados para o PostgreSQL e empurrá-los adiante para um ERP. Se essa fila está começando a parecer um produto e não um script, é isso que a nossa prática de integração constrói.






