Gomb nyomás-elengedés naplózás Arduino -val és lekérdezés Android eszközről



Alapvetően egy automatikusan működő szennyvízszivattyú működését szerettem volna valahogy nyomon követni. Ehhez egy olyan áramkör együttest találtam ki, ami egy Arduino EEPROM memóriájába gyűjti az adatokat, kiegészítve egy Real Time Clock ( RTC ) modullal, valamint a lekérdezhetőséget biztosító bluetooth modullal ( HC-06 ). Ebben a leírásban a működést csak egy nyomógombbal fogom szimbolizálni, a valóságban nyilván ettől összetettebb a felállás, hiszen a szivattyú tápvezetékén folyó áram meglétét ill. megszűntét kell érzékelni. Az eltárolt adatok lekérdezését Android eszköz végzi Blutooth kapcsolaton keresztül.

Itt is megjegyzem, hogy az Android programozását egyelőre az App Inventor alkalmazás segítségével végzem. Bár időnként áttekinthetetlen, és tudom hogy sokak szerint ez csak játékra való, nekem mindaddig megfelel, amíg maradéktalanul meg tudom vele oldani a feladatot.


Kezdjük a hardverrel, ami a következő részekből épül fel:

Áramköri megvalósítás:

circuit design  with microcontroller

A hardver rész szerintem nem szorul különösebb magyarázatra, talán csak azt érdemes kiemelni, hogy a BT modullal történő soros kommunikáció nem az Arduino hagyományos soros ( Rx és Tx ) pontjain történik, hanem feljebb, a D2 és D3 portokon. Ennek az az egyik nagy előnye, hogy nem kavar be az "üzemszerű" soros port forgalomba. Ugyancsak ki kell emelni, hogy a BT modul magas szintje 3,3 V körüli szemben az Arduino 5 V -os jelével, ezért kellett az Arduino -tól jövő jelet megjáratni egy feszültség osztón.

A valóságban így néz ki az áramkör:

Circuit diagram to measure voltage

Arduino Nano -val összerakva pedig így:


Circuit diagram to measure voltage

Következzen a szoftver, először az Arduino oldal:

Az Arduino -nak tudvalevőleg van egy olyan memória területe, amiből nem törlődik a beírt adat a tápfeszültség megszűnésekor sem, ez az EEPROM. Az általam használt eszköznél ez 1024 byte ( ha jól tudom :-). Hátránya, hogy véges számú írási ciklust bír ki, de a mi esetünkben - normál működésnél - nem fenyeget a veszély, hogy ezt akár csak meg is közelítsük. A gomb nyomási-elengedési eseményeket tehát erre a memória területre tároljuk. Az egyszerűség és célszerűség szempontjait is szem előtt tartva egy-egy bejegyzés 10 byte -os blokkokban történik. A blokkok így épülnek fel:

                    1. byte:  " + " a byte sorozat elejét jelző karakter
                    2. byte:  ÉV
                    3. byte:  HÓNAP
                    4. byte:  NAP
                    5. byte:  ÓRA  
                    6. byte:  PERC
                    7. byte:  MÁSODPERC
                    8. byte:  Mode
                    9. byte:  fenntartva későbbi célokra
                   10. byte:  " - " a byte sorozat végét jelző karakter

Az adatmentésre felhasználható memóriarész elejét és végét a program elején deklarálom. Fontos, hogy az első blokk nem a memóriaterület kezdeténél ( 0. byte -on ) indul, mert az első 10 byte -ot "egyéb" célokra tartottam fel. Az ilyen egyéb célra használt cím épp a 0., ahova azt a mutatót tárolja el a program, ami a memóriában a legutóbb beírt byte-tízes blokkra mutat. Erre azért van szükség, hogy mondjuk egy áramkimaradás után tudjuk hol tartunk a memóriába beírt adatokat illetően. Nagyon fontos a memórakezeléssel kapcsolatban, hogy ciklikusan oldottam meg, azaz ha az írással eljutottunk a felhasználható memóriaterület végére, akkor elölről kezdődik az írás, felülírva ezzel a legrégebben beírt adatot. Vagyis a memória sosem telhet be! Nyilván a mutatók beállításánál mérlegelni kell, hogy mennyi beírt blokk keletkezhet a lekérdezések között, de legegyszerűbb a lehető legnagyobbra venni az írható területet, és akkor biztos nem lehet gond.

