spp_connect.tc¶
spp_connect.tc — Verbindungsprobe fuer Bluetooth Classic (SPP).
// spp_connect.tc — Verbindungsprobe fuer Bluetooth Classic (SPP).
//
// Verbindet sich mit EINER bekannten Adresse und schreibt mit, wie weit die
// Verbindung kommt. Gedacht fuer Geraete, die bei einer Suche NICHT auftauchen —
// viele antworten nur auf einen gezielten Verbindungsversuch, nicht auf Suchanfragen.
// (Genau der Fall beim SMA-Wechselrichter.)
//
// VORAUSSETZUNG: Firmware mit USE_TINYC_SPP auf einem URSPRUENGLICHEN ESP32, und zwar
// mit der Diagnosefassung von xdrv_124_tinyc_spp.h — erst die schreibt jedes SPP- und
// GAP-Ereignis mit Statuscode ins Protokoll. OHNE die sieht man hier nur „Zustand
// bleibt 2" und weiss nichts.
//
// ⚠️ TaskLoop, nicht spawnTask: ein Worker laeuft auf einer eigenen VM ohne die
// Feld-Tabelle des Programms, dort hat JEDES Feld Groesse 0 (Laufzeitfehler 9).
//
// Bedienung in der Konsole:
// SPPCON Verbindung auf dem voreingestellten Kanal
// SPPCON 0 Kanal ueber die Dienstsuche (SDP) ermitteln lassen
// SPPCON 1 Kanal 1 erzwingen
// SPPOFF trennen
// SPPST Zustand abfragen
//
// SO LIEST MAN DAS PROTOKOLL — die Schichten antworten in dieser Reihenfolge:
//
// SPP: gap ACL_CONN_CMPL stat=0 Basisband steht. Funk, Adresse und Reichweite
// stimmen, das Geraet hat unser Paging angenommen.
// SPP: ev CL_INIT status=0 die RFCOMM-Anfrage ist raus
// SPP: gap AUTH_CMPL stat=0 Kopplung erledigt (nur falls das Geraet sie will)
// SPP: ev OPEN status=0 Kanal offen — erst jetzt meldet sppState() eine 3
//
// WO ES AUFHOERT, IST DIE DIAGNOSE:
// gar kein ACL_CONN_CMPL -> das Geraet hat nicht geantwortet: zu weit weg, aus, oder
// sein einziger Verbindungsplatz ist belegt (Sunny Beam!)
// ACL ja, aber kein OPEN -> falsche Kanalnummer oder Kopplung. Dann SPPCON 0
// versuchen, das laesst SDP den Kanal suchen.
// CLOSE direkt nach CL_INIT -> das Geraet hat die Verbindung aktiv abgelehnt
#define ZIEL_ADR "00:80:25:09:AC:E7" // SMA Sunny Boy SB 5000TL-20
#define ZIEL_KAN 1 // SMA benutzt RFCOMM-Kanal 1
#define FRIST_MS 30000 // muss ueber TC_SPP_PAGE_TIMEOUT (20 s) liegen,
// sonst gibt das Skript vor dem Controller auf
#define AUTO_START 0 // 1 = beim Hochfahren AUTO_N Versuche von selbst
#define AUTO_N 3 // (fuer Aufstellorte ohne erreichbares WLAN)
#define AUTO_PAUSE 30000 // Pause zwischen den Selbstversuchen, ms
#define PROTOKOLL "/spp_probe.log" // jeder Versuch landet hier
char adr[24];
char zeile[112];
char rx[128];
char rest[16];
int auftrag; // 1 = verbinden, 2 = trennen, 3 = Zustand melden
int kanal;
int rest_auto; // noch offene Selbstversuche
int naechster; // millis() des naechsten Selbstversuchs
persist int laeufe; // wie oft insgesamt versucht
persist int treffer; // wie oft der Kanal aufging
// Ergebnis in eine Datei schreiben. ⚠️ Ohne Netz gibt es keine Uhrzeit, deshalb steht
// hier die Laufzeit seit dem Start statt eines Zeitstempels — die reicht, um Versuche
// auseinanderzuhalten, und sie luegt nicht.
void merken(char text[]) {
char zl[128];
sprintf(zl, "%d s Lauf %d %s", millis() / 1000, laeufe, text);
int f = fileOpen(PROTOKOLL, 2); // 2 = anhaengen
if (f >= 0) {
strcat(zl, "\n");
fileWrite(f, zl, strlen(zl));
fileClose(f);
}
addLog(zl);
}
// Zustandsnamen — 4 gibt es erst mit der Diagnosefassung der Firmware.
void zustand_melden(int st) {
if (st == 0) { sprintf(zeile, "SPPCON: Zustand %d (aus)", st); }
else if (st == 1) { sprintf(zeile, "SPPCON: Zustand %d (bereit)", st); }
else if (st == 2) { sprintf(zeile, "SPPCON: Zustand %d (verbindet)", st); }
else if (st == 3) { sprintf(zeile, "SPPCON: Zustand %d (OFFEN)", st); }
else if (st == 4) { sprintf(zeile, "SPPCON: Zustand %d (letzter Versuch FEHLGESCHLAGEN)", st); }
else { sprintf(zeile, "SPPCON: Zustand %d (unbekannt)", st); }
addLog(zeile);
}
void verbinden() {
if (!sppInit()) { addLog("SPPCON: Bluetooth-Stapel kam nicht hoch"); return; }
sppClose(); // einen Rest aus einem frueheren Versuch abraeumen
delay(500);
sprintf(zeile, "SPPCON: verbinde mit %s auf Kanal %d ...", adr, kanal);
addLog(zeile);
laeufe = laeufe + 1;
int rc = sppConnect(adr, kanal);
if (rc != 0) {
sprintf(zeile, "SPPCON: sppConnect abgelehnt, rc=%d", rc);
addLog(zeile);
return;
}
// Warten und JEDEN Zustandswechsel melden — so sieht man, ob es ueberhaupt
// vorangeht oder ob es sofort in 2 haengen bleibt.
int ende = millis() + FRIST_MS;
int vor = -1;
int st = 0;
int fertig = 0;
while (fertig == 0) {
st = sppState();
if (st != vor) { zustand_melden(st); vor = st; }
if (st == 3) { fertig = 1; }
else {
if (st == 4) { addLog("SPPCON: abgebrochen — Grund steht oben im SPP-Protokoll"); fertig = 2; }
else {
if (millis() > ende) { addLog("SPPCON: Zeitueberschreitung, kein Kanal"); fertig = 3; }
else { delay(250); }
}
}
}
if (fertig != 1) {
if (fertig == 2) { merken("FEHLGESCHLAGEN (abgelehnt oder kein Basisband)"); }
else { merken("FEHLGESCHLAGEN (Zeitueberschreitung)"); }
sppClose();
return;
}
treffer = treffer + 1;
merken("KANAL OFFEN");
saveVars();
// Kanal steht. Der SMA meldet sich von selbst — was auch immer kommt, roh anzeigen.
// Das ist der Beweis, dass nicht nur der Kanal, sondern auch die Gegenstelle lebt.
addLog("SPPCON: Kanal offen, lausche 5 s ...");
int bis = millis() + 5000;
int ges = 0;
while (millis() < bis) {
int n = sppAvailable();
if (n > 0) {
if (n > 128) { n = 128; }
int got = sppRead(rx, n);
if (got > 0) {
ges = ges + got;
sprintf(zeile, "SPPCON: %d Byte empfangen", got);
addLog(zeile);
// Erste 32 Byte als Hexwerte, 8 je Zeile
int i = 0;
int m = got;
if (m > 32) { m = 32; }
while (i < m) {
sprintf(zeile, " %02X %02X %02X %02X %02X %02X %02X %02X",
rx[i] & 0xFF, rx[i+1] & 0xFF, rx[i+2] & 0xFF, rx[i+3] & 0xFF,
rx[i+4] & 0xFF, rx[i+5] & 0xFF, rx[i+6] & 0xFF, rx[i+7] & 0xFF);
addLog(zeile);
i = i + 8;
}
}
}
delay(100);
}
sprintf(zeile, "SPPCON: insgesamt %d Byte in 5 s", ges);
addLog(zeile);
}
void TaskLoop() {
// Selbstversuche, falls dieser Aufstellort kein erreichbares WLAN hat
if (rest_auto > 0) {
if (millis() > naechster) {
rest_auto = rest_auto - 1;
naechster = millis() + AUTO_PAUSE;
auftrag = 1;
}
}
if (auftrag == 1) { auftrag = 0; verbinden(); }
else {
if (auftrag == 2) { auftrag = 0; sppClose(); addLog("SPPCON: getrennt"); }
else {
if (auftrag == 3) { auftrag = 0; zustand_melden(sppState()); }
}
}
delay(200);
}
void Command(char cmd[]) {
// ⚠️ Es gibt kein strncmp und kein atoi(puffer+N) in TinyC — strStartsWith und
// strSub/strToInt sind die vorgesehenen Mittel.
if (strStartsWith(cmd, "CON")) {
kanal = ZIEL_KAN;
if (strlen(cmd) > 4) {
strSub(rest, cmd, 4, 0); // alles hinter "CON "
int k = strToInt(rest);
if (k >= 0 && k <= 30) { kanal = k; }
}
auftrag = 1;
sprintf(zeile, "Verbindungsversuch auf Kanal %d angestossen", kanal);
responseCmnd(zeile);
} else {
if (strcmp(cmd, "OFF") == 0) {
auftrag = 2;
responseCmnd("trenne");
} else {
if (strcmp(cmd, "ST") == 0) {
auftrag = 3;
responseCmnd("Zustand folgt im Protokoll");
} else {
responseCmnd("SPPCON [kanal] | SPPOFF | SPPST");
}
}
}
}
int main() {
strcpy(adr, ZIEL_ADR);
kanal = ZIEL_KAN;
addCommand("SPP");
// ⚠️ ABSICHTLICH kein Verbindungsversuch beim Start: das Geraet hat nur EINEN
// Verbindungsplatz, und nach dem Trennen haelt es ihn noch etwa eine Minute.
// Ein Versuch beim Hochfahren wuerde sich mit dem naechsten selbst blockieren.
#if AUTO_START
rest_auto = AUTO_N;
naechster = millis() + 15000; // Netz/Stapel erst hochkommen lassen
addLog("SPPCON: bereit — AUTO_START an, Versuche laufen von selbst");
#else
rest_auto = 0;
addLog("SPPCON: bereit — SPPCON in der Konsole startet den Versuch");
#endif
sprintf(zeile, "SPPCON: bisher %d Versuche, %d Treffer (siehe %s)", laeufe, treffer, PROTOKOLL);
addLog(zeile);
return 0;
}