Tartalom:
- Dátum és időpont adathosszúság minimalizálásának indokai
- Dátum átvitel lehetőségei unsigned int értékkel két byte-on
- Időpont átvitel lehetőségei unsigned int értékkel
- Példa program
———————————————————————————————————————
Egy ideje az olcsó RF433-LC adó-vevő modulokkal foglalkozom. Ezekkel a pár száz forintos eszközökkel 10mW teljesítménnyel és egy kb. 17cm-es vezetékből készített antennával 10-20 méteres hatótáv valósítható meg. Ideálisak lakáson belüli, illetve esetemben két közeli épület közötti információ átadásra. Úgy tapasztaltam, hogy több adó modul által szétsugárzott adatokat több vevő is képes venni párhuzamosan. Gond csak akkor van, ha az adók nem tudnak egymásról, és pont egyszerre indítják az adást. De ez sem probléma esetemben, mert legfeljebb egy-egy adat kimarad. Ez nem űrhajó, pl. ha a kinti hőmérő jele csak 10 percenként érkezik meg a tervezett 5 perccel szemben, az nem tragédia. A tervem szerint különböző berendezésekből továbbítok adatokat egy központi helyre, illetve ez a bizonyos központ is sugároz információt, nevezetesen a pontos időt pl. óránként egyszer. A lakásban elhelyezett különféle berendezésekben így nincs szükség óra modulra, az Arduino saját órajelével memória változókban képzem az időt, amit időnként ezzel a szétsugárzott jellel pontosítok. Így a lakás összes óráját egyszerre lehet beállítani. Az egyéb adatok (hőmérséklet, légnyomás, különféle kapcsolók állapota stb.) float, long, int illetve byte típusúak lehetnek. Így elegendőnek látszik egy adat megjelölő byte, és a tényleges adatok átvitele 4 byte-on. Azért kell minimum 4 byte, mert pl. a float értékhez ennyi mindenképpen szükséges. A rendelkezésre álló RadióHead programkönyvtárban megkötés, hogy az átvinni kívánt információ hossza kötött, előre el kell dönteni és leprogramozni az adóban és a vevőben is. Tehát minden adónak ugyanabban a struktúrában kell sugározni, hogy egyetlen vevővel mindegyik adatát venni lehessen. Ezért választottam a négy byte hosszúságot, mint univerzális egységet mert ebbe sokféle adat típus belefér.
Ha már négy byte, akkor Jó lenne a pontos dátumot és időt egyszerre kisugározni ugyanekkora hosszúságon, ezért kezdtem el foglalkozni azzal, hogy lehetséges-e? És igen, kisebb megkötésekkel lehetséges.
Egyik megkötés, hogy elkészült berendezésünket 2178-ben halálra ítéljük, azaz nem fog ettől a dátumtól kezdve jól működni. Ennek oka, hogy 2 byte-on maximum 65535 az ábrázolható maximális szám. Ez 365 napos évekkel számolva kb. 179 év átvitelét teszi lehetővé. Azért csak kb., mert vannak szökőévek, amik alaposan megbonyolítják a problémát. Tegyük fel, hogy átadunk egy számot, legyen pl. a 86. Hogyan számoljuk ki, hogy az év 86. napja milyen dátum? A január 31 napos, tehát marad 86-31=55. A február 28 napos kivéve szökőévben, amikor 29. Tegyük fel, hogy éppen szökőév van. Vagyis 55-29=26. Tehát a 86-al március 26.-át küldtük át a vevő egységnek. Már csak azt kellene kideríteni, hogy melyik évre gondoltunk. Ez önkényes. Én a 2000 évet választottam bázis évnek, így esetemben az eredmény 2000 március 26 lenne. Ha pl. az 53034-et küldjük át ezzel a bázis évvel, akkor az 2145 március 15.-ét jelentené. Amikor ezeket a sorokat írom, éppen 2021 december végén járunk, miért választanám a 2000 évet bázisnak? Miért nem a 2021-et illetve már lehetne a 2022 is. Ennek egyszerű oka van. Nagyon sokat kínlódtam azzal, hogy átgondoljam a konvertáló és a vissza konvertáló függvényeket. Amikor elkezdtem, egyszerűbbnek tűnt 2000-el számolgatni. Amikor végeztem, akkor meg már elfáradtam, és nem akartam elrontani a függvényem működését! A tudomány mai állása szerint 2178-at még az éppen 4 hónapos unokám sem fogja megérni. Ha hobbi berendezésem esetleg még működik majd, akkor kidobják. Egyébként a 2000 év véletlenül szökőév, mivel éppen a 100. és a 400. év is az időszámítás kezdete óta, és ez meg is zavart. Amikor kipróbáltam és teszteltem a programom működését, kiderült, hogy a 2000 február 29.-ét nem kezeli, mert rosszul írtam meg a konvertálást. De csak ezt nem kezeli, a többi dátum egészen 2178-ig jól működik. Ezért azt a megoldást választottam, hogy csak 2001-től lehet dátumot átküldeni. Nem vesződtem a hiba kijavításával, nekem így is megfelel.
A másik megkötés az időpontra vonatkozik! Mivel egy nap összesen 86400 másodpercből áll, az éjfél óta eltelt időt egy unsigned int értéken nem lehet ábrázolni. Ezért csak minden 5. másodperc átvitelét tűztem ki célul. Ez azt is jelenti, hogy praktikusan csak miden 5. egész másodpercben érdemes az időpontot küldeni, és ekkor vevő oldalon ugyanazt az időpontot kapjuk, mint az adó oldalon. Az a terv, hogy óránként adok egy pontos idő sugárzást, és minden Arduino programban megvalósított óra program ennek vételekor erre az időpontra szinkronizál. Áramszünet esetén (amikor elindul minden berendezés) a központi egységem természetesen megvárja a következő 5. egész másodpercet, és akkor is sugároz egy idopontot, így pár másodperc után minden óra a lakásban a pontos időt fogja mutatni. A gond „lokális” áramszünetnél lehet, mert akkor meg kellene várni, hogy elérkezzen a központi egységben az egész óra. Erre egy nyomógombot tervezek, amivel bármikor lehet kényszeríteni az időpont küldést, ami szintén megvárhatja a következő 5. egész másodpercet. És ezzel minden lehetséges szituációt megoldottunk. Az 5 másodperces idő felbontás esetünkben nem jelent problémát.
Már megmértem, hogy 5 byte értékes adat átvitele kb. 90 msec időt vesz igénybe. Ezért várható, hogy óráim ezzel a módszerrel kb. 1 tized másodpercet késni fognak. Azonban ezt is el tudom fogadni!
Lássuk a példa programot, ami bemutatja a dátum és időpont átalakítását integer értékké, valamint vissza is alakítja dátummá illetve időponttá! Ha megleszek a rádiós adatátviteli megoldással, tenni fogok ide egy linket, amin elérhető ez a forrás is!
/********************************************************************************************************** * A programokat rádiós adatátvitelhez terveztem, ahol szempont a minél rövidebb adat használata! * * Ez a példaprogram bemutatja, hogyan lehet egy unsigned int számmá alakítani a pillanatnyi dátumot, * * valamint egy másik unsigned int számmá a pillanatnyi időt. Így összesen 4 byte-on lehet átadni * * a dátumot és az időt. A küldő (átalakító) oldalhoz tartoznak a datum_eletletnap() és ido_eltelt5sec() * * függvények. Az időpont átadása nem lehetséges 2 byte-on teljes pontossággal, csak 5 másodperc * * pontossággal, ezért van a idő átalakító függvényben az "5sec" jelzés. Pontos időt csak minden * * kerek 5. másodpercben lehet küldeni. A visszaalakító függvények az elteltnap_datum() illetve * * az eletelt5sec_ido(), ezek az unsigned int értékből megadják a pillanatnyi dátumot illetve időpontot. * * A szokoev() függvény a dátum küldő és visszaalakító függvényekhez kell. A február hónap napjainak * * száma négyévente 29, azonban minden kerek 100. évben nem. Minden 400. évben viszont mégis * * szökőév! A dátum küldő illetve fogadó függvény és visszaalakító függvény további tulajdonsága, * * hogy csak 2001 és 2178 között működik. A függvények egy központi időszolgáltatáshoz lettek * * készítve, így tényleg csak a pontos idő küldésére alkalmasok 2178-ig! * **********************************************************************************************************/ int ev; //a visszaalakító függvény ebbe teszi az eredményt byte ho,nap,ora,perc,masodperc; //a visszaalakító függvény ebbe teszi az eredményt void setup() { Serial.begin(9600); //példa a dátum küldésre és a fogadásra unsigned int napok=datum_elteltnap(2024,2,29); //2024.02.29 dátum átalakítása elteltnap_datum(napok); //visszaalakítás Serial.print(ev);Serial.print("-"); Serial.print(ho);Serial.print("-"); Serial.println(nap); //példa az időpont küldésre illetve fogadásra unsigned int masodpercek_5=ido_eltelt5sec(13,25,38); //13:25:38 időpont átalakítása eltelt5sec_ido(masodpercek_5); //visszaalakítás, amiben az eredmény 13:25:35 lesz! Serial.print(ora);Serial.print(":"); Serial.print(perc);Serial.print(":"); Serial.println(masodperc); } void loop() { } unsigned int datum_elteltnap(int ev, byte ho, byte nap) { /****************************************************************************************************************************** * Ez a függvény egy dátumot alakít át a 2000 év óta eltelt napok számává. Alapvetően a pillanatnyi dátum átadására lett * * készítve, így nem volt cél akármilyen dátum érték átvitele. Mivel a visszaadott érték unsigned int csak * 65535 napot, * * azaz 178 évet tud kezelni. Az egyszerűség kedvéért (szökőévek számolgatása), 2000 év lett a bázis év, így ebben a * * formájában 2178-ig működhet. * * Bemnő paraméterek: * * ev - évszázadokkal megadva pl. 2021 (csak 2001 és 2178 közötti érték adható meg) A 2000 évet rosszul * * számolja, mert az szökőév, de nincs rá szükség, mert az már múlt, így csak 2001-től működik * * ho - 1-12 közötti szám * * nap - 1-31 közötti szám, de az adott hónaptól függ, szökőéveket is figyelembe kell venni február hóban. * * Visszaadott érték: * * unsigned int érték 1-65535 közötti szám. * * Hibásan megadott bemenő paraméter esetén 0-át ad vissza. Hibás lehet a 2000 előtt és 2178 utáni év, * * 1-nél kisebb vagy 12-nél nagyobb hónap, illetve ha a nap 1-nél kisebb vagy az adott hónapban * * lehetséges maximális (30,31 februárban 28 szökő évben 29) napszámnál. * * Késiült egy elteltnap_datum() függvény is, ami ennek a függvények az eredményét visszaalakítja év, hó,nap értékre. * ******************************************************************************************************************************/ if (ev<2001 or ev>2178) {return(0);} //év helyességének ellenőrzése if (ho<1 or ho>12) {return 0;} //hónap helyességének ellenőrzése //hónap napjainak ellenőrzése (szökőév figyelembevételével) if ((ho==1 or ho==3 or ho==5 or ho==7 or ho==8 or ho==10 or ho==12) and nap>31) {return 0;}; if ((ho==4 or ho==6 or ho==9 or ho==11) and nap>30) {return 0;} if (ho==2) { //februárt vizsgáljuk if (szokoev(ev)) { //megmondja, hogy az adott év szökőév-e if (nap>29) { return 0;}} //ez egy szökőév és nagyobbat adtunk meg mint 29 else { if (nap>28) { return 0;}} //ez nem szökőév és nagyobbat adtunk meg mint 28 } //eddig csak ellenőriztünk, most jön a 2000 óta elmúlt napok kiszámítása int ho_napjai[] = {0,31,59,90,120,151,181,212,243,273,304,334,365}; // az évből eltelet hónapok napjainak száma, nem szökőéveben int elmult_szokonap=(ev-2000)/4; //ebbe számoljuk ki a szökőévekből adódó plusz napok számát if (szokoev(ev)) {elmult_szokonap--;} //ha az aktuális év éppen szökőév, akkor azt nem szabad beleszámolni, mert később a hónapok számlálásakor fogjuk figyelemebe venni if (ev>=2100) {elmult_szokonap--;} //2100 nem szökőév, pedig az előbb a 4-el osztással azt is annak számoltuk int elmult_nap = (ev-2000) * 365; // eddig ennyi napja volt az elmúlt éveknek a szökőévek figyelembevétele nélkül (az induló év 2000) elmult_nap=elmult_nap+elmult_szokonap; //ennyi nap telt el az éppen aktuális évig elmult_nap = elmult_nap+ho_napjai[ho-1] + nap; //kiszámoljuk, mennyi nap telt el az aktuális évben, és azt is hozzáadjuk (itt még nem foglalkozunk azzal, hogy az aktuális év szökőév-e) if (ho>2) { //ha már elmúlt február, akkor meg kell állapítani, hogy az aktuális év szökőév-e if (szokoev(ev)) {elmult_nap++;} // ha az aktuális éveben már elmúlt február, és az év szökőév, akkor még egy napot hozzá kell adni } return (elmult_nap); //és ezzel megkaptuk 2000 óta óta eltelt napok számát } void elteltnap_datum(unsigned int elmult_nap) { /********************************************************************************************************* * Ez a függvény a 2000 óta eltelt napok számából visszaadja a pontos dátumot az ev, ho, nap változókba. * * A bázisnak a 2000 évet tekinti, de a 2000 évebn nem működik jól, mert a datum_elteletnap() függvény * * írásakor elrontottam, és nem vettem figyelembe, hogy a 2000 év szökőév. Mivel a függvényt az aktuális * * dátum átvitelére terveztem, nem volt cél, hogy 2000-ban is jól működjön. Elegendő, hogy 2001-től * * 2178-ig képes a dátum átvitelére a függvénypáros. * * Bemenő paraméter egy unsigned int érték. Kimenő paraméterek (globális változóként deklarálni kell * * a programban: * * ev - int * * ho - byte * * nap -byte * *********************************************************************************************************/ ev=2000; //200-től indulunk a napokat évente csökkentő ciklussal int evnapja=365; //ez a változó az év aktuális napjainak számát adja meg while (elmult_nap>evnapja) { //addig megy a ciklus, amíg kisebb a hátralévő napok száma, mint az aktuális év napjainak száma elmult_nap=elmult_nap-evnapja; //kivonjuk az adott év napjainak számát ev++; //növeljük az évet! Így a ciklus befejezésekor az éppen aktuális évnél fogunk járni if (elmult_nap==0) {elmult_nap=1;} //ha pont megegyezik, akkor sem lehet 0 az eredmény, mert csak 1.-e lehet if (szokoev(ev)) {evnapja=366;} //a következő évben szökőév esetén 366-ot vonunk le else {evnapja=365;} //ha a következő év nem szökőév, akkor 365-öt vonunk le } //megállapítottuk, hogy hány évet adtunk meg a bemenő számadatban, most jön a maradék alapján, hogy az éven belül hányadik hónapban vagyunk int ho_napjai[] = {31,28,31,30,31,30,31,31,30,31,30,31}; //tudnunk kell, hogy egy adott hónap hány napos if (szokoev(ev)) {ho_napjai[1]=29;} // szökőévben a február 29 napos ho=1; while (elmult_nap>ho_napjai[ho-1]) { //addig megy a ciklus, amíg a napok csökkentésével egy hónap alá nem kerül a maradék elmult_nap=elmult_nap-ho_napjai[ho-1]; //csökkentjük a maradék napokat az adott hónap napjaival ho++; //növeljük a hónapot } nap=elmult_nap; //megkaptuk az adott hónap napját is a maradékban } unsigned int ido_eltelt5sec(byte ora, byte perc, byte masodperc) { /**************************************************************************************************** * Ez a függvény az éjfél óta eltelt időt alakítja másodpercekké és visszaadja egy unsigned int * * értékben. Mivel egy nap 86400 másodpercből áll, és ez nem fér el az unsigned int-ben (max:65535) * * 5 másodperces felbontásban adjuk vissza az időt, tehát csak akkor lesz pontos a visszaadott * * érték, ha a másodpercek száma éppen osztható 5-el. Az időpont átvitele mindig az 5-el osztható * * másodpercekben legyen (pl. egész perckor) * * Bemenő paraméter az óra, perc és másodperc. A másodperc lehet bármilyen érték 0-59 között * * de mindig 5-el osztható másodperceben adja meg, pl. 59-ből 55-öt csinál. * * Ha az óra nagyobb mint 23, akkor 23-ra állítja be. Ha a perc illetve másodperc nagyobb mint 59, * * akkor 59-re állítja be. * ****************************************************************************************************/ //bemenő paraméterek ellenőrzése és hibás érték esetén maximum beállítása if (ora>23) {ora=23;} if (perc>59) {perc=59;} if (masodperc>59) {masodperc=59;} masodperc=masodperc/5; //a másodperceket 5-el osztjuk, és csak ennek egész részét vesszük figyelembe return (ora*720+perc*12+masodperc); //egy órában 720-sor öt másodperc van, egy percben pedig 12-szer öt másodperc } void eltelt5sec_ido(unsigned int sec5) { /***************************************************************************************** * Ez a függvény az éjfél óta öt másodperces felbontásban megadott számból visszaállítja * * a pontos időpontot. * * Globális változókat használ, tehát a program setup része előtt deklarálni kell az * * ora, perc, masodperc byte típusú változókat. * *****************************************************************************************/ ora=sec5/720; //ennyi óra van a megadott számban perc=(sec5-ora*720)/12; //ennyi perc van az órák által megadott öt másodpercek kivonása után masodperc=(sec5-ora*720-perc*12)*5; //ennyimásodperc marad az órák és percek öt másodperces számának kivonása után. } bool szokoev(int ev) { /************************************************************************************** * Ez a függvény megállapítja egy évről, hogy szökőév-e. Bemenő paraméter az év * * évszázadokkal megadva pl. 2021. Az eredmény fals, ha nem szökőév, és true ha igen. * * Egy adott év akkor szökőév, ha 4-el osztható, de minden 100. év nem szökőév * * minden 400. pedig mégis szökőév. Pl. szökőévek: 4, 8, ...2020, 2024 stb. * * Nem szökőév 100, 200 ... 1900, 2000. Azonban mégis szökőév: 1200, 1600, 2000 stb.) * **************************************************************************************/ if (ev==0) {return false;} //a 0.évet nem vesszük szökőévnek bool szoko_ev=false; if (ev%4==0) { //ha néggyel osztva nem ad maradékot, akkor szökőév szoko_ev=true; if (ev%100==0) { //ha 100-al osztva nem ad maradékot, akkor nem szökőév szoko_ev=false; if (ev%400==0) {szoko_ev=true;} //ha 400-al osztva nem ad maradékot, akkor mégis szökőév } } return szoko_ev; }