A Loop ciklusban hajtjuk végre a nyomógomb kezelését ill. onnan hívogatjuk a bluetooth kezelést.

A nyomógomb kezelés viszonylag egyszerű: lenyomáskor kiolvassuk a pillanatnyi dátumot és időt az RTC -ből, majd a nyomásra vonatkozó információval kiegészítve letároljuk a következő szabad byte-tízes blokkba.

A bluetooth kezelés már kicsit összetettebb. Alapvetően az Android -os eszköz "dirigál", azaz ő mondja meg mit szeretne. Ezt egy kód elküldésével jelzi a következő módón:

                          - KÜLDHETI A PONTOS IDŐT  AZ RTC PONTOSÍTÁSÁRA     ( a =  97 )
                          - LEKÉRDEZHETI AZ RTC IDEJÉT ÉS A MEMÓRIA ADATOKAT ( b =  98 )
                          - LEKÉRDEZHETI AZ EEPROMBAN TÁROLT ADATOKAT        ( c =  99 )
                          - KÉRHETI a TÁROLT ADATOK TÖRLÉSÉT                 ( d = 100 )

Mindezek alapján az Arduino kód így néz ki:


#include "Wire.h"
#include <EEPROM.h>
#include <SoftwareSerial.h>    // Ez a BlueTooth soros kommunikációjához kell

#define DS3231_I2C_ADDRESS 0x68

#define rxPin 2                     // Define SoftwareSerial rx data pin  , azaz a BlueTooth RX
#define txPin 3                     // Define SoftwareSerial tx data pin  , azaz a BlueTooth TX  

SoftwareSerial BlueTooth(rxPin, txPin);  // create instance of SoftwareSerial  , ez a BlueTooth soros kommunikációjához kell

byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  
byte byteRead;     // Soros port (bluetooth) beolvasáskor ide olvasunk
byte ByteSorszam;  // Megadja, hogy hányadik byte beolvasásánál tartunk a BT modul felől

byte EV1;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott év első byte -ja
byte EV2;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott év második byte -ja
byte HON;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott hónap érték
byte NAP;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott nap érték
byte NA_;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott hét napja ( Appinventor: Weekday, Arduino: dayOfWeek )
byte ORA;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott óra érték
byte PER;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott perc érték
byte MAS;          // Soros port (bluetooth) beolvasáskor ide kerül az Android eszköztől kapott másodperc érték

int Gomb;			    // Egyelőre egy gomb működését naplózzuk.
int Mode = 0;			/* Ezzel jelezzük, hogy az aktuális gombnyomás már naplózva lett-e
                     0 - alapeset
                     5 - gombnyomás történt
                     6 - elengedés történt az előző gombnyomás óta */

int Akt;    		  // Ez mutatja az EEPROM -ban a legutóbb beírt ( Aktuális ) bejegyzés címét.
                  // Ennek TIZED részét letároljuk az EEPROM 0. címére, hogy áramszünet után is tudjuk hol tartunk.
                  // Azért a tized részét, mer csak 255 -öt tudnánk egyébként letárolni, és úgyis csak minden 10. tárhelyre mutathat.
                  // Tehát ha 0. -on 1 van akkor Akt=10,
                  //       ha 0. -on 2 van akkor Akt=20, stb.
                  /* A legeslegelső bejegyzés a Mem_kezdet tárhelyre kerül, mert az előtte levő tárhelyet fenntartjuk egyéb célokra.
                     Egy bejegyzés 10 byte, ami a következőképpen épül fel:

                    1. byte:  " + " a byte sorozat elejét jelző karakter
                    2. byte:  ÉV
                    3. byte:  HÓNAP
                    4. byte:  NAP
                    5. byte:  ÓRA  
                    6. byte:  PERC
                    7. byte:  MÁSODPERC
                    8. byte:  Mode
                    9. byte:  fenntartva későbbi célokra
                   10. byte:  " - " a byte sorozat végét jelző karakter */  
                  
					        // Írás Akt+10 címre történik, kivéve ugye ha a memória végén vagyunk, mert akkor a 'Mem_kezdet' -re visszaugorva írunk,
                  // felülírva az esetlegesen ott levő - régebbi - adatokat.
					        // Olvasás Akt+10 -től Akt -ig, figyelembe véve a memória végét.
					        // Ha még nincs teljesen feltöltődve a memória adatokkal akkor 0 -kat olvasunk ( vagy amivel előre feltöltöttük ), azt le kell kezelni kiíratáskor

