Klasser og biblioteker

Klasser og biblioteker

I dette kapittelet skal vi lage en egen klasse som vi senere kan lage flere objekter ut fra. Vi skal gjøre det i form av et bibliotek, slik at også andre programmerere kan dra nytte av jobben vi gjør. Dette eksempelet viser hvor bra objektorientert programmering er fra et samarbeidsperspektiv. Ved å dokumentere klassene godt (og lage mange åpne/fleksible metoder) kan flere programmerere jobbe parallelt med hver sin del av et større program. 

Morsebibliotek

I While-løkker bygget vi en SOS-blinker som kunne blinke morsebokstavene S og O. I dette eksempelet skal vi bygge en mer fleksibel løsning som fungerer for alle morsebokstaver. Når løsningen vår er klar, kan vi erstatte den lange koden i While-løkker med bare én kodelinje!

blinker.pulseText("sos");

Som koden ovenfor antyder skal vi lage et objekt som heter blinker. Det objektet har en metode som heter pulseText og brukes til å sende morsemeldinger. Oversettingen fra tekststrenger til morsekommandoer skjer altså ikke i skissen, men i klassen som blinker er en forekomst av.

For å kunne lage blinker-objekter må vi først lage klassen. Vi vil at klassen skal være enkel å bruke og ha mange funksjoner. Klassen skal kunne brukes til både enkeltstående tegn og hele tekststrenger. Den skal også kunne brukes for ulike GPIO-pinner og ulike morsepulslengder (hastigheter).

Når klassen er klar, pakker vi den i et bibliotek, slik at andre programmerere kan dra nytte av det vi har gjort (på samme måte som vi har dratt nytte av en annen programmerers Button-bibliotek).

Et bibliotek består av minst fire filer:

  • klassens header-fil som beskriver selve klassen 
  • klassens kildekodefil som inneholder selve programkoden
  • en eksempelfil som viser hvordan biblioteket kan brukes
  • en nøkkelordfil som gir utviklingsmiljøet informasjon om bibliotekets funksjoner.

Header- og kildekodefil

Vi vil at klassen vår skal hete MorsePulse. Det innebærer at header-filen skal hete MorsePulse.h og kildekodefilen skal hete MorsePulse.cpp. Vi begynner med å lage header-filen ved å klikke på pilen lengst til høyre, velge Ny Fane og gi filen navnet MorsePulse.h.

Vi lager en ny fil kalt MorsePulse.h.

Header-filer skrives i henhold til følgende mal.

#ifndef MorsePulse_h
#define MorsePulse_h

#include "Arduino.h"

class MorsePulse {
  public:

  private:
};

#endif

Uferdig versjon av MorsePulse.h

Legg merke til at vi begynner med #ifndef MorsePulse_h (og avslutter med #endif). Dette er en sikkerhetsforanstaltning som forhindrer at noen inkluderer samme bibliotek flere ganger.

Legg også merke til at #include "Arduino.h" står i innledningen. Denne instruksjonen er nødvendig for å gi klassen tilgang til alle standardfunksjoner som Arduino har (vanlige skisser får tilgang til dem automatisk).

Vi kommer tilbake til hva som skal stå under henholdsvis public og private, men først må vi skrive selve kildekodefilen. Det gjør vi ved å klikke på pilen lengst til høyre, velge Ny Fane og gi filen navnet MorsePulse.cpp.

Tips! Arduino IDE lagrer automatisk filene MorsePulse.h og MorsePulse.cpp i samme mappe som skissen du arbeider med. Hvis du har lukket filene MorsePulse.h og MorsePulse.cpp og vil åpne dem igjen, gjør du det ved å åpne skissen som deler mappe med de aktuelle filene. Du kan ikke åpne bare MorsePulse.h eller MorsePulse.cpp i Arduino IDE. Du kan derimot bare åpne disse filene med andre utviklingsmiljøer, for eksempel Sublime Text (se Bruke Sublime Text).

Filene MorsePulse.h og MorsePulse.cpp åpnes sammen med en skisse.

I kildekodefilen begynner vi med å inkludere Arduinos standardfunksjoner og header-filen.

Del 1/6 av MorsePulse.cpp

Vi lager deretter klassens såkalte konstruktør. Konstruktøren er den funksjonen som kjøres når noen lager et nytt objekt av klassen. Konstruktøren skal ha samme navn som klassen, og alle funksjoner som tilhører klassen, skal navngis med klassens navn etterfulgt av dobbelt kolon og funksjonens navn. Konstruktøren skal i vårt tilfelle hete MorsePulse::MorsePulse.

Vår konstruktør skal ha to argumenter (dvs. to attributter som skal angis når et nytt objekt lages):

  • hvilken GPIO-pinne som skal brukes
  • hvilken morsepulslengde som skal brukes.

