Arduino a Ethernet shield
V dnešním článku si ukážeme, jak pracovat s Ethernet shieldem, což je zajímavé rozšíření pro Arduino, které nám přináší nové možnosti interakce Arduina se sítí i internetem.
Obsah článku:
Ethernet Shield
Ethernet shield si (stejně jako celé Arduino) prošel poměrně dlouhým vývojem, proto se setkáme hned s několika jeho verzemi. My budeme pracovat s jeho nejnovější verzí Arduino Ethernet.
Dominantním prvkem desky je RJ45 konektor pro připojení Ethernet kabelu. Mimo něj ale na shieldu nalezneme i slot na SD kartu, jejíž používání jsme si popsali v minulém dílu. Ovládání čipu (W5100) i SD karty probíhá přes SPI rozhraní. Rychlost síťové komunikace 10/100 MB není v dnešní době zrovna strhující, ale pro naše účely je zcela dostačující. Než shield připojíme k Arduinu, otočíme jej.
Na jeho spodní straně nalezneme nálepku s MAC adresou (unikátní identifikační číslo síťového zařízení). Tu si někam poznamenáme pro pozdější použití. Když máme MAC adresu zapsanou, můžeme shield připojit na Arduino (v našem případě Arduino Mega).
Funkce
Pro programování Ethernet shieldu se používá hned několik tříd. Třída Ethernet slouží k základnímu nastavení.
Název | Zápis | Funkce |
Ethernet.begin(mac) | Ethernet.begin(mac) Ethernet.begin(mac, ip) Ethernet.begin(mac, ip, dns) Ethernet.begin(mac, ip, dns, gateway) Ethernet.begin(mac, ip, dns, gateway, subnet) |
Slouží k zahájení komunikace shieldu s okolím (většinou router či switch). Vlevo vidíte použití různých parametrů. Nejčastěji se používají pouze mac a ip. Když parametr ip nepoužijeme, je IP adresa přidělena automaticky DHCP serverem. |
Ethernet.localIP() | – | Vrátí IP adresu shieldu. Tato funkce se nepoužívá, pokud IP adresu přiřazujeme manuálně, ale když ji necháme přes DHCP přidělit automaticky. |
Ethernet.maintain() | – | Pokud má shield přiřazenou adresu automaticky, může touto funkcí požádat o její obnovení. Přidělená adresa může být v závislosti na nastavení routeru stejná i nová. |
Jakousi pomocnou třídou je třída IPAddress. Ta slouží k uchování IP adresy.
Název | Zápis | Funkce |
IPAddress() | IPAddress jmeno(a,b,c,d) | Tato funkce uloží IP adresu. Zápis adresy ve tvaru a.b.c.d (např. 10.0.0.1) se jménem mojeIP se provede vytvořením objektu IPAddress mojeIP(a,b,c,d). |
Třída Server slouží k odesílání a přijímání dat mezi shieldem a připojenými klienty (programy na jiných zařízeních, které se připojují k serveru).
Název | Zápis | Funkce |
Server() | EthernetServer mujSvr = EthernetServer(port) | Vytvoří server, který naslouchá příchozím připojením na vybraném portu (80 pro HTTP, 23 pro telnet…). |
mujSvr.begin() | – | Spustí vybraný server. |
mujSvr.available() | EthernetClient client = server.available() | Vrátí objekt Client, který je připojen k našemu serveru a odesílá data ke čtení. |
mujSvr.write() | mujSvr.write(val)mujSvr.write(buf, len) | Pošle data všem klientům připojeným k serveru. Data mohou být typu char, byte, nebo pole těchto typů (buf je poté délka tohoto pole). |
mujSvr.print() | mujSvr.print(data)mujSvr.print(data, BASE) | Stejné jako .write(), jen data převádí na text. Parametr BASE může nabývat hodnot: BIN (dvojková soustava), OCT (osmičková soustava), DEC (dekadická soustava), HEX (šestnáctková soustava). |
mujSvr.println() | mujSvr.println(data)mujSvr.println(data, BASE) | Stejné jako .print(), jen na konec přidá zalomení řádku. |
Ke zpracování dat ze serveru slouží třída Client. Tato třída vytvoří ze shieldu klienta, který se může připojit k jiným serverům.
Název | Zápis | Funkce |
EthernetClient ja | – | Vytvoří klienta s názvem ja. |
ja | while(!ja){delay(1)} | Počká, dokud není klient připojen. |
ja.connected() | – | Vrátí true, pokud klient odesílá data (v dobu zpracování už nemusí být přítomen, ale musí být od něj přijaty data). |
ja.connect() | ja.connect(ip, port) ja.connect(URL, port) |
Připojí se k vybrané ip, nebo URL přes zadaný port. |
ja.write() | ja.write(val) ja.write(buf, len) |
Pošle data serveru, ke kterému je shield připojen. |
ja.print() | ja.print(data) ja.print(data, BASE) |
Pošle data serveru jako text. |
ja.println() | ja.println(data) ja.println(data, BASE) |
Pošle data serveru jako text končící zalomením řádku. |
ja.available() | – | Vrátí počet bytů přijatých klientem od serveru. |
ja.read() | – | Přečte a vrátí hodnotu přijatého bytu. |
ja.flush() | – | Vyprázdní frontu přijatých a nepřečtených dat. |
ja.stop() | – | Odpojí se od serveru. |
Poslední třída EthernetUDP slouží k přijímání a odesílání UDP zpráv. My se jí však nebudeme zabývat. Pro více informací navštivte dokumentaci.
Použití
Když už jsme si popsali všechny potřebné funkce, můžeme se pustit do programování.
Vytváříme server
Na úvod si naprogramujeme jednoduchý server. Než ale začneme, musíme pochopit, jak spolu komunikuje server s klientem. Komunikace zde probíhá na principu dotaz-odpověď. Tyto dotazy a odpovědi mají formát pouhého textu. Jakým způsobem musí být text zapsán, definuje HTTP protokol. Nejjednodušší bude si vše předvést na ukázce komunikace. Když se chce klient připojit na server, zašle mu požadavek přibližně v tomto tvaru:
GET / HTTP/1.1 Host: 10.0.0.15 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml, application/xml;q=0.9, image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36 Referer: Accept-Encoding: gzip,deflate,sdch Accept-Language: cs,en;q=0.8,de;q=0.6,sk;q=0.4
Tato zpráva obsahuje vše potřebné pro server pro odeslání vhodných dat klientovi. Obsahuje informaci o tom, s jakou verzí HTTP pracujeme, jaký je pro klienta přijatelný formát, o jakého klienta se jedná, jaké kódování používá a jaký jazyk očekává. Prázdný desátý řádek zde není z nepozornosti. Tímto způsobem se označuje, že je požadavek ukončen.
Server poté musí požadavek zpracovat a odeslat zpět odpověď v HTML tvaru s vhodnou HTTP hlavičkou. Hlavička obsahuje informace o verzi HTTP, o stavu připojení po ukončení přenosu, nebo o automatickém obnovování. Tyto informace nám nyní stačí. Existuje ale samozřejmě celá řada dalších HTTP příkazů, jejichž seznam nalezneme například na wikipedii. Zde najdete pěkný HTML tahák.
Hlavička odpovědi tedy vypadá takto (opět s volným řádkem).
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 1
Po hlavičce a volném řádku následuje kód stránky ve formátu HTML. Představme si nyní základní strukturu stránky.
<!DOCTYPE HTML> - říkáme prohlížeči, že pracujeme s HTML <html> <head> mezi značky head se píší základní informace o stránce (kódování, CSS styly...) <title>Titulek stránky </title> - zobrazí se v záložce </head> <body> sem patří obsah stránky (ten, který vidíme v prohlížeči) </body> </html>
Se všemi získanými informacemi už můžeme poskládat program jednoduchého serveru, který bude měnit barvu pozadí pomocí css stylů podle toho, jestli je nebo není stisknuto tlačítko připojené k Arduinu na pinu 45. Tlačítko budeme kontrolovat každou vteřinu. Mimo tlačítka budeme potřebovat ještě 10k resistor a několik vodičů.
V programu použijeme také jednoduché css stylování. My budeme chtít, aby pozadí celé stránky bylo zelené nebo červené. To se v html provede tak, že se k elementu <body> připojí style=“background: red/green“. Dvojité uvozovky by ale program Arduina mohly mást, proto se používá tzv. escapování, kdy se před vybraný znak dá zpětné lomítko. Ten se poté projeví až při zpracování prohlížečem. Výsledný element body tedy bude vypadat třeba takto: <body style=\“background: green\“>
#include <SPI.h> #include <Ethernet.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x9C, 0xB7 }; IPAddress ip(10,0,0,15); //ip serveru je 10.0.0.15 EthernetServer mujSvr(80); //vytvoříme server na portu 80 void setup(){ Ethernet.begin(mac); mujSvr.begin(); //spustíme server pinMode(45, INPUT); } void loop(){ EthernetClient client = mujSvr.available(); if (client){ boolean prazdnyRadek = true; while (client.connected() && client.available()){ //dokud klient něco odesílá (HTTP požadavek) char c = client.read(); //přečti byte od klienta if(c == '\n' && prazdnyRadek){ client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println("Refresh: 1"); client.println(); client.println("<!DOCTYPE HTML>"); client.println("<html>"); client.println("<head>"); client.println("<title>Zkoumame HTML a HTTP</title>"); client.println("</head>"); if(digitalRead(45) == HIGH){ client.println("<body style=\"background:green\">"); } else{ client.println("<body style=\"background:red\">"); } client.println("</body>"); client.println("</html>"); } if(c == '\n'){ prazdnyRadek = true; } else if(c != '\r'){ prazdnyRadek = false; /*dokud klient nepošle dvakrát za sebou \r a \n znamená to, že stále odesílá data*/ } } delay(1); //dáme klientovi čas na zpracování client.stop(); //komunikace je u konce } }
Sosáme data
V druhém příkladu se připojíme k serveru a budeme po něm požadovat nějaká data, která si vypíšeme po sériové lince. Konkrétně to bude server bastlirna.hwkitchen.cz, po kterém budeme chtít soubor ahoj.txt. Ten je uložen v kořenovém adresáři serveru, tedy přímo na adrese https://bastlirna.hwkitchen.cz/ahoj.txt. Na začátek si sestavíme HTTP požadavek.
GET https://bastlirna.hwkitchen.cz/ahoj.txt HTTP/1.1 Host: bastlirna.hwkitchen.cz Connection: close
Slovy: Dej mi soubor ahoj.txt přes protokol HTTP verze 1.1 z bastlirna.hwkitchen.cz a potom ukonči spojení.
#include <SPI.h> #include <Ethernet.h> byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x9C, 0xB7}; char server[] = "bastlirna.hwkitchen.cz"; //server, kam se připojujeme EthernetClient client; void setup(){ Serial.begin(9600); Ethernet.begin(mac); delay(1000); Serial.println("Spojuji..."); if(client.connect(server, 80)){ //připojí se k serveru Serial.println("Pripojeno!"); client.println("GET /ahoj.txt HTTP/1.1"); client.println("Host: bastlirna.hwkitchen.cz"); client.println("Connection: close"); client.println(); } else { Serial.println("Spojeni se nepovedlo."); } } void loop(){ if(client.available()) { char c = client.read(); Serial.print(c); //vypíše přijatá data } if(!client.connected()) { Serial.println(); Serial.println("Odpojuji."); client.stop(); while(true){} //zastaví činnost shieldu } }
Ovládání přes síť
V posledním příkladu si ukážeme, jak Arduino ovládat pomocí prohlížeče, či jiného síťového zařízení. Budeme ovládat čtyři LED připojené na pinech 3, 4, 5 a 6. Informaci o tom, která LED bude svítit, předáme shieldu pomocí parametru v URL (to co píšeme do adresního řádku). Parametr se píše za ?. My tedy budeme v HTTP požadavku klienta hledat ? a poté čísla za ním následující. Náš program poté rozsvítí postupně LED diody daných čísel. Pokud napíšeme do prohlížeče: 10.0.0.15/?3456, HTTP požadavek odeslaný na server vypadá nějak takto.
GET /?3456 HTTP/1.1 Host: 10.0.0.15 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml, application/xml;q=0.9, image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36 Accept-Encoding: gzip,deflate,sdch Accept-Language: cs,en;q=0.8,de;q=0.6,sk;q=0.4
Jediná věc, která nás teď zajímá je první řádek, a to až za otazníkem. Poté už nás další informace nezajímají. Data, která chceme, tedy začínají otazníkem a končí mezerou. Pozor na to, že jsou tu i číslice kódovány jako ASCII.
#include <Ethernet.h> #include <SPI.h> boolean zacatekCteni = false; byte mac[] = {0x90, 0xA2, 0xDA, 0x00, 0x9C, 0xB7 }; IPAddress ip(10,0,0,15); EthernetServer mujSvr = EthernetServer(80); void setup(){ pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); Ethernet.begin(mac, ip); mujSvr.begin(); } void loop(){ EthernetClient client = mujSvr.available(); if(client){ boolean prazdnyRadek = true; boolean hlavickaPoslana = false; while(client.connected() && client.available()){ if(!hlavickaPoslana){ //jednou pošleme hlavičku client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); hlavickaPoslana = true; } char c = client.read(); if(zacatekCteni && c == ' '){ //ukončí čtení zacatekCteni = false; } if(c == '?'){ //začne čtení zacatekCteni = true; } if(zacatekCteni){ if(c == '3'){ blikni(3, client); } else if(c == '4'){ blikni(4, client); } else if(c == '5'){ blikni(5, client); } else if(c == '6'){ blikni(6, client); } } if (c == '\n') { prazdnyRadek = true; } else if (c != '\r'){ prazdnyRadek = false; } } delay(1); client.stop(); } } void blikni(int pin, EthernetClient client){ client.print("Sviti LED na pinu "); client.print(pin); client.print("<br>"); //zalomení řádku digitalWrite(pin, HIGH); delay(250); digitalWrite(pin, LOW); delay(250); }
Tímto jsme získali základní přehled o tom, co Ethernet shield umí. To ale samozřejmě není vše. Můžeme ho například naučit komunikovat s Twitterem, zjišťovat čas podle atomových hodin a další. Několik zajímavých příkladů nalezneme v oficiální dokumentaci.
V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.
- Sledovač slunce s Arduinem - 23.3.2022
- Programovatelný kytarový pedál s Arduinem - 26.2.2020
- Arduino infračervený teploměr vytištěný na 3D tiskárně - 11.2.2020
Milan_cze
28.4.2017 at 13:38Ahoj lidi. Článek je super, to fakt musím ocenit.
Mám ale problém s připojením shieldu do sítě. Síť si obsluhuju sám. Mám doma hodně zařízení a všechno si nastavím v pohodě. Ale shield né a né dostat do sítě. Mám čínský klon s jednou diodou na desce a dvě diody u konektoru LAN.
Dioda na destce svítí stále, u konektoru zelená se zelená rozsvítí a svítí. Žlutá se rozbliká a pak bliká nepravidelně. Ale router prostě nové zařízení nevidí.
Router má zaplé DHCP.
Do arduina jsem nahrával program jak s nastavením IP adresy, tak i bez, aby adresa byla poskytnuta DHCP a nic… Našel by se tu někdo s trpělivostí, aby mi to pomohl rozjet?
Zbyšek Voda
28.4.2017 at 16:05Dobrý den,
z poskytnutých informací mě nenapadá, kde by mohl být problém.
Zkuste svůj problém napsat na naše fórum https://bastlirna.hwkitchen.cz/forum/. Tam je větší pravděpodobnost, že si příspěvku všimnou i ostatní 🙂
Rovnou prosím připojte odkaz, odkud jste shield koupil a kód, který do Arduina nahráváte.
Díky
Mára
20.5.2016 at 7:51Dobrý den, chtěl bych se zeptat, jestli existuje program na Ethernet shield jako rekordér IP kamer s web prostředím a možností ukládání záznamů na MicroSD. Je to vůbec možné? Bohužel nemám dobrou zkušenost s programováním Arduina. Děkuji
vito
19.3.2016 at 22:29Ahoj, prosím o radu. Když zkouším načíst soubor ahoj.txt, napíše mi arduino Spojuji… Připojeno! Odpojuji. Žádný text mezi tím. Nevěděl mi někdo, čím to může být? Přitom když si nechám vypsat dotaz se serveru google, tak to frčí (a je to téměž identický kód). Potřebuji logovat data na sql, což mi nefungovalo a různým zkoumáním jsem zjistil, že mi nefunguje ani tahle jednoduchost.
Zbyšek Voda
20.3.2016 at 16:21Dobrý den, zkuste prosím příklad znovu. Trochu jsem jej poupravil.
Sam
11.3.2016 at 14:11Dobrý den.
Podařilo se mi zprovoznit Arduino jako server, který zobrazuje stránku s aktuální teplotou ze senzoru. Arduino je zapojeno do routeru. Stane se mi ale to, že mi zablokuje komunikaci do internetu. Jakmile Arduino odpojím, přístup do internetu zase začne fungovat. Nevíte co s tím?
Zbyšek Voda
11.3.2016 at 15:49Dobrý den,
s takovýmto problémem jsem se ještě nesetkal.
Jediné, co mě tak napadá je, jestli nedochází ke konfliktu IP adres. Je adresa Vašeho shieldu v síti unikátní?
Sam
14.3.2016 at 10:16Ano je unikátní. Zkoušel jsem změnit v kódu mac ardesu i IP adresu přiřazenou Arduinu, ale problém nezmizel. Je to nějaká zvláštní reakce routeru (FritzBox) na Arduino (UNO).
Po tom co připojím Arduino do routeru se začnou ztrácet pakety. Když pošlu z PC ping na nějaký server do internetu, tak ze 4 paketů je pravidelně 1 ztracený a na 3 dostanou normální odpověď. A není to vždycky stejný paket, prostě náhodný výběr. Ještě zkusím jiný router.
Zdeněk
19.2.2016 at 13:44Dobrý den,
mohu se zeptat trochu z jiného soudku, existuje nějaká možnost nahrát do arduina SW (binarku) pres modul ethernet shield. Konkretne mam arduino uno.
Moc dekuji za odpoved.
Zbyšek Voda
19.2.2016 at 14:02Něco podobného řeší zde: http://www.freetronics.com.au/pages/how-to-upload-a-sketch-to-your-arduino-via-a-network
B.S
4.2.2016 at 9:09No funguje to, ale není to to co potřebují 🙁
Potřebují, aby šlo s windows stáhnout soubor co mam na sd kartě a upraveny zase vratit.
Stahovat by to šlo přes web servr.
Ale jak ho dostat zpátky?
Zbyšek Voda
5.2.2016 at 10:38Dobrý den,
asi nejjednodušší řešení, co mě napadá je naprogramovat Arduino tak, aby po přijetí určitého příkazu přes síť přečetlo obsah textového souboru (znak po znaku) a poslalo jej k vám.
Na straně vašeho PC může běžet software, který znaky opět seskládá do podoby .txt souboru.
Dalším způsobem je zobrazit soubor jako web stránku – viz například zde: http://www.ladyada.net/learn/arduino/ethfiles.html
B.S
8.2.2016 at 10:22Tak lepší řešení je FTP
Nainstalovat si na win ftp servr a do arduina klienta.
Pak přes web posilat přikazy/stahni/ odešli/
Poblém nastane, pokud nemate stalou IP a musita si ji take posilat.
Neco takového by to chtělo.
https://hexifact.wordpress.com/2011/08/20/tiny-ftp-server/
Zbyšek Voda
8.2.2016 at 11:22S tím FTP máte pravdu. Jen mi to přijde trochu moc na něco tak malého, jako je Arduino 🙂
Podobný problém řeší zde: http://forum.arduino.cc/index.php?topic=179084.0
Pokud máte Arduino YUN, tak bych to neřešil a rozjel FTP na Linuxu. U slabších desek je to horší.
B.S
9.2.2016 at 8:53Yun už jede na linuxu :(.
Šel jsem do arduina kvuli perfektní podpoře s mnoha moduly.
Když linux, tak si ještě chvilku počkat na to nove rasi. za těch slibovaných 5 dlaču.
Zbyšek Voda
9.2.2016 at 8:58Yun má dva procesory – jeden s Arduino corem (ATmega32u4 – ten co má např. Leonardo) a druhý s Linuxem.
Pepepi
29.1.2016 at 10:51Dobrý den,
bylo by prosím možné vytvořit DÍL, který by se trošku podrobněji zabýval tím, jak by měla vypadat webová stránka, která komunikuje s arduinem přes klienta? Protože, myslím, že je spousta lidí, jako já, kteří si umí naprogramovat arduino, ale HTML a HTTP 🙂 je pro ně španělská vesnice. Nebo mi alespoň poraďte kde mám začít když si chci vytvořit stránku která by uměla základní funkce (zobrazení hodnot z ARD, zátržítko komunikující s I/O ARD a aby ARD umělo přečíst zadanou hodnotu z web stránky. Nechci toho moc ? 🙂 Děkuji
Zbyšek Voda
31.1.2016 at 10:08Dobrý den, v dohledné době se pokusím něco sepsat 🙂
Pepepi
31.1.2016 at 20:57Super už se nemůžu dočkat 🙂 Ať žije IoT 🙂
B.S
29.12.2015 at 13:52Fajne to je 🙂
Jak to ale zabezpečit heslem?
Zbyšek Voda
29.12.2015 at 14:16Dobrý den,
nejjednodušší je to udělat tak, aby před každým příkazem bylo nutné poslat heslo. Nevýhodou je, že se Arduino povětšinou moc nezmůže na nějaké šifrování, takže jsou příkazy jednoduše odchytitelné. Záleží na tom, jak moc velké zabezpečení potřebujete.
B.S
29.12.2015 at 18:42To ovládání přes síť.
Mam to trochu upravené.
192.168.1.50/?+1 //sepne rele 1
192.168.1.50/?-1 //rozepne rele 1
Na ovladaní použivam tento widget, kde se dá zadat login.
https://play.google.com/store/apps/details?id=com.idlegandalf.httprequestwidget
Tedy ano.
„aby před každým příkazem bylo nutné poslat heslo.“
Neva že to nepujde zašifrovat.
Alespoň to bude trochu zabezpečené.
Jak na to?
Zbyšek Voda
30.12.2015 at 20:55Máte dvě možnosti.
Buď poslat přihlašovací údaje v URL
192.168.1.50/?hodnota=-1&login=abc&pass=123
192.168.1.50/?hodnota=+1&login=abc&pass=123
Nebo využít to ověření poskytované použitou aplikací. Jestli jsem ji dobře pochopil, tak používá jednoduché ověření (basic-auth), které je součástí HTTP protokolu – viz: https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side.
Takže budete hledat řádek ve tvaru
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Řetězec QWxhZGRpbjpvcGVuIHNlc2FtZQ== je zašifrovaný řetězec: „jmeno:heslo“ pomocí RFC2045-MIME.
Vám stačí si zjistit, do jakého tvaru budou vaše přihlašovací údaje zašifrovány, uložit si ho jako řetězec do Arduina a potom je porovnat s přijatým tvarem.
B.S
28.1.2016 at 19:09Tak toto jsem zvlad.
O nějakém dobrem sample, jak dostat data na FTP s loginem by nebyl?
Zbyšek Voda
29.1.2016 at 9:53Dobrý den.
Zkuste http://playground.arduino.cc/Code/FTP
Přihlásit se | info@hwkitchen.cz
Bastlírnu provozuje e-shop HWkitchen.cz 2014-2024
Funkční Vždy aktivní
Předvolby
Statistiky
Marketing