int Mem_kezdet = 20 ;   // A használható memória első byte -jának címe
int Mem_plafon = 80 ;   // E fölötti címekre már nem írhatunk adatot ( az UNO -nál elvileg 1024 byte az EEPROM mérete, tehát efölé semmiképp nem szabad venni )
int Foglalt_blokk =0;   // Hány foglalt byte-tízes van a rendelkezésre álló ( Mem_kezdet és Mem_plafon közötti ) memóriaterületen
int MitAkar = 0;        /* Az Android eszköztől kapott byte, ami megadja, hogy mit szeretne tőlünk
                         *  - KÜLDHETI A PONTOS IDŐT                           ( a =  97 )
                         *  - LEKÉRDEZHETI A MI IDŐNKET ÉS A MEMÓRIA ADATOKAT  ( b =  98 )
                         *  - LEKÉRDEZHETI AZ EEPROMBAN TÁROLT ADATOKAT        ( c =  99 )
                         *  - KÉRHETI a TÁROLT ADATOK TÖRLÉSÉT                 ( d = 100 )
                        */

/* ======================================================================================================================================================================= */
void setup()
{
  Wire.begin();
  Serial.begin(9600);               // Ezen megy a sima soros kommunikáció
  BlueTooth.begin(9600);            // Ezen megy a BlueTooth kommunikáció
  
  // set the initial time here:
  // DS3231 seconds, minutes, hours, day, date, month, year
  
  setDS3231time( 50, 59, 23, 1, 24, 4, 18)  ;   // 2018.04.24.   23:59:__

  pinMode(5, INPUT);  

  Akt = EEPROM.read(0) * 10;       // Első lefutáskor beállítjuk Akt -ba a legutóbb beírt adat-tízes memóriacímét

}

/* ======================================================================================================================================================================= */
void loop()
{
 
  bt_kezeles();                                       // Ha jön a soros porton valami, beolvassuk

  displayTime();                                      // display the real-time clock data on the Serial Monitor,  

  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year); 

  
  Gomb = digitalRead( 5 );   				// Beolvassuk hogy nyomták-e a gombot 
 
  if (( Gomb == 1 ) && ( Mode == 0 ))    	// Ha nyomva lett, és még nem lett naplózva akkor naplózzuk
     {
      Mode = 5;                           // Ezzel jelezzük, hogy lenyomást kell naplózni
	   	naplozas();
     }

  if (( Gomb == 0 ) && ( Mode == 5 ))      // Ha nincs nyomva, de előzőleg nyomást naplóztunk az azt jelenti, hogy most engedték el, tehát   
     {
		   Mode = 6;                           // Ezzel jelezzük, hogy elengedést kell naplózni
		   naplozas();                         // naplózzuk az elengedést
		   Mode = 0;						               // Vége egy nyomás-elengedés naplózási ciklusnak
     }  

}


/* ======================================================================================================================================================================= */
/* ====================================================== FÜGGVÉNYEK: ==================================================================================================== */
void naplozas()
{

  /* Az Akt tehát mindig a legutóbb beírt byte-tízes-csomag első byte -jára mutat  */

  if ( (Mode == 5) || (Mode == 6) )      // Ha nyomás vagy elengedés történt akkor naplózzuk
     {
      if ( Akt+10 >= Mem_plafon )         // Ha a következő írás már a megadott memória cím limitre vagy fölé esne
         { Akt = Mem_kezdet; }        // akkor visszamegyünk a memória elejére,
           else
         { Akt = Akt + 10; }          // ha nem, akkor egyszerűen csak egy adaggal feljebb ugrunk a következő írható byte-tízesre */

      // Írunk az EEprom -ba. A struktúrát lásd fentebb.
      EEPROM.write(Akt, '+');       Akt++; delay(200);
      EEPROM.write(Akt, year);      Akt++; delay(200);
      EEPROM.write(Akt, month);     Akt++; delay(200);
      EEPROM.write(Akt, dayOfMonth);Akt++; delay(200);
      EEPROM.write(Akt, hour);      Akt++; delay(200);
      EEPROM.write(Akt, minute);    Akt++; delay(200);
      EEPROM.write(Akt, second);    Akt++; delay(200);
      EEPROM.write(Akt, Mode);      Akt++; delay(200);        // A Mode jelzi, hogy milyen esemény történt az adott időpontban, azt is beírjuk a végére 
      EEPROM.write(Akt, '-');       Akt++; delay(200);
      EEPROM.write(Akt, '-');       Akt++; delay(200);
      Akt = Akt - 10;                                            // Hiszen Akt mindig a legutóbbi byte-tízes első byte -jára mutat
      EEPROM.write(0, Akt/10); delay(200);                       // Letároljuk Akt/10 -et a 0. címre. Hogy miért /10 lásd fentebb
     }
}

