Langaton tiedonsiirto¶
Osaamistavoitteet: Langattoman tiedonsiirron periaatteet ja tiedonsiirtoa hyödyntävän ohjelman toteutus Pico SDK:n ja kurssin kirjaston avulla
Picon langaton tiedonsiirto perustuu Infineonin CYW43439-siruun. Itse asiassa tämä siru koostuu kahdesta mikrokontrollerista, joilla on kummallakin käytössään muistia ja oheislaitteita langattomaan tiedonsiirtoon ja Picon RP2040-mikrokontrollerin kanssa kommunikointiin. Meidän ei kuitenkaan tarvitse sen enempää perehtyä tämän monimutkaisen laitteen sisäisiin toiminnallisuuksiin, vaan riittää, että tiedämme sen ominaisuudet ja tarvittavat kirjastot, joilla voimme näitä ominaisuuksia hyödyntää. CYW43439 tukee muun muassa IEEE 802.11n-standardin mukaista Wi-Fi-yhteyttä, sekä Bluetooth 5.4:ää. Käytännössä tämä siis tarkoittaa sitä, että voimme yhdistää Picon lähes mihin tahansa nykyään käytössä olevaan langattomaan verkkoon ilman ongelmia.
CYW43439:n lohkokaavio
Verkkoyhteys¶
Jotta voimme hyödyntää tätä mahtavaa sirua ohjelmassamme mahdollisimman tehokkaasti (ja helposti), meillä on käytössämme Pico SDK:n kirjasto
cyw43_arch. Tämän kirjaston avulla voimme siis alustaa CYW43439:n ja tämän jälkeen vaikka yhdistää langattomaan verkkoon. Alla on esimerkkikoodi, jossa yhdistämme Picon yliopistolla käytettävissä olevaan panoulu-verkkoon....
#include <pico/cyw43_arch.h>
...
void wirelessTask(void *pvParams) {
// Alustetaan CYW43439
if(cyw43_arch_init()) {
printf("Wireless init failed\n");
}
else {
// Otetaan käyttöön "Station"-tila, jossa voimme yhdistää verkkoihin
cyw43_arch_enable_sta_mode();
// Yhdistetään panoulu-verkkoon, joka on avoin (ei salasanaa)
// Yritämme yhdistää 30 sekuntia ennen virheviestin tulostamista
if(cyw43_arch_wifi_connect_timeout_ms("panoulu", NULL, CYW43_AUTH_OPEN, 30*1000)) {
printf("Failed to connect\n");
}
else {
printf("Connected to panoulu\n");
}
}
...
}
...
Tässä esimerkissä siis alustamme langattoman tiedonsiirron
cyw43_arch_init-funktiokutsulla, jonka paluuarvo on 0, jos alustus onnistui. Mikäli jokin meni pieleen, alustusfunktio palauttaa jonkin nollasta eroavan virhekoodin, mutta tätä meidän ei tarvitse ohjelmassamme sen enempää käsitellä. Virhetilanteen sattuessa koodissa, tulostamme virheestä kertovan viestin, emmekä yritä tehdä enää mitään muuta langattomaan tiedonsiirtoon liittyvää.Mikäli alustusfunktion paluuarvo taas oli nolla, siirrymme ottamaan käyttöön CYW43439:n asema-tilan (engl. station mode), joka siis käytännössä tarkoittaa sitä, että olemme päätelaite, joka yhdistää saatavilla oleviin verkkoihin. CYW43439 voisi myös toimia yhteyspiste-tilassa (engl. access point mode, AP), jolloin se näkyy asema-tilassa oleville laitteille avoimena verkkona, johon voi muodostaa yhteyden (Picon tapauksessa tosin mahdollinen yhdistettävien laitteiden määrä on vain 4 sen rajallisten laitteistoresurssien takia).
Aktivoituamme oikean tilan, siirrymme itse asiaan eli muodostamme yhteyden langattomaan verkkoon. Yhteys muodostetaan funktiokutsulla
cyw43_arch_wifi_connect_timeout_ms, jolle annetaan parametreiksi verkon nimi, salasana, suojausmenetelmä, sekä aika, jonka olemme valmiit odottamaan yhteyden muodostamista. Nyt tässä tapauksessa panoulu on avoin verkko, joka ei vaadi salasanaa yhteyden muodostamista varten. Näin ollen voimme asettaa salasanan arvoon NULL ja verkon suojausmenetelmän arvoon CYW43_AUTH_OPEN. Yhdistettäessä muihin verkkoihin tilanne voi tosin olla eri, jolloin on syytä asettaa nämä parametrit oikein, jotta yhteys voidaan muodostaa. Toinen mahdollinen suojausmenetelmä on esimerkiksi kodin verkoissa yleinen WPA2-PSK (vakio CYW43_AUTH_WPA2_AES_PSK). Lisää tietoa verkkoihin yhdistämisestä löytyykin Pico SDK:n dokumentaatiosta, jota on syytä tutkiskella.Langattomaan verkkoon yhdistääksemme meille tarjotaan myös muita funktioita
cyw43_arch.h-kirjastossa (esitellään dokumentaatiossa). Meille ehkä käyttökelpoisin näistä on kuitenkin cyw43_arch_wifi_connect_blocking, joka yhdistää Picon Wi-Fi-verkkoon samalla tavalla kuin aiemmin käytetty funktiokin, mutta nyt funktiokutsua suoritetaan ilman odotusaikaa ja niin kauan, että yhdistäminen onnistuu, tai tapahtuu esimerkiksi autentikaatiovirhe (väärä salasana). Tämän funktion käyttäminen taskissa siis tarkoittaisi käytännössä sitä, että mitään verkkoon liittyviä toimenpiteitä ei tehdä ennen kuin tiedetään että yhteys on muodostettu tai että sen saaminen on mahdotonta nykyisillä tiedoilla. Harjoitustyössä voi näistä esitellyistä tai miksipä ei myös muistakin dokumentaation esittelemistä funktioista valita sen omaan käyttöön sopivimman.Mutta, nyt olemme saaneet yhteyden verkkoon, mitä me sitten teemme tällä yhteydellä? Vastaus kuuluu: paljonkin asioita. Langattoman verkon avulla voimme nyt esimerkiksi suoraan Picosta ottaa yhteyden palvelimelle, jolle voimme esimerkiksi lähettää viimeisimmät lukemat sensoreistamme, tai paljon, paljon muuta! Tutustutaan yhteen tapaan käyttää tätä ominaisuutta hyödyksemme seuraavassa osiossa.
Pico ja palvelin¶
Internetissä tieto liikkuu päätelaitteiden (engl. client) ja palvelinten (engl. server) välillä. Tämä tieto voi olla käytännössä mitä tahansa, videota, kuvaa, tai vaikkapa sensoridataa. No, tuo viimeinen esimerkki oli tarkoituksenmukainen, sillä juuri sitä siirrämme palvelimelle Picosta, tietenkin sen langatonta verkkoyhteyttä hyödyntäen. Mutta miten tuo tiedonsiirto sitten oikein tapahtuu? Tämän kurssin osalta on vain tiedettävä, että verkossa tiedonsiirto tapahtuu TCP/IP protokollapinon avulla, joka tarjoaa meille rajapinnan ja standardoidut viestirakenteet tiedon siirtoon eri järjestelmien välillä. Picon SDK pitää myös sisällään toteutuksen TCP/IP protokollapinosta, ja tämän toteutuksen nimi on
lwIP (lightweight IP). Voimme siis hyödyntää tätä protokollapinoa ja kaikkia sen tarjoamia ominaisuuksia käyttämällä pico_lwip-kirjastoa. Tämä on hyvin pintapuolisen epätarkka selitys protokollapinoista ja TCP/IP:stä, sillä se ei ole pääosassa tällä kurssilla. Lisää näistä asioista voi oppia esimerkiksi Internetin perusteet-kurssilla.Kommunikaation muodostamiseksi sinun kannattaa tutustua seuraaviin kirjastoihin ja materiaaleihin:
- [https://www.raspberrypi.com/documentation/pico-sdk/networking.html#group_pico_lwip|pico-lwip wrapper|]: Kääre LWIP-kirjastolle, jota CYW43439 tukee.
- lwip-freertos: Funktioita, jotka helpottavat LWIP:n integrointia FreeRTOSiin.
HTTP-asiakas / -palvelin¶
Voit katsoa esimerkkejä täältä: official examples page:
//TODO: Check if Mongoose is possible.
TCP-soketit¶
Voit katsoa esimerkkejä täältä: official examples page:
//TODO: Bidirectional communication between two devices. Check if mongoose or directly lwit.
MQTT Server¶
Jotta lwIP-kirjaston käyttäminen sen kaikkine ominaisuuksineen olisi mahdollisimman vaivatonta, on kurssin henkilökunta toteuttanut oman kirjaston, joka siis kuitenkin käyttää lwIP-kirjaston funktioita hyödykseen. Erityisesti tämä kirjasto hyödyntää yhtä lwIP:n (ja TCP/IP:n) sovelluksista, MQTT:tä. MQTT on hyvin yleisesti sulautetuissa järjestelmissä käytetty viestintäprotokolla, joka keskittyy käyttämään mahdollisimman vähän resursseja ja kaistanleveyttä. Käytännössä MQTT mahdollistaa viestien lähettämisen ja vastaanottamisen palvelimen ja päätelaitteen välillä, kunhan päätelaite on ensin tunnistautunut esimerkiksi käyttäjätunnuksen ja salasanan avulla (muitakin tapoja on). Kun laitteet lähettävät viestejä palvelimelle, ne suunnataan aina tietyn aiheen alle. Palvelin julkaisee päätelaitteilta saamansa viestit niille annettujen aiheiden sisällä kaikille päätelaitteille, jotka tätä aihetta sattuvat kuuntelemaan.
Kurssin kirjasto kuitenkin kätkee suurimman osan MQTT:n ominaisuuksista, joten sen käyttäjän vastuulle jää vain oikean palvelimen osoitteen antaminen, sekä tunnistukseen vaadittavien käyttäjänimen ja salasanan määrittäminen. Kun yhteys on muodostettu, voi viestejä lähettää ja vastaanottaa yhdellä funktiokutsulla. Kätevää!
Katsotaan seuraavaksi koodiesimerkin avulla, miten voimme käyttää tätä kirjastoa
#include <wireless.h>
#include <string.h>
...
// Vakiot yhteyden muodostamiseen
#define SERVER_IP "server.server.com"
#define SERVER_PORT 8000
#define USERNAME "user"
#define PASSWORD "supersecretpassword"
...
void wirelessTask(void *pvParams) {
// Alustetaan kirjasto
wireless_init(SERVER_IP, SERVER_PORT, USERNAME, PASSWORD)
// Puskuri sensoridatalle
char dataBuffer[10];
// Ikuinen elämä
while(true) {
...
// Sijoitetaan luksiarvo globaalista muuttujasta puskuriin
sprintf(dataBuffer, "%.2f", lux);
// Lähetetään viesti, joka sisältää sensoridatan
if(wireless_send_message(dataBuffer, strlen(dataBuffer)) == WIRELESS_OK) {
printf("Message sent!");
}
else {
printf("Failed to send message\n");
}
...
}
}
...
Esimerkissä aluksi tuomme langattoman tiedonsiirron ohjelmaamme, ja määrittelemme palvelimen osoitteen ja portin, sekä käyttäjätunnuksen ja salasanan, joita käytetään palvelimelle tunnistautumiseen:
...
#include <wireless.h>
...
#define SERVER_IP "server.server.com"
#define SERVER_PORT 8000
#define USERNAME "user"
#define PASSWORD "supersecretpassword"
...
Nyt tietenkin omaa ohjelmaa tehdessä nämä tulee vaihtaa kurssin palvelinta ja ryhmälle annettua käyttäjää vastaavaksi.
Seuraavaksi
wirelessTask:in sisällä, ennen kuin aloitamme itse taskin toiminnan, kirjasto alustetaan. Alustukseen käytetään aiemmin määriteltyjä vakioita: wireless_init(SERVER_IP, SERVER_PORT, USERNAME, PASSWORD);
On myös tärkeää, että muistamme aina alustaa kirjaston ohjelmassamme ennen kuin siirrymme lähettämään viestejä. Näin voimme välttyä oudoilta bugeilta ja virhetilanteilta. (Oikeastaan tässä tapauksessa kirjasto on tietoinen omasta sisäisestä tilastaan, eikä se suostu lähettämään viestejä, jos sitä ei ole alustettu. Tämä on kuitenkin enemmänkin yleisen tason ohje)
Kun kirjasto on valmiina käyttöä varten, voimme siirtyä taskin pääosaan, eli
while-rakenteeseen, jossa viestien lähettäminen palvelimelle tapahtuu. Tässä esimerkissä meillä on valoisuuden arvo lukseina globaalissa muuttujassa lux, jonka haluamme lähettää palvelimelle. sprintf(dataBuffer, "%.2f", lux);
if(wireless_send_message(dataBuffer, strlen(dataBuffer)) == WIRELESS_OK) {
printf("Message sent!\n");
}
else {
printf("Failed to send message\n");
}
Viestin sisältämä data muutetaan aluksi merkkijonomuotoon
sprintf-funktiolla, jonka jälkeen se voidaan antaa kirjaston käsiteltäväksi. Annamme kirjaston wireless_send_message-funktiolle myös merkkijonon pituuden.Mikäli tiedon lähetys palvelimelle onnistui, on lähetysfunktion paluuarvona vakio
WIRELESS_OK. Tähän voimme reagoida ohjelmassamme joko tulostamalla viestin merkiksi siitä, että lähetys onnistui, tai olla kokonaan reagoimatta, jolloin luonnollisesti meidän ei tarvitse tarkistaa funktion paluuarvoa if-lauseella. Esimerkissä tulostamme myös virheviestin, mikäli lähetys epäonnistuu.Yhteistoimintaa¶
Nyt meillä on kaikki palaset, joiden avulla voimme lähettää Picosta tietoa palvelimelle, täysin langattomasti! Jotta tämä toimisi, on meidän siis yhdistettävä toiminnallisuudet, joiden avulla liitymme langattomaan verkkoon ja lähetämme viestejä palvelimelle. Onneksi tämä kaikki on tehty suhteellisen helpoksi kirjastojen avulla, sillä niiden taustalla tapahtuvat asiat sisältävät monia eri tasoja ja yksityiskohtia, joihin perehtyminen vaatii ihan oman kurssinsa.
Katsotaan tämä yhdistetty kokonaisuus koodiesimerkin avulla.
...
#define SERVER_IP "server.server.com"
#define SERVER_PORT 8000
#define USERNAME "user"
#define PASSWORD "supersecretpassword"
...
void wirelessTask(void *pvParameters) {
// Alustetaan CYW43439
if(cyw43_arch_init()) {
printf("Wireless init failed\n");
}
else {
// Otetaan käyttöön "Station"-tila, jossa voimme yhdistää verkkoihin
cyw43_arch_enable_sta_mode();
// Yhdistetään panoulu-verkkoon, joka on avoin (ei salasanaa)
// Yritämme yhdistää 30 sekuntia ennen virheviestin tulostamista
if(cyw43_arch_wifi_connect_timeout_ms("panoulu", NULL, CYW43_AUTH_OPEN, 30*1000)) {
printf("Failed to connect\n");
}
else {
printf("Connected to panoulu\n");
// Alustetaan kirjasto
wireless_init(SERVER_IP, SERVER_PORT, USERNAME, PASSWORD);
}
}
char dataBuffer[10];
while(true) {
...
// Sijoitetaan luksiarvo globaalista muuttujasta puskuriin
sprintf(dataBuffer, "%.2f", lux);
// Lähetetään viesti, joka sisältää sensoridatan
if(wireless_send_message(dataBuffer, strlen(dataBuffer)) == WIRELESS_OK) {
printf("Message sent!");
}
else {
printf("Failed to send message\n");
}
...
}
}
Käytännössä tässä olemme siis vain yhdistäneet edellisissä vaiheissa jo esittelemämme toiminnallisuudet. Ensiksi tietenkin muodostamme yhteyden Wi-Fi-verkkoon, ja tämän jälkeen olemme valmiit alustamaan palvelinyhteyden ja siirtämään dataa.
Nyt olemme siis oppineet käyttämään hyödyksemme yhtä lukuisista langattomaan tiedonsiirtoon ja TCP/IP:hen perustuvista teknologioista, joita Pico meille tarjoaa. Mikäli kiinnostuksesi heräsi, tai haluat muuten vaan tutkia lisää, Picon langattomaan tiedonsiirtoon liittyvä dokumentaatio löytyy täältä.
Kuten aiemmin todettiin, Picon TCP/IP-protokollapinona toimii lwIP. Mikäli haluat tutustua sen dokumentaatioon, tai mahdollisesti hyödyntää jotain toista TCP/IP:n sovellusta Picossa, voit tutustua lwIP:n dokumentaatioon täällä