GPIO-pinnen lagrer vi i en byte-variabel ved navn ledPin, og hastigheten lagrer vi i en usignert long-variabel ved navn morseSpeed. Vi flytter deretter innholdet i variablene ledPin- og morseSpeed til to nye variabler ved navn _ledPin og _morseSpeed. Grunnen til dette er at vi vil ha tilgang til GPIO-pinnenummeret og morsepulslengden utenfor konstruktøren. Vi kunne ha gitt de nye variablene andre navn, men navngivingskonvensjonene innen APL sier at variablene skal ha samme navn med innledende understrekingstegn.

Del 2/6 av MorsePulse.cpp

Legg merke til at vi ikke har deklarert variablene ennå (og vi kan ikke initiere variabler som ikke har blitt deklarert). Vi hopper derfor tilbake til header-filen (MorsePulse.h) og deklarerer variablene der. Vi kan enten deklarere dem som allmenne variabler eller som private variabler. Disse to variablene skal bare brukes inni MorsePulse-klassen, og vi angir dem derfor som private variabler. MorsePulse-konstruktøren skal derimot være allmenn, slik at den kan kalles opp fra andre skisser.

#ifndef MorsePulse_h
#define MorsePulse_h

#include "Arduino.h"

class MorsePulse {
  public:
    MorsePulse(byte ledPin, unsigned long morseSpeed);

  private:
    byte _ledPin;
    unsigned long _morseSpeed; 
};

#endif

Uferdig version av MorsePulse.h

Tilbake i kildekodefilen (MorsePulse.cpp) er det på tide å lage nye funksjoner. Den ene heter shortPulse og brukes til å utføre et kort blink. Den andre heter longPulse og brukes til å utføre et langt blink. Begge funksjonene har et argument, pulses, som angir hvor mange ganger et kort eller langt blink skal utføres.

Vi bruker variabelen _morseSpeed som referanse for hvor lenge en kort eller lang puls skal være. Hvis _morseSpeed settes til 200 ms, vil et kort blink være 200 ms langt og et langt blink 600 ms langt (_morseSpeed * 3).

Del 3/6 av MorsePulse.cpp

Ifølge morsespråket skal bokstaver atskilles med en pause som tilsvarer tre korte pulser. Hele ord skal skilles med en pause som tilsvarer seks korte pulser. Vi lager derfor to nye funksjoner som gir oss akkurat disse pauselengdene: nextLetter og nextWord.

Del 4/6 av MorsePulse.cpp

Klassens nest siste funksjon gir vi navnet pulseLetter. Den er denne funksjonen som skal oversette alle bokstaver til morsekommandoer. Funksjonen mottar en bokstav som et argument. Deretter går bokstaven gjennom en switch-setning for å finne ut hvilken kombinasjon av morsepulser den tilsvarer. Til slutt kaller vi opp kombinasjoner av shortPulse og longPulse for å få utført blinkingen.

Obs! Du kan laste ned oversettelsen mellom bokstaver og morsekoder fra github.com/kjellcompany. Hvis du skriver av boken (og ikke vil skrive av alt), er det nok å skrive av bokstavene d, e, h, l, o, r, w og mellomrom.

Del 5/6 av MorsePulse.cpp

Klassens siste funksjon gir vi navnet pulseText. Den funksjonen skal dele opp strenger i separate bokstaver og gå gjennom bokstavene etter hverandre via pulseLetter-funksjonen. Siden morse ikke gjør forskjell på små og store bokstaver, begynner vi med å gjøre om alle bokstaver til små bokstaver. Det gjør vi ved å kalle opp metoden toLowerCase. Deretter skriver vi en for-løkke som går gjennom bokstavene i tekststrengen.

Del 6/6 av MorsePulse.cpp

Obs! Legg merke til at text oppfører seg mer som et objekt enn en variabel. Det er fordi String egentlig er en klasse og text er et String-objekt. Text-objektet har blant annet metodene toLowerCase (for å omforme til små bokstaver), length, (for å avgjøre hvor lang strengen er) og charAt (for å få ut tegnet ved en bestemt posisjon).

Alle nye funksjoner må også være med i header-filen. Vi går derfor tilbake til MorsePulse.h-fanen og legger inn funksjonene. PulseLetter og pulseText er to funksjoner som skal kunne kalles opp utenfor selve klassen (allment). Det går imidlertid ikke an å kalle opp shortPulse, longPulse, nextWord eller nextLetter utenfor klassen.

Ferdig versjon av MorsePulse.h

Nøkkelordfil

En nøkkelordfil inneholder en liste over navn på klasser og funksjoner som biblioteket har. Den er nødvendig for at utviklingsmiljøet skal kunne fargeindikere de nye klassene og funksjonene. Et bibliotek fungerer også uten en nøkkelordfil, men nøkkelordfilen er til god hjelp for dem som skal bruke biblioteket.

Uten en nøkkelordfil kan ikke utviklingsmiljøet fargeindikere korrekt.
Med en nøkkelordfil kan utviklingsmiljøet fargeindikere korrekt.