/* -------------------------------------------------------------------------------- */
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year)
{
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}

/* -------------------------------------------------------------------------------- */
void readDS3231time(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}

/* -------------------------------------------------------------------------------- */
void displayTime()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
  // send it to the serial monitor
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute<10)
  {
    Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10)
  {
    Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print(" Day of week: ");
  switch(dayOfWeek){
  case 1:
    Serial.println("Sunday");
    break;
  case 2:
    Serial.println("Monday");
    break;
  case 3:
    Serial.println("Tuesday");
    break;
  case 4:
    Serial.println("Wednesday");
    break;
  case 5:
    Serial.println("Thursday");
    break;
  case 6:
    Serial.println("Friday");
    break;
  case 7:
    Serial.println("Saturday");
    break;
  }
}

/* -------------------------------------------------------------------------------- */
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return( (val/10*16) + (val%10) );
}

/* -------------------------------------------------------------------------------- */
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return( (val/16*10) + (val%16) );
}

/* -------------------------------------------------------------------------------- */
void bt_kezeles()
{

/* Ezt a függvényt kell gyúrnunk hogy az Android -os eszközzel megfelelő legyen a kommunikáció 
 *  
 * Először is meg kell állapítanunk, hogy az Android -os eszköz mit akar.  
 *  - KÜLDHETI A PONTOS IDŐT                           ( a =  97 )
 *  - LEKÉRDEZHETI A MI IDŐNKET ÉS A MEMÓRIA ADATOKAT  ( b =  98 )
 *  - LEKÉRDEZHETI AZ EEPROMBAN TÁROLT ADATOKAT        ( c =  99 )
 *  - KÉRHETI a TÁROLT ADATOK TÖRLÉSÉT                 ( d = 100 )
 *  
 *  Ezeket úgy jelzi, hogy elküldi a kívánt dolog kódját
*/

  // Először is 
  MitAkar = 0;                          // Tiszta lappal indulunk
  if  ( BlueTooth.available() )         // Ha van mit olvasni
      { 
        byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
        Serial.write(byteRead);         // Vissza echo -zzuk
        MitAkar = byteRead;
      }

// Elvileg a MitAkar csak a fenti három érték valamelyikét veheti fel mert azt kaphatjuk csak az Android eszköztől. 
// Ezeket az eseteket a következő három if feltétellel kezeljük le:


if ( MitAkar == 97 )   // AZ ANDROID -OS ESZKÖZ KÜLDI A PONTOS IDŐT
{  
/*  A BT modul felől soros porton érkezik a pontos idő a következő formátumban:

    1. byte:  " + " a byte sorozat elejét jelző karakter
    2. byte:  ÉV első byte
    3. byte:  ÉV második byte. Hogy megkapjuk a helyes évet ezt 256 -tal kell szorozni, majd hozzáadni a 2. byte -ban kapott értéket
    4. byte:  HÓNAP
    5. byte:  NAP
    6. byte:  HÉT NAPJA
    7. byte:  ÓRA  
    8. byte:  PERC
    9. byte:  MÁSODPERC
   10. byte:  " - " a byte sorozat végét jelző karakter
    
*/  
      ByteSorszam = 1;                         // Megadja, hogy hányadik byte beolvasásánál tartunk a BT modul felől

      if  ( BlueTooth.available() )         // Ha van mit olvasni
          { 
            byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
            Serial.write(byteRead);      // Vissza echo -zzuk
            if ( byteRead == 43 ) ByteSorszam = 2 ;   // Továbblépünk a ciklusból
           }
     if ( ByteSorszam == 2 )                      // Ha az előbb beolvastuk az induló "+" karaktert, akkor beolvassuk a többi byte -ot is
     {
         while ( ByteSorszam == 2 )               // Ebben a ciklusban beolvassuk az ÉV érték első byte -ját
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     EV1 = byteRead;
                     ByteSorszam = 3 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 3 )               // Ebben a ciklusban beolvassuk az ÉV érték második byte -ját
               {                                  // A helyes évet úgy kapjuk, ha ezt 256 -tal kell szorozzuk, majd hozzáadjuk az EV1 -ben kapott értéket
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     EV2 = byteRead;
                     ByteSorszam = 4 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 4 )               // Ebben a ciklusban beolvassuk az HONAP értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     HON = byteRead;
                     ByteSorszam = 5 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 5 )               // Ebben a ciklusban beolvassuk a NAP értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     NAP = byteRead;
                     ByteSorszam = 6 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 6 )               // Ebben a ciklusban beolvassuk a "HÉT NAPJA" értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     NA_ = byteRead;
                     ByteSorszam = 7 ;            // Továbblépünk a ciklusból
                   }
               }               
         while ( ByteSorszam == 7 )               // Ebben a ciklusban beolvassuk az ÓRA értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     ORA = byteRead;
                     ByteSorszam = 8 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 8 )               // Ebben a ciklusban beolvassuk az PERC értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     PER = byteRead;
                     ByteSorszam = 9 ;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 9 )               // Ebben a ciklusban beolvassuk a MÁSODPERC értékét
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     MAS = byteRead;
                     ByteSorszam = 10;            // Továbblépünk a ciklusból
                   }
               }
         while ( ByteSorszam == 10 )               // Ebben a ciklusban beolvassuk a záró " - " karaktert ( de nem csinálunk vele semmit )
               {    
                 if  ( BlueTooth.available() )       // Ha van mit olvasni
                   { 
                     byteRead = BlueTooth.read();    // Beolvasunk egy karaktert 
                     Serial.write(byteRead);      // Vissza echo -zzuk
                     ByteSorszam = 1 ;            // Továbblépünk a ciklusból
                     setDS3231time(MAS, PER, ORA, NA_, NAP, HON, EV2*256+EV1-2000)  ;    // Végül a beolvasott időt elküldjük az RTC -nek.
                   }       // Formátum: void setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year)
               }           // A DS3231 az évnek csak a második 2 számjegyét várja, ezért kell kivonni a kiszámolt év értékéből ( EV2*256+EV1 ) 2000 -et.

     }
} // Eddig tart az első funkció, azaz amikor az Android -os eszköz leküldi nekünk a pontos időt

