Jak v rámci knihovny použít funkce z jiné knihovny

Wiring, C++, C, Java, ...
Pravidla fóra
Toto subfórum slouží k řešení obecných otázek kolem programování (konstrukce, knihovny, alokace paměti, ...)
otula
Příspěvky: 9
Registrován: 03 bře 2019, 00:07
Reputation: 0

Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od otula » 03 bře 2019, 00:25

Dobrý den, chtěl bych poprosit o radu, jak v rámci knihovny využít jinou knihovnu. Konkrétně jde o to, že bych ve svém projektu chtěl použít WifiManager, který ale posílá všechny výstupy na Serial Monitor. Já bych chtěl některé z nich v upravené formě posílat na displej.

Pro displej používám knihovnu u8g2:

Kód: Vybrat vše

#include <U8g2lib.h>
#ifdef ARDUINO_ESP8266_GENERIC           // Generic ESP-01
     U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 2, 0); //SCL, SDA
#elif ARDUINO_ESP8266_NODEMCU          // NodeMCU 
     U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
WifiManager sestává v podstatě ze dvou souborů: WiFiManager.cpp a WiFiManager.h. V cpp souboru jsem si upravil potřebné výstupy, ale narazil jsem na to, že ten objekt (nebo jak se to správně nazývá) pojmenovaný u8g2 nezná. Zkoušel jsem tedy všechny možné kombinace - include <U8g2lib.h>, U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 2, 0); v rámci WiFiManager.h, WiFiManager.cpp, ponechání i smazání z mého *.ino souboru, ale nepodařilo se mi to rozchodit žádnou kombinací. Buďto to na mne řvalo, že u8g2 nezná, nebo naopak, že je deklarován vícekrát (i když byl pouze jednou v WiFiManager.h).

Můžete mi, prosím, poradit, jak se taková situace správně řeší?

Uživatelský avatar
gilhad
Příspěvky: 779
Registrován: 07 bře 2018, 11:22
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od gilhad » 03 bře 2019, 05:43

Obecne jako pouziti knihovny v jine knihovne, v normalni C++ (tady uz proste vytrkujes trochu hlavu z klasickeho Arduino lepeni kodu naslepo a stalo by za to se naucit neco o jazyku, ktery pouzivas)

Konkretne v tomto pripade (asi, prez rameno jsem ti nekoukal) jsi nejspis ty radky, co mas v KOD:VYBRAT VSE bral jako klasicke zarikadlo a cpal jsi je jako jeden blok na nahodila mista, aniz bys vedel, co presne delaji.

IMHO (ted to tu nemam jak ani kdy vyzkouset), bys mel do svojeho programu dat jen #include WiFiManager.h a do toho WiFiManager.h dat dovnitr #ifndef (tedy nekam k odstatnim #include) ten #include <U8g2lib.h>

Tim wifimanager bude vedet o u8g2lib a tvuj program o wifimanageru a jeho prostrednictvim i o u8g2lib

