feat(rede): placa em carinha.local e app que acha a placa sozinho
- sistema/rede: a placa vira "carinha" na rede (avahi), http://carinha.local na porta 80 (nftables redireciona para a 8100, sem tocar no 192.168.4.1 do portal Carinha-Config) e anúncio "_carinha._tcp" na 8100; - app 0.9.1: quando o endereço salvo não responde, procura o anúncio na rede (NsdManager), testa o endereço achado e passa a usar e guardar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="br.carinha.rosto"
|
||||
android:versionCode="12"
|
||||
android:versionName="0.9.0">
|
||||
android:versionCode="13"
|
||||
android:versionName="0.9.1">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package br.carinha.rosto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Acha a placa na rede pelo anúncio mDNS "_carinha._tcp" (avahi na Orange Pi, o mesmo do
|
||||
* carinha.local). Assim, se o IP da placa mudar, o app se reconecta sem ninguém digitar endereço.
|
||||
* Uma busca por vez; desiste depois de alguns segundos e responde no fio principal.
|
||||
*/
|
||||
final class Descoberta {
|
||||
interface Resposta { void achou(String endereco); } // null = não achou
|
||||
|
||||
private static final String TAG = "CarinhaDescoberta";
|
||||
private static final String TIPO = "_carinha._tcp.";
|
||||
private static final long LIMITE = 8000;
|
||||
|
||||
private final NsdManager nsd;
|
||||
private final Handler principal = new Handler(Looper.getMainLooper());
|
||||
private NsdManager.DiscoveryListener ouvinte;
|
||||
private Resposta resposta;
|
||||
|
||||
Descoberta(Context c) {
|
||||
nsd = (NsdManager) c.getApplicationContext().getSystemService(Context.NSD_SERVICE);
|
||||
}
|
||||
|
||||
boolean procurando() { return ouvinte != null; }
|
||||
|
||||
void procurar(Resposta r) {
|
||||
if (nsd == null) { r.achou(null); return; }
|
||||
if (ouvinte != null) return;
|
||||
resposta = r;
|
||||
ouvinte = new NsdManager.DiscoveryListener() {
|
||||
@Override public void onDiscoveryStarted(String tipo) { }
|
||||
@Override public void onDiscoveryStopped(String tipo) { }
|
||||
@Override public void onStartDiscoveryFailed(String tipo, int erro) { terminar(null); }
|
||||
@Override public void onStopDiscoveryFailed(String tipo, int erro) { }
|
||||
@Override public void onServiceLost(NsdServiceInfo s) { }
|
||||
@Override public void onServiceFound(NsdServiceInfo s) {
|
||||
if (!s.getServiceType().startsWith("_carinha._tcp")) return;
|
||||
resolver(s);
|
||||
}
|
||||
};
|
||||
try {
|
||||
nsd.discoverServices(TIPO, NsdManager.PROTOCOL_DNS_SD, ouvinte);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "busca falhou: " + e);
|
||||
ouvinte = null;
|
||||
r.achou(null);
|
||||
return;
|
||||
}
|
||||
principal.postDelayed(new Runnable() {
|
||||
@Override public void run() { terminar(null); }
|
||||
}, LIMITE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void resolver(NsdServiceInfo s) {
|
||||
try {
|
||||
nsd.resolveService(s, new NsdManager.ResolveListener() {
|
||||
@Override public void onResolveFailed(NsdServiceInfo s, int erro) { Log.w(TAG, "não resolveu: " + erro); }
|
||||
@Override public void onServiceResolved(NsdServiceInfo s) {
|
||||
if (s.getHost() == null || s.getHost().isLoopbackAddress()) return;
|
||||
String ip = s.getHost().getHostAddress();
|
||||
if (ip == null || ip.contains(":")) return; // só IPv4: o servidor ouve em 0.0.0.0
|
||||
terminar("http://" + ip + ":" + s.getPort());
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "resolver falhou: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private void terminar(final String endereco) {
|
||||
principal.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (ouvinte == null) return; // já respondeu
|
||||
try { nsd.stopServiceDiscovery(ouvinte); } catch (Exception e) { /* já parou */ }
|
||||
ouvinte = null;
|
||||
Resposta r = resposta;
|
||||
resposta = null;
|
||||
Log.i(TAG, endereco == null ? "placa não encontrada na rede" : "placa encontrada: " + endereco);
|
||||
if (r != null) r.achou(endereco);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void parar() { terminar(null); }
|
||||
}
|
||||
@@ -108,6 +108,7 @@ public class MainActivity extends Activity {
|
||||
private String servidor;
|
||||
private String token = ""; // CARINHA_TOKEN do servidor; vazio = servidor aberto
|
||||
private final CerebroLocal cerebroLocal = new CerebroLocal();
|
||||
private Descoberta descoberta; // acha a placa na rede quando o endereço salvo não responde
|
||||
private boolean jaCaiuParaLocal = false;
|
||||
private boolean jaRecarregou = false;
|
||||
private boolean usandoLocal = false; // servidor caiu: ficamos na copia do APK
|
||||
@@ -191,6 +192,7 @@ public class MainActivity extends Activity {
|
||||
usarServidorDoPedido(getIntent()); // permite apontar o servidor ao lancar
|
||||
cerebroLocal.chave = token;
|
||||
cerebroLocal.iniciar(this, servidor);
|
||||
descoberta = new Descoberta(this);
|
||||
remoto = new ControleRemoto(new Runnable() {
|
||||
@Override public void run() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -582,7 +584,7 @@ public class MainActivity extends Activity {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (!usandoLocal || !alvo.equals(servidor)) return;
|
||||
if (!voltou) { procurarServidor(); return; }
|
||||
if (!voltou) { procurarNaRede(); return; }
|
||||
Log.i(TAG, "servidor voltou: " + servidor);
|
||||
usandoLocal = false;
|
||||
jaCaiuParaLocal = false;
|
||||
@@ -594,6 +596,36 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* O endereço salvo não respondeu: procura a placa pelo anúncio na rede (carinha.local).
|
||||
* Achou em outro endereço que responde, passa a usar esse e guarda; senão tenta de novo daqui a pouco.
|
||||
*/
|
||||
private void procurarNaRede() {
|
||||
if (descoberta.procurando()) { procurarServidor(); return; }
|
||||
descoberta.procurar(new Descoberta.Resposta() {
|
||||
@Override public void achou(final String endereco) {
|
||||
if (!usandoLocal || endereco == null || endereco.equals(servidor)) { procurarServidor(); return; }
|
||||
new Thread(new Runnable() {
|
||||
@Override public void run() {
|
||||
final boolean responde = servidorResponde(endereco);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (!responde || !usandoLocal) { procurarServidor(); return; }
|
||||
Log.i(TAG, "placa achada na rede: " + servidor + " -> " + endereco);
|
||||
servidor = endereco;
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit().putString(CHAVE, servidor).apply();
|
||||
cerebroLocal.iniciar(MainActivity.this, servidor);
|
||||
usandoLocal = false;
|
||||
jaCaiuParaLocal = false;
|
||||
abrirRosto();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "testa-placa-achada").start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Qualquer resposta HTTP (até 401 de token) quer dizer que o servidor está no ar. */
|
||||
private static boolean servidorResponde(String endereco) {
|
||||
java.net.HttpURLConnection c = null;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Carinha: http://carinha.local (porta 80) leva ao painel na 8100.
|
||||
# O 192.168.4.1 fica de fora: é a página de configuração do Wi-Fi no modo ponto de acesso.
|
||||
table ip carinha {
|
||||
chain entrada {
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
ip daddr != 192.168.4.1 tcp dport 80 redirect to :8100
|
||||
}
|
||||
chain local {
|
||||
type nat hook output priority -100; policy accept;
|
||||
ip daddr 127.0.0.0/8 tcp dport 80 redirect to :8100
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
|
||||
<!-- Anúncio na rede: http://carinha.local (porta 80) e "_carinha._tcp" (porta 8100), que o app do
|
||||
celular procura quando o endereço salvo não responde. -->
|
||||
<service-group>
|
||||
<name>Carinha</name>
|
||||
<service><type>_http._tcp</type><port>80</port></service>
|
||||
<service><type>_carinha._tcp</type><port>8100</port></service>
|
||||
</service-group>
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# Nome na rede: a placa vira "carinha" e responde em http://carinha.local (rodar como root, desta pasta).
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
apt-get install -y --no-install-recommends avahi-daemon libnss-mdns nftables
|
||||
hostnamectl set-hostname carinha
|
||||
sed -i "s/^127\.0\.1\.1.*/127.0.1.1 carinha/" /etc/hosts
|
||||
grep -q "^127.0.1.1 carinha" /etc/hosts || echo "127.0.1.1 carinha" >> /etc/hosts
|
||||
install -m 644 carinha.service.avahi /etc/avahi/services/carinha.service
|
||||
install -m 644 carinha.nft /etc/nftables.d-carinha.nft
|
||||
grep -q "nftables.d-carinha.nft" /etc/nftables.conf || echo 'include "/etc/nftables.d-carinha.nft"' >> /etc/nftables.conf
|
||||
nft -c -f /etc/nftables.conf
|
||||
systemctl enable --now avahi-daemon nftables
|
||||
systemctl restart avahi-daemon nftables
|
||||
echo "pronto: http://carinha.local"
|
||||
Reference in New Issue
Block a user