if ( MitAkar == 98 )  // AZ ANDROID -OS ESZKÖZ LEKÉRDEZI A PONTOS IDŐT ÉS A MEMÓRIA ADATOKAT
{  

  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

  BlueTooth.write("20");
  BlueTooth.print(year, DEC);
  BlueTooth.write(".");  
  BlueTooth.print(month, DEC);
  BlueTooth.write(".");  
  BlueTooth.print(dayOfMonth, DEC);
  BlueTooth.write("  ");  
  BlueTooth.print(hour, DEC);
  BlueTooth.write(":");  
  BlueTooth.print(minute, DEC);
  BlueTooth.write(":");  
  BlueTooth.print(second, DEC);    // Eddig tartott a dátum és idő elküldése, de még elküldjük a memória foglaltságra vonatkozó adatokat * -al szeparálva:

  BlueTooth.write("*");                                  // Szeparátor, az androidos eszköz innen tudja, hogy más adat következik
  BlueTooth.print( (Mem_plafon-Mem_kezdet)/10, DEC);     // Elküldjük az androidnak a rendelkezésre álló memóriaterület méretét
  BlueTooth.write("*");                                  // Megint szeparátor

  Foglalt_blokk = 0;
  for ( int i = Mem_kezdet ; i <= Mem_plafon; i=i+10 )  // 10 -esével lépegetünk, hiszen ennyiből áll egy byte-tízes blokk
      {
       if (EEPROM.read(i) != 0)                         // Csak azokat a byte-tízeseket számoljuk, amikbena van valami adat, azaz az első byte -ja nem 0
          {
            Foglalt_blokk++;                            // Itt már 1 -esével lépünk végig a byte-tízes elemein          
          }
      }
  BlueTooth.print(Foglalt_blokk, DEC);                  // Elküldjük az androidnak a foglalt byte-tízesek darabszámát

} // Eddig tart a második funkció, azaz amikor az Android -os eszköz lekérdezi tőlünk a pontos időt és a memória adatokat