Vi lager en ny nøkkelordfil ved å åpne et valgfritt tekstredigeringsprogram (for eksempel Notisblokk eller Sublime Text) og lage en ny fil ved navn keywords.txt. Deretter skriver vi inn nøkkelordene etterfulgt av et tabulatortegn og enten KEYWORD1 eller KEYWORD2. Alla klasser skal være KEYWORD1, og alle funksjoner skal være KEYWORD2.

MorsePulse     KEYWORD1
pulseText      KEYWORD2
pulseLetter    KEYWORD2

keywords.txt

Eksempelfil

For at andre programmerere skal kunne bruke biblioteket vårt sender vi med en eksempelfil. Den viser kort og godt hvordan biblioteket kan brukes. Vi skriver derfor en skisse som heter BlinkHelloWorld.ino og gjør akkurat det som navnet antyder: blinker "Hello World" med morsekode.

Vi innleder skissen ved å inkludere MorsePulse-biblioteket (MorsePulse-klassen vår) og lage et nytt MorsePulse-objekt ved navn blinker. Blinker får to attributter: GPIO-pinne 13 og 200 ms. I loop-funksjonen skriver vi deretter at blinker skal blinke teksten "Hello World". Merk at skissen ikke vil fungere før vi har ferdigstilt biblioteket.

// Include the MorsePulse library
#include <MorsePulse.h>

// Create a MorsePulse object on GPIO pin 13 with 200 ms Morse pulses
MorsePulse blinker(13, 200);

void setup() {
  
}

void loop() {

  // Blink "Hello World"
  blinker.pulseText("Hello World");
}

BlinkHelloWorld.ino

Ferdigstille og installere biblioteket

Før biblioteket kan brukes må det ferdigstilles og installeres. Selve ferdigstillingen består av å legge alle filer på riktig sted. Vi begynner med å lage en ny mappe med samme navn som biblioteket. I vårt tilfelle vil vi at biblioteket skal hete MorsePulse, akkurat som klassen. Vi kopierer over MorsePulse.h, MorsePulse.cpp og keywords.txt til den mappen. Deretter lager vi en undermappe ved navn examples. Til den kopierer vi hele BlinkHelloWorld-mappen (inkl. BlinkHelloWorld.ino som ligger i BlinkHelloWorld-mappen).

For å installere biblioteket følger vi instruksjonene i Manuell biblioteksinstallasjon (dvs. at vi flytter hele MorsePulse-mappen til libraries-mappen).

Vi flytter hele MorsePulse-mappen til libraries-mappen.

Her er en oversikt over de ulike filene, filtypene og plasseringene:

FilFilnavnFilplassering
Header-fil klassenavn.h .../klassenavn/
Kildekodefil klassenavn.cpp .../klassenavn/
Nøkkelordfil keywords.txt .../klassenavn/
Eksempelfil eksempelnavn.ino .../klassenavn/examples/eksempelnavn/

Vi kan teste at alt fungerer som det skal, ved å starte utviklingsmiljøet på nytt og finne frem eksempelfilen vår (BlinkHelloWorld.ino), som ligger blant alle andre eksempelfiler på Fil-menyen. Vi kan laste opp skissen direkte til utviklingskortet (uten å koble noe annet til det), siden eksempelfilen bare bruker utviklingskortets innebygde lysdiode.

Forenkle SOS-blinkeren

Nå som vi har et ferdig morsebibliotek, kan vi med noen få kodelinjer bygge en SOS-blinker. Vi kobler knappen til utviklingskortet på samme måte som i While-løkke. Deretter skriver vi en skisse der vi inkluderer både Button- og MorsePulse-biblioteket (Button- og MorsePulse-klassene). Vi lager et nytt Button-objekt (på GPIO-pinne 2) og et nytt MorsePulse-objekt (på GPIO-pinne 13 med 200 ms i morsepulslengde).

Del 1/2 av Sos2.ino

Setup- og loop-funksjonene ser ut som i forrige SOS-eksempel, men koden i while-løkken kan vi forkorte til en eneste linje.

Del 2/2 av Sos2.ino

Forbedre SOS-blinkeren

En enkeltstående lysdiode påkaller ikke spesielt mye oppmerksomhet. Vi vil derfor forbedre SOS-blinkeren med en digital piezosummer som piper samme budskap som lysdioden blinker. I bokens tilhørende komponentpakke finnes både en analog og en digital piezosummer (les mer i Analog og digital piezosummer). Den digitale piezosummeren er den med tekst oppå. Vi kobler den til i henhold til retningen.

Illustrasjon laget med komponenter fra Fritzing (fritzing.org). CC BY-SA 3.0

Takket være MorsePulse-biblioteket vårt er det veldig enkelt å legge til en digital piezosummer i skissen vår. Det eneste vi må gjøre er å lage et nytt MorsePulse-objekt, som vi denne gangen kaller beeper. Som attributt angir vi GPIO-pinne 10 og 200 ms i morsepulslengde. I while-løkken ber vi deretter beeper om å gjøre akkurat det samme som blinker.

Sos3.ino

Obs! Test gjerne med et annet budskap enn SOS, slik at ingen person i nærheten reagerer på et falskt nødsignal.

Sist endret: 2017-09-08