Dale do wifimanager.cpp prijde ta definice u8g2 (tedy ty 4 radky zacinajici #ifdef ARDUINO_ESP8266_GENERIC a jeste je ukoncit #endif) cimz v nem vznikne objekt u8g2.

a do wifimanager.h uvest, ze se "nekde" objevi u8g2 typu U8G2_SSD1306_128X64_NONAME_F_HW_I2C , tak at s tim vsichni pocitaji (za ten include toho U8g2lib.h

tedy

Kód: Vybrat vše

extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2;
takze program.ino

Kód: Vybrat vše

#include  "WiFiManager.h" // tam je vsechno, i to u8g2
....
void setup()....
WiFiManager.h:

Kód: Vybrat vše

#ifndef WiFiManager_h
....
#include <U8g2lib.h>
extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2; // takovyhle objekt nekdo nekde venku zaridi, 
   // nestarejte se jak, proste ho muzete uzivat

const char HTTP_HEAD[] PROGMEM = "<!DOCTYPE html><html ......
WiFiManager.cpp:

Kód: Vybrat vše

#include "WiFiManager.h"

// tady ten objekt skutecne zaridime
#ifdef ARDUINO_ESP8266_GENERIC           // Generic ESP-01
     U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 2, 0); //SCL, SDA
#elif ARDUINO_ESP8266_NODEMCU          // NodeMCU 
     U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
#endif


otula
Příspěvky: 9
Registrován: 03 bře 2019, 00:07
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od otula » 03 bře 2019, 11:52

Mockrát děkuji. Kompilace proběhla bez problému. Klíčové bylo tedy použít

Kód: Vybrat vše

extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2;
Takže další věc, kterou si budu muset prostudovat. Ano, máš pravdu, že by to chtělo se něco více o C++ naučit. Nemám na to moc času, ale baví mě se to učit právě na tom, že něco tvořím. Co ty označené řádky znamenají, to vcelku chápu, sám jsem si je splácal, protože program ladím na NodeMCU a průběžně testuji na ESP-01, ale samozřejmě k učení je toho nespočetněkrát více, než jsem se zatím naučil...

Ještě jednou díky!

Uživatelský avatar
gilhad
Příspěvky: 779
Registrován: 07 bře 2018, 11:22
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od gilhad » 03 bře 2019, 22:01

Klíčové bylo vytvořit tu proměnnou (a naplnit ji) na jednom jediném místě a zároveň o ní říct všem častem programu, které s ní měly pracovat.

Ten extern (a ostatní úpravy) je jedna z mnoha různých možností, jak to zařídit. Zdaleka ne jediná a dokonce ani ne vždy vhodná. Dokonce by se dalo říct, že pro čisté používání těch knihoven je spíš docela nevhodná, jen se to splácá rychle a i jiné části Arduino ekosystému používají podobně nevhodné obraty (Wire, Serial ...).

Podstatně čistší by bylo tu knihovnu upravit na reporty přez nějaké rozhraní a v hlavním programu pak to rozhraní definovat a knihovně poskytnout. Jen by se to muselo uděla pořádně, dalo by to víc práce a z hlavy bych to asi nenapsal. Zato by ta knihovna pak mohla reportovat přez cokoli bys zrovna potřeboval - u8g, Serial, I2C, wifi, hloupé displaye, SD kartu nebo cokoli jiného, případně i v kombinaci (nebo vpřez vše naráz).

Kdybych to dělal pro sebe, tak buď vyberu nějakou logovací knihovnu, nebo si ji napíšu sám, udělám objekt typu logger s vhodnýma metodama, předám ho tomu Wifi jako parametr a budu logovat do něj.

Následně pak někde ten objekt vytvořím z příhodné třídy (podle toho co, kam a jak chci logovat) a z hlavního programu ho předám té wifi.

Pokud bych chtěl logovat na u8g2, tak příhodná třída bude potomkem loggeru a bude sama řídit tu u8g2. Pokud by mi stačil Serial, použiju potomka, co bude používat Serial. Pokud to odladím a nebudu potřebovat logy, tak buď nastavím úroveň logování tak, aby nevypisoval nic (nebo jen havárie), nebo použiju potomka, který všechny logy požere a nebude nic zobrazovat.

S tímto přístupem budu schopen konzistentně logovat neje z této wifi knihovny, ale i z každé jiné a z hlavního programu. (A dost možná to bude ve výsledku zabírat míň RAM, FLASH a ještě to bude rychlejší ...)

otula
Příspěvky: 9
Registrován: 03 bře 2019, 00:07
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od otula » 03 bře 2019, 23:19

Klíčové bylo vytvořit tu proměnnou (a naplnit ji) na jednom jediném místě a zároveň o ní říct všem částem programu, které s ní měly pracovat.
To jsem celkem rychle pochopil a snažil se to udělat, jenže se mi to právě nedařilo, nevěděl jsem jak.

A to, co dál popisuješ, zní krásně, ale u mne to už naráží na ty omezené znalosti. A v mém věku, kdy mám spoustu jiných starostí, nedostatek volného času a žádné známé, kteří by tomu rozuměli a mohli poradit, se to dohání těžko. Když jsem byl na vysoké, tak jsme se tam učili Basic pro PMD-85, který jsem ovládal lépe, než náš vyučující, no a do života to nic nedalo... ;) Ale snažím se alespoň něco naučit.

Uživatelský avatar
gilhad
Příspěvky: 779
Registrován: 07 bře 2018, 11:22
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od gilhad » 04 bře 2019, 02:15

Tady je popsano, jak se dela logovani v pythonu (je to standardni knihovna, neco jako digitalRead() pro Arduino)

https://docs.python.org/2/library/logging.html

Python neni zas tak moc jiny nez treba C++, jenom misto slozenych zavorek pouziva odsazeni - jinak se to da priblizne cist a pochopit smysl.

V podstate pro Adruino by se to dalo (pro zacatek) strasne zjednodusit a osekat:

logovani.h

Kód: Vybrat vše