if ( MitAkar == 99 )  // AZ ANDROID -OS ESZKÖZ LEKÉRDEZI AZ EEPROM TARTALMÁT
{  

   /* Az adatokat Akt+10 memóriacímtől kezdődően kezdjük olvasni és továbbítani.
      Ha elértünk a Mem_plafon -ig, akkor Mem_kezdet -ig visszaugrunk és onnan folytatjuk az olvasást egészen Akt -ig, 
      hiszen ott van a legutóbb beírt adat, és ezzel az olvasás vége.
      Ha még nem töltődött fel teljesen a memória adatokkal, Akt+10 -ről és esetleg az utána levő byte-tízesekről 0 -kat olvasunk 
      ( vagy amivel előre feltöltöttük ), azokat természetesen fölösleges elküldenünk. Ezt legegyszerűbben úgy figyelhetjük, 
      ha a byte-tízes első byte -ját nézzük hogy '+' -e, azaz 43 -e.
   */

  Serial.print(" ************************* Android -nak küldve *******************************:");  

  for ( int i = Akt+10 ; i < Mem_plafon; i=i+10 )  // 10 -esével lépegetünk, először az Akt utáni byte-tízestől a memória terület végéig
      {
       if (EEPROM.read(i) != 0)                   // Csak akkor foglalkozunk a byte-tízessel, ha van benne valami adat, azaz az első byte -ja nem 0
          {
            for (int k=0 ; k<10; k++)             // Itt már 1 -esével lépünk végig a byte-tízes elemein          
               {
                BlueTooth.write(EEPROM.read(i+k));          // Ez egyben küldi el a karaktert,  tehát ha pl. az ÉV -nél 18 van, akkor egy 18 -as byte -ot küld
                Serial.print(EEPROM.read(i+k));
               }
          }
      }

  for ( int i = Mem_kezdet ; i < Akt+10; i=i+10 ) // 10 -esével lépegetünk, mostmár a memória terület kezdetétől Akt -ig
      {
       if (EEPROM.read(i) != 0)                   // Csak akkor foglalkozunk a byte-tízessel, ha van benne valami adat, azaz az első byte -ja nem 0
          {
            for (int k=0 ; k<10; k++)             // Itt már 1 -esével lépünk végig a byte-tízes elemein          
               {
                BlueTooth.write(EEPROM.read(i+k));          // Ez egyben küldi el a karaktert,  tehát ha pl. az ÉV -nél 18 van, akkor egy 18 -as byte -ot küld
                Serial.print(EEPROM.read(i+k));
               }
          }
      }

  Serial.println();  

  
} // Eddig tart a harmadik funkció, azaz amikor az Android -os eszköz lekérdezi tőlünk az EEPROM -ban tárolt adatokat

if ( MitAkar == 100 )  // AZ ANDROID -OS ESZKÖZ KÉRI AZ EEPROM TARTALMÁNAK TÖRLÉSÉT
{  

   /* Az adatokat Akt+10 memóriacímtől kezdődően kezdjük olvasni és továbbítani.
      Ha elértünk a Mem_plafon -ig, akkor Mem_kezdet -ig visszaugrunk és onnan folytatjuk az olvasást egészen Akt -ig, 
      hiszen ott van a legutóbb beírt adat, és ezzel az olvasás vége.
      Ha még nem töltődött fel teljesen a memória adatokkal, Akt+10 -ről és esetleg az utána levő byte-tízesekről 0 -kat olvasunk 
      ( vagy amivel előre feltöltöttük ), azokat természetesen fölösleges elküldenünk. Ezt legegyszerűbben úgy figyelhetjük, 
      ha a byte-tízes első byte -ját nézzük hogy '+' -e, azaz 43 -e.
   */

  for (int i = 0 ; i <= Mem_plafon ; i++)      // Mindent nullázunk az elejétől a lehetséges memória plafonig
    {
      EEPROM.write(i, 0);
    } 

  Akt = Mem_kezdet;                          // Az Akt értékét is újra generáljuk, de azt nem szabad elfelejteni, hogy ezzel a sorral azt adjuk meg,
                                             // hogy legutóbb a Mem_kezdet -re lett adat beírva, tehát az ezutáni beírás a Mem_kezdet+10 címre fog kerülni.
                                             // Így memória törlés után az első beírt adat tulajdonképpen nem a Mem_kezdetű byte-tízesre, hanem az
                                             // azt követőre esik, de ennek nincs jelentősége.
  EEPROM.write(0, Akt/10); delay(200);       // Akt értékét letároljuk a memória 0. byte -ján. Hogy miért /10 lásd fentebb. Utána szusszanunk egyet.
  Serial.println();  

  
} // Eddig tart a negyedik funkció, azaz amikor az Android -os eszköz azt kéri, hogy töröljük az EEPROM -ban tárolt adatokat

               
               // Csak a fenti négy eset lehetséges, ha a négy kódon kívül valami más érkezik akkor nem reagálunk

}

 

Ezután lássuk az Android oldalt:

Az alkalmazást App Inventorral 2 -vel készítettem, melyről bővebbet ezen a linken lehet megtudni. Használata erősen leegyszerűsíti az alkalmazások készítését, tulajdonképpen grafikus elemekből lehet "összerakni" a kívánt funkciókat. Ennek a leírásnak nem célja az App Inventor bemutatása. A fejlesztői környezet alapvetően két részből áll. Az egyik a Designer, melyben az alkalmazásban részt vevő elemeket tudjuk kiválasztani és felparaméterezni. A mi esetünkben ilyenből 2 is van, tekintve, hogy az alkalmazás futása során a "fő" képernyő mellett van egy külön képernyő az adatok megjelenítésére-mentésére. A kezdő ( azaz "fő" ) képernyő így néz ki:


Az adatokat megjelenítő második képernyő jóval egyszerűbb:


Működés közben az Android -os eszköz képernyőjén ezeket láthatjuk:


Ezekután nincs másra hát, mint megnézni a designer képernyőkhöz tartzozó blokkokat, vagyis tulajdonképpen magát az Android -os program kódokat. Azért többes szám, mert itt is külön van ugye a Fő képernyőhöz (Screen1) és az adatokat kiíró (Adat_kiir ) képernyőhöz tartozó rész. Ezen rész - és ezzel az App Inventor mint fejlesztőrendszer - egyik nagy hátránya, hogy az alkalmazások komplexitásának növekedésével egyre áttekinthetetlenebb, és a kommentelési lehetőség is nagyon korlátozott , ezért a jobb érthetőség miatt utólag kommenteztem.

Az egyetlen szokatlan, és talán nehezen érthető a kódban a BT adatletöltés kezelése. Nem egyértelmű számomra, hogy az App Inventor, vagy magának az Android rendszernek a sajátossága, hogy bizonyos időkritikus dolgokat speciális módon kell megoldani, kerülni kell a késleltetést. A rövidebb ( bőven 1 sec alatti ) késleltetések úgy tűnik nem jelentenek problémát, de a hosszabb, mint pl. egy byte sorozat letöltése már igen. Először klasszikus módon, ciklussal szerettem volna megoldani, ill. meg is oldottam, viszont a ciklusban szerettem volna betenni egy folyamat kijelzést, hogy éppen hol tart a letöltés. Ez sajnos nem sikerült. Akit bővebben érdekel az ezzel kapcsolatos küzdelmem itt olvashat bővebben:

MT App Inventor forum - No text displayed until the While loop finished

A megoldás lényege tömören annyi, hogy az adatáttöltés ciklus helyett egy Timer -be kellett tenni, mely - a mi esetünkben, de természetesen ez változtatható - 1 ms -onként fut le ... ha engedélyezve van. A felhasználó a letöltés gombra klikkelve engedélyezi ezt a Timer -t ( egész pontosan a Timer2 -t ), ami addig fut, amíg van letöltendő adat, vagy valami hiba nem jelentkezett. Ha ez megtörtént akkor letiltja saját magát. Tehát először a fő képernyő, azaz a Screen1 "programja":

Végezetül az adat kíírás rész programja. A Screen1 részben bluetooth -on beolvasott adatokat az ItemList tömbön keresztül kapjuk meg. Megfelelő feldolgozás után kiírjuk a kapott adatokat valamint a Be-Ki kapcsolások közötti időt ( lásd fentebb az utolsó Android -os képernyőt ). A felhasználónak lehetősége van az adatok lementésére file -ba. Íme a program:


Ezzel a végére is értem a leírásnak, remélhetőleg érthető volt. Ha nagyon sok időm lesz videót is csinálok a működéséről, de nem ígérem :-). Ha kérdés van szívesen válaszolok e-mail -ben.



JozsBiker, 2020