#ifndef LOGOVANI_H
#define LOGOVANI_H
// hlavicku vkladame do dane kompilacni jednotky jen jednou, kdyz ji includuje nejaka dalsi hlavicka, tak uz to preskocime


#define LOG_INFO 20
#define LOG_ERROR 40
class logger{
public:
  int set_level; // hladina, od ktere se loguje
  virtual void log(int level, static char *msg)=0; // posle chybovou hlasku, pokud je level >= set_level - ZDE NEIMPLEMENTOVANO
  void info( static char *msg) { log(LOG_INFO,msg);};
  void error( sttic char * msg) { log(LOG_ERROR,msg);};
}

// sem prijdou treba dalsi tridy

// konec hlavicky
#endif
(a s tim uz se pokryje vetsina potrebneho - cokoli dokazeme prevest na retezec, pokud je treba, udelame vic logu za sebou. Samozrejme to jde vylepsit ukladanim hlasek do PROGMEM a tak podobne, udelanim logu pro inty, nebo ve smyslu strf a s promennym poctem parametru, nebo podobne jako Serial ...)

no a pak udelat tridy pro ruzne vystupy, treba pro Serial:

logovani.h

Kód: Vybrat vše

class serial_loger : public logger {
  virtual void log(int level, static char *msg); // tohle zimplementujeme
};
logovani.cpp

Kód: Vybrat vše

#include logovani.h
  void serial_logger::log(int level, static char *msg){
    if (level >= set_level) Serial.writeln(msg);
  };
program.ino:

Kód: Vybrat vše

#include logovani.h
#include neco.h

serial_logger mylog();

jina_trida jt(mylog,param1, param2);

void setup(){
  Serial.begin(9600);
  // po odladeni treba
  // mylog_set_level=LOG_ERROR
  // aby to nebylo tak ukecane
  mylog.info("Setup");
  .....
  jt.begin();
  ...

neco.h

Kód: Vybrat vše

#ifndef NECO_H
#define NECO_H

#include logovani.h

class jina_trida {
 public:
 jina_trida(logger al, int ap1, int ap2); //konstruktor
 ~jina_trida(); //destruktor
 void begin();
 // dalsi funkce ...
 private:  // nebo klidne public, podle potreby a pouziti
 logger l;
 int par1;
 int par2;
 

#endif
neco.cpp

Kód: Vybrat vše

#include neco.h
jina_trida::jina_trida(logger al, int ap1, int ap2) : l(al),par1(ap1),par2(ap2){
  l.info("Jina trida - konstruktor");
  if (par1==0) {
      l.error("par1 nesmi byt nula");
      //nejake osetreni ci havarie
   }
};
    
jina_trida::~jina_trida(){// prazdny destruktor
};

jina_trida::begin(){
  l.info("jina trida begin()");
  // ....
  };


Uživatelský avatar
gilhad
Příspěvky: 779
Registrován: 07 bře 2018, 11:22
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od gilhad » 04 bře 2019, 12:50

No, psal jsem to pozde, jsou tam ruzne chyby (static misto const a tak), tak behem tydne sem dam link na funkcni knihovnu i s nejakym popisem ...

otula
Příspěvky: 9
Registrován: 03 bře 2019, 00:07
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od otula » 04 bře 2019, 14:55

Mockrát díky, budu se tím muset pomalu v klidu prokousat :)

Uživatelský avatar
gilhad
Příspěvky: 779
Registrován: 07 bře 2018, 11:22
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od gilhad » 04 bře 2019, 20:40

Tak jsem udělal příklad se Serial a hodil ho na svůj blog http://micro-corner.gilhad.cz/ jako článek Arduino: lib.logging

Na rozdíl od toho, co jsem tu včera psal z hlavy, tak tohle už opravdu funguje na skutečném HW a dělá to něco zajímavého.

Pro u8g2 jsem tam zatím nenapsal nic, protože nemám žádné lcd při ruce, kde bych to vyzkoušel a není mi zcela jasné, jak by měl vypadat výsledek, ale nemělo by to být těžké upravit.

Rozhodně to ukazuje jak se s knihopvnama dá zacházet a různě je kombinovat, aniž by si člověk příliš šlápnul na nohu :)

EDIT: typo v odkazu na Arduino: lib.logging

otula
Příspěvky: 9
Registrován: 03 bře 2019, 00:07
Reputation: 0

Re: Jak v rámci knihovny použít funkce z jiné knihovny

Příspěvek od otula » 05 bře 2019, 00:04

Nezbývá mi, než ještě jednou moc poděkovat. Mockrát díky!

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 23 hostů