SSL-protokoll for web-applikasjoner på web.py

 920 total views

SSL (TLS) -protokoll er svært vanlig i bruk for de fleste web-stedene i Internet. Om du lager en web-applikasjon bør du sette deg inn i hvordan du konfigurerer den for bruk med SSL. Om du bruker web.py for å lage web-applikasjoner i Python, får du en veiledning i denne artikkelen.

(c) 16.01.2024 Anders Fongen

Innledning

Det første leddet i en web-adresse (URL) angir hvilken protokoll som brukes. Oftest står det https:// der, sjeldnere http://. Den første formen forteller web-leseren at forbindelsen til web-tjeneren skal bruke SSL-protokoll (også kalt TLS) for å beskytte mot avlytting og forfalskning.

Jeg gir ingen detaljert beskrivelse av SSL her, fordi det finnes andre steder på Internet, og fordi det ikke er av vesentlig betydning å kjenne detaljene i denne protokollen.

SSL gir kryptert overføring av data, slik at andre ikke kan lese innholdet av dataoverføringen om de lytter til kanalen som brukes. IP-adresser til sender og mottaker er derimot ikke kryptert, og kan oppfattes gjennom avlytting.

SSL gir også autentisering av én eller begge parter i dataoverføringen, slik at det er vanskelig å utgi seg for å hete f.eks. www.vg.no uten å presentere et digitalt nøkkelsertifikat som attesterer at det er web-tjenerens virkelige navn.

Mindre brukt, men allikevel viktig, er at SSL også kan autentisere web-leseren ved hjelp av et digitalt sertifikat, som attesterer at brukeren av web-leseren heter f.eks. Anders Fongen. Denne funksjonen i SSL kan erstatte tradisjonell innlogging med brukernavn og passord.

Bruk av sertifikater og nøkler, og hvordan sertifikater kan validere andre sertifikater, er et noe omfattende tema som jeg heller vil behandle i en egen artikkel. Begrepet kalles Public Key Infrastructure, og det finnes mye informasjon om dette på Internet.

Jeg har i en tidligere artikkel vist en web-applikasjon bygget på web.py-rammeverket og programmert i Python. Denne artikkelen vil videreføre det samme fokus på rammeverk og programmeringsspråk, og vise hvordan SSL-protokoll konfigureres i en web-applikasjon bygget på web.py.

Nøkkelpar og sertifikater

For å konfigurere en web-tjener for SSL-protokoll trenger den et nøkkelpar. Det kan utstedes av en kommersiell leverandør, noe som koster en del (fra 50€/år og oppover). Med et slikt nøkkelpar kan din web-tjener autentisere seg overfor alle klienter i Internet. Det er derimot ikke praktisk mulig å be alle brukerne av web-applikasjonen din om å betale et slikt beløp for å oppnå klient-autentisering.

For web -applikasjoner som skal brukes innenfor en gruppe, firma, forening etc., er det hensiktsmessig å selv stå for utstedelsen av nøkkelpar og sertifikater. Alle som skal bruke den aktuelle web-tjeneren må da installere et sertifikat og et nøkkelpar i sin egen web-leser, og jeg skal vise hvordan dette gjøres i Google Chrome. For Firefox er fremgangsmåten ganske lik.

Bruk helst en Linux-maskin som web-tjener, da fungerer den presenterte metoden best. Her følger et skall-skript som bruker programmet openssl til å lage et rotsertifikat (også kalt CA-sertifikat), og nøkkelpar for web-tjeneren og et nøkkelpar for én klient. Skallskriptet er ment som et eksempel som du modifiseres etter eget ønske.

# Generate keys and certificates for OpenVPN
openssl genrsa -out ca_key.pem 2048
openssl genrsa -out server_key.pem 2048
openssl genrsa -out client_key.pem 2048
#  Generate CA certificate
openssl req -new -x509 -days 1000 -key ca_key.pem -out ca_cert.pem \
    -outform PEM -subj '/C=NO/O=FONGEN/CN=CA'
#  Generate Server certificate
openssl req -new -key server_key.pem -out server_csr.pem \
    -subj '/C=NO/O=FONGEN/CN=SSLSERVER' \
    -addext 'subjectAltName = DNS:ssl.fongen.no'
openssl x509 -req -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem \
    -CAcreateserial -addtrust serverAuth -out server_cert.pem \
    -days 1000 -sha256 -copy_extensions copy
openssl verify -CAfile ca_cert.pem server_cert.pem
#  Generate Client certificate
openssl req -new -key client_key.pem -out client_csr.pem \
    -subj '/C=NO/O=FONGEN/CN=AndersFongen'
openssl x509 -req -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem \
    -CAcreateserial -addtrust clientAuth -out client_cert.pem \
    -days 1000 -sha256
openssl pkcs12 -export -inkey client_key.pem -in client_cert.pem \
    -out client_keypair.p12
openssl verify -CAfile ca_cert.pem client_cert.pem
#  Remove unneccesary files
rm *_csr.pem

Når dette skallskriptet kjøres (du blir bedt om å skrive et passord underveis, det skal brukes når du installerer klientens nøkkel i web-leseren) skapes det følgende filer på samme katalog:

  • ca_key.pem – Dette er utstederens private nøkkel og skal aldri kopieres eller sendes til andre
  • ca_cert.pem – Dette er utstederens nøkkelsertifikat som skal installeres både i web-tjeneren og i web-leseren
  • server_key.pem, server_cert.pem – Dette er web-tjenerens private nøkkel og nøkkelsertifikat som som skal installeres der, men ikke gis til web-leserne
  • client_keypair.p12 – Dette er nøkkelen og sertifikatet til én klient, beskyttet med det passordet du nettopp skrev inn.

Installasjon av nøkler og sertifikater i Google Chrome

  • I Google Chrome, klikk på knappen med “tre prikker i vertikal ordning” øverst til høyre
  • Velg så “Settings” fra menyen
  • I listen til venstre, klikke “Privacy and Security”
  • Klikk deretter på “Security”, så på “Manage Certificates”
  • Klikk på fanen “Authorities”, og så på knappen “Import”
  • Fra den fildialogen du nå får se, velg filen “ca_cert.pem”
  • Etter at import-jobben er fullført skal du nå se dette rotsertifikatet i listen. Navnet som vises er “org-FONGEN” dersom du ikke har endret dette i skallskriptet (noe du bør gjøre).

Nå skal klientsertifikatet og nøkkelen installeres.

  • Klikk på “Your Certificates” og deretter på knappen “Import”.
  • Fra fildialogen, velg “client_keypair.p12”.
  • Du må nå skrive inn passordet som du valgte da sertifikatet ble laget for litt siden.
  • Nå skal sertifikatet vises i listen med det navnet som du har valgt å bruke.

Installasjon av nøkler og sertifikater i web-tjeneren

Nå er web-leseren klargjort, og vi skal sette opp web-tjeneren med sine nøkler og sertifikater:

from cheroot.server import HTTPServer
from cheroot.ssl.builtin import BuiltinSSLAdapter
import web, ssl

ssl_adapter = BuiltinSSLAdapter(
	certificate = 'server_cert.pem',
	private_key = 'server_key.pem',
	certificate_chain = 'ca_cert.pem')

#ssl_adapter.context.verify_mode = ssl.CERT_REQUIRED
ssl_adapter.context.verify_mode = ssl.CERT_OPTIONAL
HTTPServer.ssl_adapter = ssl_adapter

urls = ("/.*", "hello")
app = web.application(urls, globals())

class hello:
	def GET(self):
		try:
			cn = web.ctx.env['SSL_CLIENT_S_DN_CN']
		except:
			return "No client certificate available"

		return "Hello, " + cn

if __name__ == "__main__":
	app.run()

Jeg har laget en demonstrasjonsvideo som tar deg gjennom disse stegene enkeltvis, og også hvordan web-tjeneren startes og testes. Du kan se den her:

Demonstrasjon av SSL-autentisering i web.py

Takeaway

Avslutningsvis i denne artikkelen vil jeg påpeke at en del av dette stoffet også er til nytte ved bruk av andre web-rammeverk enn web.py. De fleste web-tjenere har støtte for klientautentisering med SSL, og de konfigurerer dette på forskjellige måter. Men laging av sertifikater og nøkler, samt installasjonen av klientnøkler i web-leseren, er de samme operasjonene, derfor kommer det presenterte skallskriptet til nytte.

CMS – Et lettvekts samarbeidssystem for mindre brukergrupper

 790 total views

Visst har vi Teams og andre skybaserte systemer for å samarbeide gjennom nettet, men vi har også behov for noe enklere for små grupper med veldefinerte samarbeidsmodeller. Vi skal vise et slikt system her, og påpeke hvilke fordeler som følger av et kompakt og fokusert system.

CMS-demonstratoren kan kjøres fra adressen http://hos.fongen.no/cms
Kildekoden i arkivform kan lastes ned fra denne lenken

(c) Anders Fongen, 2024

Innledning

Se for deg en gruppe med noen titalls medlemmer, som skal samarbeide om en fokusert og veldefinert oppgave. F.eks planlegging og utforming av et undervisningsfag, en leteoperasjon etter savnede personer, en oppgave knyttet til kartlegging og etterretning, skriving av sakprosa, en chat.

Slike oppgaver er kjennetegnet ved visse egenskaper og behov:

  • Enkle behov for typografisk formatering av tekst
  • Behov for enkelt å opprette nye dokumenter, og endre på eksisterende
  • Høy tillit mellom deltakerne, med felles fokus og nødvendig kompetanse
  • Behov for raskt å gjøre seg kjent med andres bidrag til en aktivitet
  • Enkelt å fjerne utdatert, irrelevant og distraherende innhold

For slike oppgaver er det kanskje fristende å ta frem et generisk samarbeidverktøy med stort behov for styring og konfigurasjon, med høye kapasitetskrav til utstyr og kommunikasjonskanaler. F.eks. Teams, DocuLive, Itslearning, Canvas, Google Disk. Disse dekker muligens behovet, men ikke nødvendigvis på den mest effektive måte, de kan nelig fremstå som en “skyte spurv med kanoner”-løsning. Dessuten trenger de en internett-forbindelse til en skytjeneste, bare så det er sagt.

Vi skal i denne artikkelen presentere et enkelt samarbeidssystem kalt CMS – Content Management System. CMS kjennetegnes ved følgende egenskaper:

  • Minimalt med programkode, < 300 linjer i Python
  • Bruker markdown syntaks for tekstformatering
  • Web brukergrensesnitt (krever kun en web-leser)
  • Dokumentlås som virker
  • Enkel mekanisme for adgangskontroll
  • Dokumenter kan “oppfriskes” automatisk av web-leseren
  • Dokumenter kan opprettes og redigeres gjennom MQTT-protokoll
  • OpenSource, enkel kode for enkel utvidelse og endring

La oss i resten av dette dokumentet presentere disse egenskapene enkeltvis, og forklare hvordan de støtter aktuelle brukstilfeller. En grundigere presentasjon ligger i selve CMS-systemets demonstrasjonstjener på http://hos.fongen.no/cms.

Agil utviklingsmetodikk og Wiki

Behovene som ble listet opp i innledningen minner mye om egenskaper ved moderne system- og programutvikling, kjent som “agile development”. Gå gjerne via denne lenken for å lese litt om “agile” metoder (“agil” har i mellomtiden rukket å bli et norsk ord med norsk uttale).

For å støtte agilt samarbeid utviklet Ward Cunningham i 1995 en dokumentlagringstjeneste kalt Wiki. Med en Wiki kan enhver bruker opprette, redigere og slette dokumenter, altså et fravær av adgangskontroll som kun fungerer i grupper med felles mål og metode. En Wiki kan skrives kompakt med kun noen titalls programlinjer, men uten en redaktør som kan jobbe som “husmor” er de fleste erfaringer den at dokumentsamlingen med tiden blir en uhåndterlig blanding av nyttige, feilaktige, overlappende og utdaterte dokumenter. Wiki-prinsippet finnes også i Wikipedia, men her er det ansatt husmødre som overvåker redigeringen og opprettelsen av nye sider.

Den positive erfaringen med Wiki-prinsippet at en “90%”-løsning, dvs. 90% av behovene til en samarbeidsgruppe, lar seg løse med enkel og kompakt programmering, mens en 99%-løsning er kanskje 100 ganger større og dyrere. Såkalt “feature creep” i systemspesifikasjonene, dvs. at stadig nye funksjonskrav blir lagt til underveis, er en svært dyr uvane, og en fokusert og nøktern “90%”-løsning kan gi mye bedre effekt av innsatsen.

Overordnet beskrivelse av CMS

Dette er en web-tjeneste programmert i Python over rammeverket web.py, og som lar en gruppe av brukere søke, opprette og redigere dokumenter direkte fra sin web-leser. Den skiller mellom “lese”- og “redigere”-brukere slik at kun sistnevnte kan endre innholdet av dokumentsamlingen.

Brukerkontroll

Skillet mellom de to brukergruppene (“lese” og “redigere”) kan gjøres på mange måter, men for å unngå egne tabeller over brukernavn og passord (vi har nok av passord å huske på allerede) har jeg testet ut to metoder: IP-adresser eller klient-sertifikater:

IP-adresser: Bare klienter med IP-adresser som brukes bak brannveggen gir redigeringstillatelse, f.eks. IP-adresser som begynner med 192.168.x.x. Klienter forespørsler fra Internet vil aldri bruke disse adressene. Klienter tilkoplet via Virtuelle Private Nett (VPN) vil derimot kunne bruke adresser som gir redigeringstillatelse.

Klient-sertifikater: Der hvor det benyttes TLS (https:…) protokoll mellom klienten og tjeneneren kan TLS også sørge for at klientmaskinen må fremvise et nøkkelsertifikat og demonstrere sin private nøkkel for å autentisere seg. Kun de klientene som gjennomfører autentisering får redigeringstillatelse, alle andre kun lesetillatelse. Bruk av klient-sertifikater har den fordel at brukerne blir identifisert individuelt og man kan loggføre den enkelte brukers aktiviteter.

Dokumentorganisering

Dokumentene i CMS er hierarkisk organisert omtrent slik som i et filsystem, men istedetfor filkataloger brukes “mor-dokumenter”. Ethvert dokument kan være “mor” til et antall underordnete dokumenter. De underordnete dokumentene med felles mor kalles “søsken”. Når et dokument vises på skjermen vil både de underordnede dokumentene og søsken-dokumentene vises på et sidepanel. CMS inneholder også en enkel søkefunksjon hvor det kan søkes etter ordforekomster i tittel og innhold.

Dokumentene i CMS ligger lagret i en database, ikke som separate filer. Det åpner for en rekke muligheter for å sortere og søke i dokumenter, etablere reasjoner mellom dokumenter, låse dokumenter under redigering osv. Det åpner også for muligheten for andre programmer å skrive direkte til databasen for f.eks. å opprette dokumenter automatisk. Dette er en viktig egenskap ved CMS som vi skal omtale senere.

Redigering med MarkDown

De fleste er vant til å se en ferdig layout når dokumentet redigeres, på den fom det vil bli presentert for en som leser det. Det finnes andre måter å redigere på hvor layout påføres dokumentet som instruksjoner i tekstformat. Hypertext Markup Language (HTML) er det mest kjente eksemplet, hvor f.eks. teksten “<h2>” ikke vises i web-leseren, men indikerer at påfølgende tekst skal vises som en overskrift. Latex er et velkjent system for fremstilling av vitenskapelige artikler som benytter det samme prinsippet.

En av fordelene med markup er at instruksjonene indikerer intensjonen til forfatteren, ikke nøyaktig hvordan utseendet skal bli. “<h2>” kan resultere i ulik utseende, men alltid i form som indikerer en overskrift. En fargeskriver kan gi overskriften en annen uforming enn en svart-hvitt skriver f.eks.

Markdown er et “slenguttrykk” for et markup-språk som produserer HTML-kodet innhold. Markdown er ofte et enkelt sett av instruksjoner i en kort og enkel form, som f.eks. _underlinjert_ eller **uthevet** skrift. Når dokumentet blir presentert vil disse kodene være erstattet av den ønskede layouten. Wiki-systemene har ofte benyttet en form for markdown ved redigering.

Den viktigste egenskapen ved markdown er derimot at et slikt manuskript føles enkelt og naturlig å lese, også uten omforming av instruksjonene. Her er noen eksempler på tekst skrevet med HTML, Latex og Markdown, for å vise at sistnevnte er lettest å lese. Et dokument som ser slik ut:

Kan skrives med henholdsvis HTML, Latex og Markdown på denne måten:

Det er lett å konstatere fra dette eksemplet at Markdown er lettere å skrive inn, og lettere å lese i manuskriptform. I andre enden finner vi Latex, med kraftige mekanismer for å organisere layout og struktur på dokumentet, men som også er tung å skrive inn og vanskelig å lese i manuskriptform.

Markdown kan behandles av programkode for å f.eks. fremstille HTML kode for fremvisning i en web-leser. Med enkle regler kan Markdown behandles med såkalt regulære uttrykk i mange programmeringsspråk, men det finnes også egne programbiblioteker og ferdige programmer som kan gjøre dette. Dette er “kjedelig” programmeringsarbeid og en jobb som er godt å slippe.

Programmet heter pandoc, og finnes i versjoner for Mac, Linux og Windows. Det finnes også et Python-bibliotek kalt pypandoc som tilbyr et utvalg av pandoc’s muligheter gjort tilgjengelig for et Python-program.

Låsing av dokumenter under redigering

Kan flere brukere redigere samme dokument samtidig? I prinsippet ja, og Google Drive tillater nettopp det, men da må de i fellesskap sørge for at dokumentets innhold forblir meningsfylt og sammenhengende. I de fleste tilfellene er det tilstrekkelig løsning å la kun én bruker redigere, og andre som også ønsker dette, må vente på tur. Microsoft Word har lenge hatt en slik “lås”, som åpnes først når dokumentet lukkes etter bruk.

Dette har aldri fungert godt! Alle som har redigert med Word eller Excel har opplevd at en kollega har gått hjem for dagen uten å lukke dokumentet, som da forblir låst inntil neste dag.

I CMS er det valgt en låsemekanisme som automatisk åpner seg etter en viss tid (5 minutter), eller når dokumentet lukkes etter redigering. Dette er et kompromiss: Det løser problemet med gjenglemte låser, men gir brukeren 5 minutter på å fullføre redigeringen (må da gå ut av redigeringen og inn igjen). Dette skjer ved et triks i databasen som også sikrer at flere ikke kan låse et dokument samtidig. Sjekk kildekoden for å se hvilken SQL-syntaks som brukes.

Overvåking av dokumenter

Alle dokumenter i databasen er påført en “refresh rate”, dvs. et tidsrom mellom hver gang dokumentet skal hentes på nytt av web-leseren. Om “refresh rate” er satt til 10 sekunder, vil endringer i dokumentet (gjort av andre) vises frem i alle web-lesere som ser på dette dokumentet senest etter 10 sekunder. Slik kan f.eks. en situasjonsrapport alltid bli vist med de siste oppdateringene uten at brukerne på gjøre en “reload” av dokumentet.

Denne mekanismen introduserer en nødvendig balanse mellom akseptabel forsinkelse i oppdateringen av andre klienter, og den nettverkstrafikken som oppstår knyttet til den stadige hentingen av dokumenttdata. Settes “refresh rate” til 0 skjer ingen slik oppdatering automatisk.

Bruk av bilder

Bilder kan settes inn i dokumentene som redigeres ved å skrive inn en instruksjon i den løpende teksten: <img src=Pictures/filnavn.jpg>. Dette er den vanlige HTML-kommandoen som brukes for dette formålet, og ekstra parametre for å justere størrelse kan også brukes.

Alle bilder legges på samme katalog (Pictures) på CMS-tjenerens filområde, og nye bilder kan lastes opp i PNG eller JPG-format ved å klikke på en knapp i det høyre sidepanelet. Pass på at bildene får unike filnavn, og bilder som ligger der kan inkluderes i mange dokumenter.

Automatiske dokumenter – Internet of things

Automatiske dokumenter betegner dokumenter som legges inn i databasen uten medvirkning fra brukerne av systemet. Disse dokumentene vises frem som andre dokumenter, og egner seg for å vise data fra miljøsensorer, web-kameraer, mashup fra internettkilder (værmelding, trafikkrapporter, børsdata) osv. Et automatisk dokument kombinert med bruk av “refresh rate” kan vise et situasjonsbilde eller en operasjonell status i tilnærmet sann tid, og oppdateres automatisk.

  • Dokumenter som legges inn av brukere kan ikke overskrives eller endres av denne automatiske prosessen.
  • Automatiske dokumenter kan ikke redigeres eller slettes av brukere

Automatiske dokumenter utvider bruksområdet til CMS kraftig, utover det å være en interaktivt dokumentarkiv. CMS kan dermed inkludere og vise tilstanden i omgivelsene. Dette er en kopling til det som kalles Internet-of-things.

I den foreliggende programkoden finnes modulen PubSubAdapter.py som kopler automatiske dokumenter til Publish-Subscribe distribusjon og MQTT-protokoll. På denne måten kan CMS være en del av et større nettverk av noder som samarbeider om å behandle sensor-information. Modulen createCMS doc.py gir et eksempel på hvordan MQTT-protokollen kan brukes for å opprette eller endre et CMS-dokument.

Legg merke til at programkoden i PubSubAdapter.py også tillater at tekst bli lagt til i eksisterende dokumenter, ved å søke etter tegnstrengen <!-- Insert --> i det eksisterende dokumentet. Denne tegnstrengen blir da erstattet med det nye innholdet, og dokumentet forøvrig blir bevart.

Egen kjøring, videre utvidelser og eksperimentering

Linken øverst i artikkelen laster ned et tar-arkiv av kildeteksten. Jeg anbefaler å kjøre CMS-tjeneren på en Linux-maskin, da den ikke er testet under Windows.

Last ned kildekoden i tar-format fra lenken i toppen av artikkelen, og hent frem cms.py i en editor. Du må se over definisjonene i begynnelsen av filen, spesielt om verdien av innerIP svarer til IP-adressene i ditt lokalnett.

Om du vil at CMS skal være tilgjengelig for omverdenen må du konfigurere routeren din med port forwarding, og kanskje satt opp dynamisk dns-navn. Dette omtales ikke her.

Før du starter CMS må disse programmene være installert:

  • Programmene pandoc, sqlite3
  • Python-bibliotekene web.py, pypandoc, paho-mqtt

Start så det hele med kommandoen

python3 cms.py 8080

8080 betegner TCP-porten som CMS skal bruke, om du endrer denne til 80 må du huske på å være root-bruker.

CMS har muligens ingen funksjoner som ikke finnes i andre samarbeidssystemer, men har det fortrinn at det er enkelt og kompakt, med kun noen hundre programsetninger. Systemets konstruksjon er dermed lett å forstå, utvide og endre. Det er derfor mitt ønske at de som laster ned og starter opp CMS vurderer utvidelser og nye funksjoner.

Konfigurering av mikrokontroller med WLAN (Raspberry Pico W, ESP32)

 1,074 total views

En mikrokontroller med WLAN-kretser kan betjenes via nettet, og trenger derfor mindre knapper og lamper for betjening og konfigurasjon. Det mest nærliggende er å lage et web-grensesnitt, selv om andre protokoller også kan brukes. Men det å kople seg til et nytt WLAN-nettverk uten å legge navn og passord inn i programkoden krever noen knep som jeg skal beskrive her. Programmeringsspråket som brukes er MicroPython.

Anders Fongen, juni 2023

Innledning

Problemstillingen er som følger: Du har programmert en slik mikrokontroller til å bruke WLAN-forbindelsen for brukerbetjening, hente data fra Internet, eller kommunisere med andre maskiner. WLAN-adapteret må programmeres til å kople seg til et trådløst nettverk innen rekkevidde, og må kjenne navnet og passordet til det.

Det enkleste er å skrive nettverksnavnet (SSID) og passordet inn som verdier i programkoden, Men om du gir fra deg programkoden til andre er disse opplysningene til liten hjelp. Dessuten er det sjelden lurt å skrive passord direkte inn i programkoden.

Denne artikkelen løser dette problemet: Hvordan konfiguere en slik mikrokontroller for å kople seg til det stedlige trådløse nettverket?

For en raskere innføring, gå til slutten av artikkelen for å se et demonstrasjonsvideo.

Overordnet metode

  1. Kontrolleren har en liste over kjente nettverk, med navn og passord, lagret på en fil. Når kontrolleren starter, vil den gå gjennom listen og forsøke å kople seg til ett av nettverkene på denne listen.
  2. Dersom intet nettverk lar seg kople til (kanskje fordi listen er tom) vil kontrolleren sette opp sitt eget WLAN-nettverk med kjent navn og passord. Andre maskiner kan nå kople seg til dette nettverket.
  3. I tilfelle punkt 2, vil kontrolleren også starte opp en web-tjener for å bli konfigurert gjennom en web-leser. Eieren kan så skrive inn navn og passord på WLAN-nettverk som kontrolleren skal kunne kople seg til.
  4. Denne listen av kjente nettverk bli så lagret til en fil og blir hentet frem neste gang kontrolleren startes (punkt 1).

I resten av denne artikkelen vil jeg gå gjennom enkeltdelene i programmet som bruker denne metoden. Den fulle programkoden kan lastes ned og kjøres i kontrolleren for demonstrasjonsformål, men du vil trolig ønske å gjøre dine egne endringer.

Metoden del for del

I de følgende avsnittene vises ikke programmet i sin helhet, men kun de enkeltsetningene som knytter seg til hver enkelt delfunksjon. Du må selv finne ut hvordan disse delene settes sammen, men du kan også laste inn den komplette programkoden og studere den.

Starte WLAN-adaperet og observere eksisterende WLAN

import network
net = network.WLAN(network.STA_IF)
net.deinit() # Found to be useful
net.active(True)
observed_networks = net.scan()

Denne koden setter WLAN-adapteret i “station”-modus, som lar den kople seg til eksisterende nettverk. Den siste setningen scanner omgivelsene og bygger opp en liste med nettverk innen rekkevidde. Denne listen inneholder navn, mac-addresse, beskyttelse, signalstyrke, radiokanal m.m. for hvert av dem.

Lage en liste over aktuelle WLAN

Programkoden vil så finne ut hvilke av de observerte nettverkene den har navn og passord til (kalt kjente nettverk), og sortere dem etter synkende signalstyrke, slik at den siden kan velge det nettverket med best radiosignal. Listen over kjente nettverk ligger på såkalt JSON-format, og leses inn i en dictionary-variabel med disse setningene:

import json
def load_json(filename):
    f = open(filename,"r")
    d = json.load(f)
    f.close()
    return d

Listen over nettverk som både er observert og kjent, sortert etter synkende signalstyrke, lages med disse programsetningene:

# Merge the list of observed network and known networks,
# the resulting list will contain the known networks which
# are observed at the moment, sorted by descending rssi
def merge(known_list,observed_list):
    new_list = list()
    for (ssid,pw) in known_list:
        for obs in observed_list:
            if ssid == obs[0].decode(): # Network name
                rssi = obs[3] # Signal strength (RSSI)
                new_list.append((rssi,ssid,pw))
                break # Out of inner for loop
    new_list.sort(reverse=True)
    return new_list

Tilkoplingsprosedyre

Resultatet av funksjonen merge() er en liste over nettverk som det er aktuelt å kople seg til. Programmet vil forsøke det sterkeste nettverket først. Programkoden ser slik ut:

import network, time
# Merge the two lists
interesting_networks = merge(known_networks,observed_networks)
for (rssi,ssid,pw) in interesting_networks:
    net.deinit() # Form of reset
    net.active(True)
    attempts = 1
    net.connect(ssid,pw)
    status = net.status()
    if status in [network.STAT_NO_AP_FOUND, \
                  network.STAT_WRONG_PASSWORD]:
        print("Rejected from ", ssid, "reason=",status)
        break
    else:
        while status != network.STAT_GOT_IP:
            time.sleep_ms(500)
            attempts += 1
            if attempts > 20: # More than 10 seconds?
                print("Give up ", ssid, "reason=",status)
                break # Out of while loop
            status = net.status()
        if status == network.STAT_GOT_IP:
            print("Successful connection to ",ssid)
            return True # Successful connect
    # else iterate in the for loop            
return False

Denne koden er litt omstendelig. Fordi det tar litt tid å kople seg til et WLAN, bør du sette en tidsgrense for hvor lenge du vil vente før du gir opp og forsøker neste nettverk på listen. Denne koden sjekker to ganger i sekundet om forbindelsen er opprettet, og gir opp etter 10 sekunder.En tilkopling vil også kreve at nettverket tildeler en IP-adresse, noe som vises med verdien network.STAT_GOT_IP. Kodesetningene er en del av en funksjon som returnerer True dersom en forbindelse ble opprettet, False ellers.

Dersom forbindelsen ble opprettet, er alt bra. Programmet kan nå fortsette sin utføring av anvenderprogrammet, sette opp socket og forbindelser etc. Dette ligger bak horisonten for denne artikkelen.

Sette opp en konfigurasjonstjener

Dersom det ikke lot seg gjøre å kople seg til et WLAN, kan programmet nå starte en konfigurasjonstjener som gjøre det mulig å redigere listen over kjente nettverk. Det krever at vi lar mikrokontrolleren sette opp sitt eget WLAN med kjent navn og passord, og starter en web-tjener med en brukerdialog for dette formålet.

Opprette et WLAN er enkelt, det skjer med disse programsetningene:

import network
def make_ap(ssid="RPico-config",pw="123456789"):
    net = network.WLAN(network.AP_IF)
    net.deinit() # Form of reset
    net.config(essid=ssid,password=pw)
    net.active(True)
    while not self.sta_if.active():
        time.sleep_ms(500)
    # Access point now active
    print("Access point active")

Når dette er utført, kan programmet sette opp en socket som tillater at en web-leser kopler seg til. Dette gjøres i det web-tjeneren startes. Her vises programsetningene som setter opp en tjener-socket (sock.bind() og sock.listen()), og lar en web-leser kople seg til (sock.accept()). Deretter leser programmet inn de dataene som web-leseren sender, og nå er vi inne i HTTP-protokollen.

def start_web_server(self,port=80):
    sock = socket.socket()
    sock.bind(('0.0.0.0',port))
    sock.listen() # Accepting at most one pending connection
    print('listening on port', port)
    # Now enter main loop. Accept connections and process http requests
    while True:
        try:
            print("Waiting for incoming connection")
            client_socket, cl_addr = sock.accept()
            print('Client connection from', cl_addr)

            client_socket.settimeout(1) # Set socket timeout 1 sec
            request = ''
            try:
                while True:
                    buf = client_socket.recv(1024).decode()
                    request = request + buf
            except OSError:
                pass
            if request.startswith("GET / "):
                response = self.get_response()
                response_code = "200 OK"
            elif request.startswith("POST / "):
                print(request) # Just for debugging
                # Find where the html body starts (two newline chars
                ix = request.find('\r\n\r\n')
                if ix==-1: return # Garbage, quit
                post_parameters = self._parse_parameters(request[ix+4:])
                # Send the post parameters to a HANDLER, which returns
                # a response message
                response = self.post_handler(post_parameters)
                response_code = "200 OK"
            else:
                response_code = "404 Not Found"
                response = '<h1>404 Not Found</h1>'
        except Exception as e:
            response_code = "500 Internal Server Error"
            response = "<h1>500 Internal Server Error</h1>" 
        finally:
            client_socket.send('HTTP/1.0 %s \r\nContent-type:
                                text/html\r\n\r\n'%response_code)
            client_socket.send(response)
            client_socket.close()

HTTP-protokollen er et kapittel for seg, i korthet starter en forespørsel med ordet GET eller POST, etterfulgt av et stinavn til den ressursen som adresseres. I dette tilfellet tillater vi kun adressen “/”, alt annet ignoreres. På de påfølgende linjene i forespørselen kommer mer opplysninger om web-tjeneren og hva den ønsker. De skal vi også se bort fra. Denne delen av forespørselen slutter med en blank linje, uttrykt som '\r\n\r\n'. Etter dette kan det komme opplysninger knyttet til selve web-tjenesten, f.eks. data som legges inn i et skjema, men dette begrenser seg til POST-forespørsel.

Programkoden over viser hvordan den ved en POST-forespørsel trekker ut dataene som knytter seg til web-tjenesten, og leverer dem til en annen funksjon for behandling.

En web-tjener vil behandle forespørselen og sende en respons til web-leseren ved å skrive dataene til en socket. Dette er hva som vises på web-leserens skjerm etter at forespørselen er behandlet, og vil normalt være et resultat av noe databehandling. I dette tilfellet gir GET-operasjonen en respons som er gitt i variabelen get_response, og POST-operasjonen det som returneres fra metoden post_handler(..). Det vil fremgå av den komplette programkoden at get_response inneholder et HTML-skjema som lar brukeren skrive inn data for de kjente WLAN-nettverkene, og post_handler(..) vil produsere en form for bekreftelse på at dataene er blitt lagret på en fil.

Slik vises resultatet av en GET-operasjon: et skjema som skal fylles ut med WLAN-opplysninger

Når brukeren fyller inn det skjemaet som blir vist og sender det med SAVE-knappen vil innholdet sendes til web-tjeneren i form av en POST-operasjon, og dataene vil hentes ut av datastrømmen med funksjonen parse_parameters(). Koden til denne funksjonen, og hjelpefunksjonen unescape() er vist nedenfor:

# Parse a http query string and return named values as a dictionary
def parse_parameters(par_string):
    params = dict()
    for element in par_string.split('&'):
        nv = element.split('=')
        # Unescape %hex coded characters
        unesc = unescape(nv[1])
        params[nv[0]] = unesc
    return params

# Replace e.g. %2f with /
def unescape(s):
    b = bytes(s,'utf-8')
    ix = 0
    ix = b.find(b'%',ix)
    while (ix != -1):
        hx = b[ix+1:ix+3]
        bt = bytes.fromhex(hx)
        b = b.replace(b[ix:ix+3],bt)
        ix = b.find(b'%',ix+1)
    return b.decode()
Resultatet av å trykke “Save” er en POST-operasjon som har dette bildet som resultat.

Jeg tror ikke det er nødvendig med en full beskrivelse av hvordan data fra HTML-skjemaer blir overført, men forsøk gjerne å sette inn noen print-setninger for å studere koden mens den kjører.

Slik programkoden er skrevet vil dataene fra det utfylte skjemaet lagres på en fil i JSON-format. Siden, når mikrokontrolleren starter på nytt, vil den lese innholdet av denne filen og bygge opp en tabell over kjente nettverk som brukes slik det ble beskrevet innledningsvis i denne artikkelen.

Hvilken adresse skal brukes for å kople seg til konfigurasjonstjeneren?

Når din maskin kopler seg til kontrollerens WLAN, bruk disse adressene:

  • Nettverkets navn er “RPico-config” og passordet er “123456789”. Med mindre du skriver noe annet inn i programkoden.
  • Maskinen din får tildelt en IP-adresse ved tilkoplingen, den kan du se i Windows med ipconfig-kommandoen (se bildet nedenfor). Se etter IPv4-adressen som er tildelt ditt trådløse adapter. Noter også IPv4-adressen til “Default Gateway”. Den er sannsynligvis 192.168.4.1, så eksemplet som følger vil vise denne adressen.
  • I web-leseren får du nå kontakt med konfigurasjonstjeneren med URL’en http://192.168.4.1/
Utskriften av “ipconfig” viser at kontrollerens IP-adresse er 192.168.4.1

Single-threaded server

Slik denne programkoden er laget ser du lett at den tar i mot en forbindelse og forespørsel med sock.accept(), og behandler denne forespørselen helt og fullt før neste forbindelse blir akseptert. Dette prinsippet kalles single-threading, og medfører at web-tjeneren gir dårlig ytelse når flere web-lesere bruker tjeneren samtidig. Det har liten betydning i dette tilfellet, fordi det WLAN’et som skapes av mikrokontrolleren kun tillater et par samtidige forbindelser.

Noen kommentarer til det fulle programmet

I denne artikkelen har jeg vist forenklede utdrag av et fullt program, som kan lastes ned herfra. Det kan kjøres slik som beskrevet, men koplingen til et anvenderprogram som vil bruke WLAN-adapteret for sin kommunikasjon må settes inn i programmets linje 210.

Ved å studere programmet i sin helhet kan du få bedre inntrykk av hvordan de enkeltdelene som tidligere er beskrevet, blir knyttet sammen i en samlet kontrollflyt. Dessuten er programmet utformet objekt-orientert, som er en nyttig programmeringsteknikk å beherske.

Programkoden er laget for Raspberry Pico W. Dersom du vil bruke den på en ESP32 krever 5 små endringer i koden, disse er angitt i kommentarsetninger. Søk etter “ESP32” og du finner dem. Send meg en melding om du ikke får det til.

Dersom det er aktuelt å konfigurere andre programparametre, f.eks. nettadresser til bestemte tjenester i Internet, kan de gis verdier med et tilsvarende web-grensesnitt. Jeg anbefaler ikke å bruke denne konfigurasjonskoden til andre formål enn for WLAN-opplysninger. Lag heller en separat web-tjener for de andre parametrene, laget på tilsvarende vis. Python-klassen web-config kan brukes til dette uten endringer.

Heller bruke ferdige biblioteker?

Det finnes ferdige Python-moduler på nettet som gjør deler av denne jobben for oss. Når jeg har valgt å presentere en metode som bare bruker de grunnleggende standardbibliotekene i Python, er det for (1) at denne prosessen skal skape læring, ikke bare en løsning, og (2) standardbiblioteker er skrevet for mange slags anvendelser og tar mye mer plass i minnet enn en skreddesydd løsning slik som denne.

Demonstrasjonsvideo

Her vises hvordan denne konfigurasjonsmetoden fungerer i praksis.

Full programlisting

Den fulle programlistingen for dette eksperimentet kan lastes ned fra denne linken.

Nettradio og MP3-spiller med Raspberry Pico

 983 total views,  1 views today

Ved hjelp av et kretskort som dekoder MP3 til lydsignaler kan vi lage en MP3-spiller eller en Internet-radio. Alt som trengs er litt programmering i Rasberry Pico. Her brukes programmeringsspråket MicroPython.

Anders Fongen, mai 2023

Lyd lagret som MP3 må dekodes før det kan sendes som lydsignaler til en høyttaler. En slik dekoder kan lages i programvare, dersom prosessoren er kraftig nok. Dette går greit i en PC, men en mikrokontroller (f.eks. Arduino, EPS8266, Raspberry Pico) kan ikke påregnes å ha tilstrekkelig datakraft for dette formålet.

Det finnes egne integrerte kretser som utfører MP3-dekoding i maskinvaren, uavhengig av prosessorkraften i kontrolleren, og som lar oss lage en MP3-spiller med billige og strømsparende komponenter. Denne bloggartikkelen vil vise hvordan slike komponenter koples sammen og hvordan programmeringen gjøres.

Komponenter som kan brukes

Mange komponenter kan brukes for et eksperiment likt det som presenteres her. De valgte komponentene er en Raspberry Pico (heretter kalt RPico) og en dekoderkrets kalt VS1053 fra VLSI Solutions.

Oppsett og programmering av RPico blir ikke gjennomgått her, det finnes mange hjelperessurser for dette formålet på nettet. Her vil det vises hvordan RPico og VS1053 koples sammen, og den programmeringskoden som kjører i RPico for å oppnå ønsket virkemåte.

En VS1053 er kun en liten brikke/chip, så for eksperimentformål er det vanlig å bruke et kretskort som også inkluderer støttekretser og tilkoplingspunkter. Som vist på bildet under er det utstyrt med koplingspinner som passer ned i et vanlig prototyp-brett (“breadboard”).

Et eksperimentkort for VS1053

VS1053 er en ganske avansert brikke som i tillegg til å dekode MP3 også dekoder lyd kodet med WAV, WMA, OGG, eller MIDI. Den kan ta opp lyd gjennom en mikrofonport (evt. linjeport) og kode den til OGG m.m. (dog ikke til MP3). Kretskortet som er brukt i denne artikkelen har også en SD-kortleser.

For å ta kretskortet i bruk koples en høyttaler til den ene minijack-porten, mens tilkoplingspinnene koples til pinner på RPico. Gjennom disse ledningene går det signaler for å konfigurere VS1053 for det foreliggende formålet, og for å overføre digitale lydsignaler som skal dekodes.

I ledningene mellom VS1053 og RPico benyttes en overføringsmetode som kalles Serial Peripheral Interface (SPI). Dette er en såkalt seriell metode hvor én og en bit overføres i høyt tempo over en enkel ledning. Fordelen med å benytte en “standardisert” metode er at den kan styres fra maskinvaren slik at det krever mindre programmering, og belaster hovedprosessoren mindre. Dessuten er SPI utformet slik at kontrolleren kan snakke med flere enheter gjennom samme ledning (som vi da kaller en buss). I dette tilfellet kan både SD-kortleseren og dekoderen bruke samme ledningssett.

Ledningsnettet

Tegningen nedenfor viser hvordan RPico og VS1053 kan koples sammen. Her er det mange valgmuligheter, men det må være samsvar mellom hvilke pinner på kontrolleren som brukes, og programvaren som skal styre dem. Det er altså mulig å velge andre pinner på RPico enn hva som foreslås her.

Tilkoplingspunkter på VS1053 og RPico

Bildet over viser navnene som er gitt til pinnene på VS1053. De avviker noe fra de navnene som brukes på databladet, så her kreves litt utprøving for å fortstå hvilke funksjoner de har. Her er fasiten:

VS1053 påtrykkBeskrivelseKoples til
RPico pin
Navn på
RPico
5VDriftsspenning40VBUS
GNDJordforbindelse38GND
CSChip select for SD-kortleseren2GPIO 1
MISOSPI datalinje fra VS10536GPIO 4
SISPI datalinje til VS10535GPIO 3
SCKSPI klokkesignal4GPIO 2
XCSChip select for MP3-dekoderen9GPIO 6
XRESHard reset (aktiv lav)1GPIO 0
XDCSData select (RPico->VS1053)10GPIO 7
DREQData request (VS1053->RPico)11GPIO 8
Sammenkoplingen av VS1053 og RPico

Merk at pinnene CS, XCS, XRES, XDCS er alle “aktiv lav”, dvs. at de skal ha 0 volt for å gi effekt. Dette fremgår også fra programkoden.

Bildet over til høyre viser bruken av en flatkabel med ledninger i forskjellige farger for å gjøre denne sammenkoplingen. Det er lett å gjøre feil i denne prosessen, og fargene var til stor hjelp. Et bilde over pinnene på RPico vises på bildet under. Legg merke til at pinnene MISO, SI og SCK er koplet til RPico på pinnene 4-6, som alle er benevnet SPI0. Om andre pinner brukes til dette formålet er det nødvendig at de koples til samme SPI-kanal (0 eller 1) med betegnelsene RX, TX og SCK.

Kommunikasjon med VS1053

RPico kommuniserer med VS1053 på en av fire måter:

  1. Skriv til et register. Verdiene i registrene konfigurerer VS1053. Programkoden viser hvilke verdier som er nødvendig for eksperimentet i denne artikkelen. Full oversikt over registerverdier finnes i databladet.
  2. Les verdien av et register. Ikke strengt nødvendig i denne artikkelen, men nyttig for å kontrollere at en skriveoperasjon er vellykket.
  3. Sende data. I denne artikkelen vil vi sende MP3-kodet lyd til dekoderen, og det skjer på denne måten.
  4. Motta data. VS1053 kan også kode lyd til digital form, som da kan mottas av RPico. Ikke brukt i denne artikkelen.

Skrive til et register

For å skrive en registerverdi må RPico gjøre følgende:

  • Sette XCS=0, XDCS=1
  • Vente til DREQ==1
  • Bygge en 4 byte streng som starter på x02, deretter fulgt av adressen til registeret, og to bytes med den 16-bits verdien som skal skrives dit.
  • Sende strengen til SPI-kanalen
  • Sette XCS=1

Her ser du Python-koden for funksjonen write_command:

    def write_command(self, address_byte, data_byte1, data_byte2):
        # Set the control pins to indicate a command
        self.xcs.value(0)
        self.xdcs.value(1)

        while not self.dreq.value():
            pass

        # Send the address and data bytes over SPI
        ba = (bytearray([0x02, address_byte, data_byte1, data_byte2]))
        self.spi.write(ba)

        # Turn off XCS signals
        self.xcs.value(1)

Lese en registerverdi

For å lese verdien lagret i et register må RPico gjøre dette:

  • Sette XCS=0, XDCS=1
  • Vente til DREQ==1
  • Bygge en 2 byte streng som starter på x03, deretter følger adressen til registeret.
  • Sende strengen til SPI-kanalen
  • Lese to bytes fra SPI-kanalen, som inneholder verdien av registeret.

Slik:

    def read_register(self, address_byte):
        # Set the control pins to indicate a command
        self.xcs.value(0)
        self.xdcs.value(1)
        while not self.dreq.value():
            pass
        self.spi.write(bytearray([0x03, address_byte]))

        resp = self.spi.read(2)

        self.xcs.value(1)
        return resp

Sende data

Disse stegene er nødvendige:

  • Sette XCS=1, XDCS=0
  • Vente til DREQ==1
  • Sende maksimalt 32 bytes til SPI-kanalen
  • Sette XDCS=1

Slik:

    def write_data(self, data):
        # Set the control pins to indicate data
        self.xcs.value(1)
        self.xdcs.value(0)

        while not self.dreq.value():
            pass
        self.spi.write(data)
        self.xdcs.value(1)

Nødvendige deklarasjoner

Noen programsetninger er nødvendige for å deklarere og initialisere portene, samt importere noen nødvendige biblioteker:

from machine import Pin, SPI
import time
spi_mosi = Pin(3)
spi_miso = Pin(4)
spi_sck = Pin(2)

# Define the pins for the VS1053 module
vs1053_xcs = Pin(6)
vs1053_xdcs = Pin(7)
vs1053_dreq = Pin(8)
vs1053_reset = Pin(0)

# Initialize the SPI bus for the mp3 decoder 
co_spi = SPI(0, baudrate=1000000, polarity=0, phase=0, sck=spi_sck,\\
   mosi=spi_mosi, miso=spi_miso)

Jeg har valgt å legge all håndteringen av VS1053 inn som en klasse i Python, og den blir initialisert med denne koden:

class VS1053:
    def __init__(self, spi, xcs, xdcs, dreq, reset):
        self.xcs = xcs
        self.xdcs = xdcs
        self.dreq = dreq
        self.reset = reset
        self.spi = spi

    def init(self):
        self.xcs.init(Pin.OUT, value=1)
        self.xdcs.init(Pin.OUT, value=1)
        self.dreq.init(Pin.IN)
        self.reset.init(Pin.OUT, value=1)

        # Reset the VS1053 module
        self.reset(vs1053_reset)
        self.write_command(0x0, 0x8, 0x4)
        self.write_command(0x3, 0xe0, 0)    # Clock multiplier
        self.write_command(0xb, 0x30, 0x30) # Volume control, 0-loudest	

    def reset(self,pin_reset): # Hardware reset of VS1053
        self.reset.value(0)
        time.sleep_ms(1)
        self.reset.value(1)

Utover det å opprette instansvariabler, vil koden initialisere GPIO-portene med riktig tilstand, resette VS1053 og så skrive inn noen nødvendige registerverdier. Se seksjon 9.6 i databladet for en full beskrivelse av disse.

Full operasjon av VS1053

Etter konfigurasjon slik som beskrevet over og med de funksjonene for lesing og skriving til VS1053 kan vi nå finne frem MP3-data for å teste at vi får avspilt lyd. MP3-data kan finnes fra mange kilder, men i denne artikkelen vil to muligheter belyses nærmere: (1) Hente MP3-filer fra et SD-minnekort, dvs. filer som du har kjøpt på nettet eller hentet fra CD-plater (kalt “ripping”). (2) Hente MP3-data fra nettet med såkalt “strømming”, som innebærer at lyden spilles av etter hvert som de hentes. Begge alternativer vil bli demonstrert, men metoden med å hente data fra nettet er den enkleste og vil bli vist først.

Internet radio

Raspberry Pico finnes i en “W”-versjon (kalt RPicoW heretter), og den har kretser som kan koples til et trådløst nett og videre til Internet. Med denne versjonen brukt i dette eksperimentet er det relativt enkelt å bruke denne muligheten til å spille av radiostasjoner på Internet.

Her viser vi først programsetningene for å kople RPicoW til et trådløst nett (connect_wifi). Vi trenger som vanlig SSID (nettets navn) og et passord som må legges inn i programmet. Funksjonen connect setter opp en forbindelse til en web-tjener. For det formålet trenger vi en URL som inneholder web -tjenerens navn og angivelse av den MP3-strømmen som ønsket mottatt. MicroPython inneholder ikke ferdiglaget kode for HTTP-protokollen, så funksjonen inneholder også noen kodesetninger som starter den faktiske dataoverføringen.

class netradio:
    def connect_wifi(self,ssid,password):
        self.sta_if = network.WLAN(network.STA_IF)
        self.sta_if.active(True)
        self.sta_if.connect(ssid,password)
        while not self.sta_if.isconnected():
            time.sleep_ms(500)

    def connect(self,radio_IP,radio_URL,tcp_port=80):
        self.sock = socket.socket()
        addr = socket.getaddrinfo(radioIP, tcp_port)[0][-1]
        self.sock.connect(addr)
        request = "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n" % (radioURL,radioIP)
        self.sock.send(request)
        return self.sock
    
    def read_data(self,numbytes):
        return sock.recv(numbytes)

Funksjonen read_data returnerer en såkalt socket, som er en “brønn” som vi kan øse data fra. Den kan brukes som en parameter i VS1053-klassens funksjoner for å hente MP3-data i 32-bytes porsjoner og sende til dekoderen med funksjonen write_data. Funksjonen for å oppnå dette ligger i VS1053-klassen og kalles play_stream. Koden ser slik ut:

    def play_stream(self,socket):
        while True:
            soundpacket = socket.recv(32)
            if soundpacket:
                self.write_data(soundpacket)
            else:
                break
        socket.close()

Hva vi nå trenger for å starte nettradioen er en høyttaler koplet til minijack-utgangen på VS1053, og følgende initialiseringskode i RPicoW:

# Initialize the VS1053 module
vs1053 = VS1053(co_spi, vs1053_xcs, vs1053_xdcs, vs1053_dreq, vs1053_reset)
vs1053.init()

# Start netradio
radioIP = "lyd.nrk.no"
radioURL= "/nrk_radio_klassisk_mp3_h?_hdr=0"

nradio = netradio()
nradio.connect_wifi("WiFi-navn","WiFi-passord")
vs1053.play_stream(nradio.connect(radioIP,radioURL))

Avspilling av MP3 fra SD-kort

Dette kretskortet med VS1053 inneholder også en SD kortleser (ikke alle gjør det), og en tilkoplet RPico (trenger ikke være en RPicoW) kan lese og skrive data til et minnekort. Minnekortet blir en del av filsystemet til RPico, og data leses og skrives gjennom ordinære metoder for filbehandling.

Tilkoplingen av en kortleser bruker også SPI-grensesnittet, og er litt mer omstendelig enn WiFi, så denne delen er spart til slutt, da selve dekoderen nå er testet og kjent.

Kortleseren bruker de samme ledningene til SPI-kommunikasjonen, men trenger sitt eget Chip Select signal. Den finner vi på pinnen CS på kretskortet, og koples til pin 2 (GPIO 1) på RPico. Se illustrasjonen under som viser hvordan SPI-ledningene kan deles av flere tilkoplede enheter.

Deling av SPI-buss på flere enheter. Merk av de har separate SS (Chip select) linjer

Kanaler på samme SPI-buss må initialiseres hver for seg, men en ny deklarasjon trenger ingen parametre når den bruker det samme bussnummeret. Altså slik:

import sdcard, os
# Initialize the SPI bus for the mp3 decoder and the sd card reader
co_spi = SPI(0, baudrate=1000000, polarity=0, phase=0, sck=spi_sck, \\
     mosi=spi_mosi, miso=spi_miso)
sd_spi = SPI(0)

# Chip select for SD card reader
sd_cs = Pin(1, Pin.OUT, value=1)

# Init SD card reader interface
card = sdcard.SDCard(sd_spi, sd_cs)
os.mount(card, '/sd')
print(os.listdir('/sd')) # Prints the files on the root directory

Modulen sdcard blir importert, dette er koden for å styre lesing og skriving på SD-kortet. Kildekoden kan lastes ned via lenken nedenfor, og må plasseres i filsystemet på RPico, f.eks. på /lib-katalogen.

Koden vist ovenfor gjør at filene på SD-kortet er gjort tilgjengelig fra katalogen /sd. Filer med MP3-innhold kan nå spilles av med en ny funksjon som vi legger til VS1053-klassen. Slik:

def play_file(self, filename):
        # Open the file and read the data
        chunk = 8192
        with open(filename, "rb") as f:
            data = f.read(chunk)

            # Loop through the file and send the data to the VS1053
            while data:
                idx = 0
                while True:
                    if idx+32 > len(data):
                        self.write_data(data[idx:])
                        break
                    else:
                        self.write_data(data[idx:idx+32])
                        idx += 32
                data = f.read(chunk)
    

Koden i funksjonen over kan trenge en forklaring: Her leses MP3-dataene i porsjoner på 8192 bytes, som deles opp i pakker på 32 bytes som sendes til VS1053 med write_data-funksjonen. Lesing av 8192 bytes tar mindre tid enn det å spille av 32 bytes, så det oppstår ikke hakking i lyden, noe som skjer dersom dette tallet settes mye høyere. Avspilling av filen /sd/andrea.mp3 kan nå skje med programsetningen

vs1053.play_file("/sd/andrea.mp3")

Brukergrensesnitt, sier du?

Denne artikkelen har som mål å vise hvilken Python-kode som kreves for å spille MP3-data fra nettet eller fra et SD-kort. Dette kan være til hjelp for dem som kjenner til MicroPython-programmering og kan inkludere disse programsetningene i sin egen kode. Den viste programkoden kan i sin helhet lastes ned herfra:

(Filene lastes ned med .pyx på slutten av filnavnet. Endre navnet til .py før bruk)

Skal denne koden brukes i egne programmer bør disse funksjonene legges til:

  1. Feilhåndtering. Alt som kan gå galt, vil trenge kode som fanger opp og behandler feilsituasjonen.
  2. Brukergrensesnitt. Start/stopp/pause i avspilling. Valg av katalog eller spilleliste for serieavspilling. Visning av tittel i et display m.m.

Anders håper at denne artikkelen kan være til nytte, skriv gjerne en kommentar om feil og mulige forbedringer.

Morsetrener på en Micro:Bit

 867 total views

Med dette utstyret kan man trene på sending og mottak av morsesignaler sammen med andre. Hver deltaker trenger en Micro:Bit (version 2), og programvaren finner du nederst på siden.

Her demonstrerer jeg hvordan Micro:Bit kan brukes for å sende og motta morse

Med morsealfabetet kan du sende og motta tekst ved å overføre korte og lange pipesignaler. På 1800-tallet kom telegraf-tjenesten i gang i Norge, fra nord til sør ble ledninger trukket som kunne overføre elektrisk strøm for dette formålet. Den gang fantes ikke radio, og heller ikke elektronikk som kunne overføre et talesignal. Profesjonelle operatører som var trent i bruk av morse sørget for at telegrammer ble sendt og mottatt, skrevet ned på papir med vanlige bokstaver og levert til mottakeren med bud.

Etter hver ble radiosenderen oppfunnet, men fortsatt uten muligheter for å overføre tale. Morsesignaler var også her i vanlig bruk mellom skip og til landstasjoner.

Da radioene ble utviklet til også å overføre tale, fortsatte allikevel morsesignaler å være i vanlig bruk. Det viste seg at et system med pipetoner var lettere å oppfatte når det var dårlig radioforbindelse, og man trengte mindre effekt for å få frem meldingene. En radiosender med mindre effekt er lettere og billigere, og egner seg i bærbart utstyr.

Mens profesjonell bruk av morse mellom skip og landstasjoner ble avviklet i år 2000, er det fortsatt mange radioamatører som bruker morse, og det er egne frekvensbånd som brukes til dette formål. Bruk av morse er

  • morsomt, fordi det krever øvelse
  • lett å kombinere med selvbygget radioutstyr
  • effektivt, signalene når over lange avstander med lav effekt og enkle antenner

Morsetrening

Morsealfabetet er lett å være, det finner du mange steder på nettet. Her ser du en slik tabell hvor de korte pipesignalene er vist som prikker, de lange som streker.

Det er grunner til at du ikke anbefales å pugge morse som prikker og streker, fordi det kommer til å hindre deg i å oppfatte signalene som bokstaver og ord når hastigheten øker. Det er mye bra programvare gratis tilgjengelig som trener deg i nettopp dette.

Men det er også viktig å trene morse sammen med andre, hvor dere kan sende til hverandre på skift og lage en faktisk samtale. Slik trening vil lære dere å bruke de vanligste forkortelsene, be om repetisjon av deler av meldingen ved behov osv. Dessuten er det morsommere å lære noe nytt sammen med andre. Om du allerede er radioamatør med tilgang til en radiostasjon kan du øve med virkelige radiosendere, i motsatt fall må du bruke andre overføringsmekanismer, som f.eks.

  • En internet taletjeneste (Messenger, Zoom, Skype m.fl.)
  • Lydsignaler i luft

I begge disse tilfellene ovenfor trenger du en morsenøkkel og en “piper” som avgir et pipesignal når du trykker ned spaken på morsenøkkelen. Som du ser her er en morsenøkkel ganske dyr, men du kan selvsagt lagt din egen fordi det er bare en enkel elektrisk bryter.

Alt i ett med Micro:Bit v2

Micro:Bit er en mikrokontroller som inkluderer en del brytere, sensorer, radio, lamper og lydgivere og koster ca kr.400,- (version 2 kreves for dette formålet). Den kan programmeres og brukes til mange formål, og vi har laget programvare slik at den kan brukes til morsetrening. Programmet er gratis og kan lastes ned fra denne siden via en link lenger ned.

  • Bruker knappene på kretskortet som morsenøkkel
  • Brukere lydgiver og lamper på kretskortet for å vise/spille morsesignaler
  • Bruker den innebygde radioen for at flere deltakere kan sende og motta signaler
  • En “virkelig” morsenøkkel kan koples til kretskortet om ønskelig

Med Micro:Bit v2 og den nødvendige programvaren har en gruppe med deltakere alt de trenger for å sende og motta morsesignaler over radio (men med kort rekkevidde, opp til ca 10 meter).

Innlasting av programvaren

  1. Last ned filen CWtransceiver.hex
  2. Kople MicroBit til PC med en USB-kabel
  3. Nå vil det vises en ny “disk” i FileExplorer, kalt MICROBIT
  4. Kopiere CWtransceiver.hex til denne “disken” (drag and drop i FileExplorer)

For å teste at programvaren er riktig installert, trykk ned Button A (på venstre side av kretskortet. Da skal du høre en pipetone.

Bruksanvisning for morsetreneren

  1. Du kan bruke treneren alene for å øve sending av morsetegn. Da bruker du Button A for å lage pipelyd.
  2. Om flere Micro:Bit-enheter er i nærheten av hverandre, vil signaler som sendes på en enhet spilles av med pipelyder også på de andre enhetene. Der vil også led-lampene på kretskortet lyse opp i samme takt.
  3. Om du berører touch-sensoren (på forsiden av kretskortet like ved USB-kontakten, den har to prikker med en oval rundt), vil LED-displayet skiftevis vise “P” og “S”. Om du vil sende morse slik som vist ovenfor (kalt “Straight Key”) skal det vises en “S” i displayet. Dersom det står en “P” i displayet vil Button A og Button B utgjøre en såkalt paddle keyer. Dette er en mer effektiv måte å sende morsetegn på, men den krever litt øvelse for å beherske. Demonstrasjonsvideoen i starten viser hvordan den kan brukes.
    * Button A gir en serie med prikker mens den holdes nede
    * Button B gir en serie med streker mens den holdes nede
    * Begge knappene nedtrykket gir en serie med skiftevis prikk og strek
    * Mens Button A holdes nede, kan Button B gis et kort trykk. Da vil streken bli sendt ferdig, deretter en prikk før strekene igjen blir sendt. Tilsvarende gjelder i motsatt retning.
  4. Om du ønsker å endre hastigheten på prikker og streker i “P” (paddle keyer) innstillingen gjør du som følger:
    * Hold Button A nede slik at du hører en serie prikker bli sendt.
    * Snu kretskortet på høykant til høyre (slik at Button A er øverst). Da vises en høyrepil i LED-displayet og du hører at hastigheten på prikkene øker. Rett opp kretskortet når hastigheten er passe. Tilsvarende senkes hastigheten om du snur kretskortet til venstre (da vises en venstrepil).

Communication between DMR and a computer network

 1,874 total views,  2 views today

Anders Fongen, November 2022

Abstract: The integration of a sensor network with services from voice communication, text messaging and Global Positioning System (GPS) creates opportunities for improved situational awareness, better safety for field operators, higher confidence in sensor readings and improved return on equipment investment. Digital Mobile Radio (DMR) has been the choice of communication technology for a series of experiments where these potentials have been investigated. Additional technology components used were mostly inexpensive and open source.

The article was published in the The Sixteenth International Conference on Sensor Technologies and Applications (SENSORCOMM 2022) October 2022, Lisbon Portugal.

Demonstration videos

Below are two demonstration videos from the integration experiment, view these if you are not into reading academic papers:

The first video shows how to integrate a DMR radio into an pub-sub network using the MQTT protocol.

Demonstration video from the integration experiment – pubsub intergation

The second video shows how two MMDVM units can provide routing of IP packets across a DMR radio link.

Demonstration video from the integration experiment – IP routing

Full text of article

Download source code

This link allows you to download the source code in the form of a compressed tar file. Please observe that this is experimental code not meant for production, and:

  • The module gwtest.py is the root module, and the execution starts there
  • You will need to import the module dmr_utils3, paho-mqtt, netifaces, etc.
  • You will need to inspect the source code and modify values for ip-addresses, DMR-ids, UDP port number, MQTT topics etc.
  • You need to configure pi-star so that it connects to the Controller at its IP-address.
  • The code also includes code to route IP-packets over a DMR link. Un-comment this code if necessary, but this will require the program to run in root mode (sudo…)
  • If you make improvements and want to share them with me, you are welcome to contact me. Please accept that I cannot spend much time on advice and support otherwise.

Docker – nettverk og svermer

 609 total views

Anders Fongen, september 2022

Dette er et blogginnlegg som skal diskutere nettverk av Docker-kontainere, hvordan de kan kommunisere gjennom beskyttede interne nett, og hvordan de kan opptre i svermer under styring av en sentral kontrollnode. Forutsetningen for å få godt ubytte av denne teksten er grunnleggende kjennskap til bygging og kjøring av vanlige Docker-komponenter (eng. images)

Vi kan enkelt tenke oss fornuftige anvendelser hvor en Docker-komponent har nytte av å påkalle tjenester fra en annen komponent. Dette er da typisk en komponent som ikke betjenes med en HTML-basert web -dialog, men som benytter maskin-til-maskin kommunikasjon (m2m). Slik kommunikasjon kan gjerne være basert på HTTP-protokoll og benytte programmeringsbiblioteker for dette, men vil ha innholdet kodet med XML eller JSON. Slike komponenter forbinder vi med begrepet Service Oriented Architecture (SOA). Jeg kommer ikke til å komme inn på slik programmering her, men det finnes mengder av læremidler der ute basert på ditt favoritt programmeringsspråk.

Beskyttet nettverk internt i Docker

Når en komponent ønsker å kalle en annen, er det selvfølgelig mulig å utplassere den kalte komponenten slik at den kan kalles fra “utsiden” (evt. hele Internet) og bruke denne utvendige IP-adressen som servicepunkt. Det er lett å tenke seg hvorfor dette ofte ikke er ønskelig: Uvedkommende kan fritt utnytte og sabotere tjenesten med mindre man lager mekanismer for adgangskontroll, noe som strengt tatt ikke burde vært nødvendig.

Docker tilbyr derimot “interne” nettverk som kun er synlig for Docker-kontainere på den samme maskinen og som er koplet til det interne nettverket. I det man skaper et slikt nettverk:
$ docker network create my-internal-network
$ docker network ls
$ docker network inspect my-internal-network

vil det også bli tildelt en subnett-adresse, og forsynes med tjenester for tildeling av adresser fra subnettet (lik DHCP) og tjenester for å finne IP-adresser til de tilkoplede kontainerne (lik DNS). For siden å kople en kontainer til nettverket kan vi velge to metoder:

  1. Kople til nettverket i det kontaineren skapes, f.eks slik:
    $ docker run -p 80:80 --name mycontainer --net my-internal-network image-name

    Med denne metoden vil kontaineren mycontainer være knyttet til kun my-internal-network. Om kontaineren skal være knyttet til flere nettverk, bruk metode nr.2:
  2. Lage kontaineren, kople til nettverket og starte programmet, i tre steg (eksempelvis)
    $ docker create -p 80:80 -name mycontainer image-name
    $ docker network connect my-internal-network mycontainer
    $ docker network connect my-second-internal-network mycontainer
    $ docker network disconnect bridge mycontainer
    $ docker start mycontainer

    Denne listen av kommandoen viser bruken av create, som oppretter en kontainer uten å starte den, network connect som kopler kontaineren til interne nettverk, og disconnect, som kopler fra nettverk. Deretter starter kjøringen av programmet i kontaineren med $docker run.

Det forholder seg nemlig slik at en kontainer som standardinnstilling koples til et nettverk kalt bridge. Alle andre kontainere vil også ha forbindelse til denne, noe som i mange tilfeller ikke er ønskelig. Metode nr.1 vil erstatte bridge-forbindelsen med en forbindelse til my-internal-network. Metode nr.2 vil legge nye forbindelser til eksisterende.

Eksempel 1 – BusyBox

Som eksempel på viste metode nr.2 kan komponenten BusyBox brukes. Den gir et lite Linux-kjøremiljø som vi kan skrive kommandoer til.

Først lister vi opp eksisterende nettverk og skaper to nye:


Vi er interessert i å vite subnet-adressene til disse to nye nettene:


Vi skaper en kontainer med BusyBox-komponenten og knytter den til de to nye nettverkene, i tillegg til bridge:


Nå starter vi kontaineren bb med BusyBox inni, og skriver kommandoen ip a for å se nettverkskonfigurasjonen på komponenten:

Fra denne kommandoen ser vi de tre ethernet-adaptrene eth0, eth1 og eth2 med subnettadresser som tilsvarer nettverkene bridge, my-net-1 og my-net-2. Som nevnt tidligere ville vi trolig koplet fra bridge for å unngå påvirkning fra uvedkommende komponenter.

Eksempel 2 – Trafikk mellom to Docker-komponenter

Det første eksemplet viste kun konfigurasjon av nettverksforbindelser, ikke hvordan vi kan bruke dem. Derfor vil vi nå sette opp komponenten first slik at vi kan kalle den fra busybox med linux-kommandoen wget og vise html-innholdet som first sender. Komponenten first er beskrevet i et tidligere blogginnlegg.


Her brukes kommandoene som er vist som metode nr.1 ovenfor og vi starter henholdsvis first og busybox med forbindelser til my-net-3. I busybox bruker vi kommandoene slik:

  1. ip a for å se at vi har fått et nettverksadapter med en adresse fra subnettet til my-net-3
  2. ping first for å vise at det foreligger en navnetjeneste som kopler navnet first (navnet på kontaineren) til ip-adressen 172.27.0.2, og at det foreligger en virkelig forbindelse dit.
  3. wget first:8080 for å sende et http-forespørsel til 172.27.0.2, port 8080 og lagre svaret på filen index.html.
  4. more index.html for å vise innholdet av denne filen på konsollet.

Vi har altså med dette eksemplet demonstrert at to komponenter kan ha en nettverksforbindelse som er skjermet fra andre komponenter. Det som er verd å huske er:

  • Interne nettverk får automatisk en subnett-adresse.
  • Containere som kopler seg til et internt nettverk får tildelt IP-adresse automatisk.
  • Interne nettverk (unntatt bridge) har en navnetjeneste som returnerer IP-adressen til navngitte containere. Derfor er det lurt å gi containere et navn med --name parameteren.

Av en eller annen grunn er det ingen navntjeneste i nettverket bridge, slik at ping first ikke vil fungere dersom komponentene ønsket å kommunisere over det nettverket.

Docker-compose

En applikasjon vil gjerne bestå av flere Docker-containere som samarbeider og må konfigureres og startes på en kontrollert måte. Enkeltkommandoer som vist i eksemplene ovenfor kan kanskje settes sammen i en kommandofil (.bat, .sh) og kjøres samlet. Dette forutsetter at du har konsoll-adgang til maskinen som skal kjøre applikasjonen, og at ulike maskiner kan forstå denne kommandofilen. Dette er forutsetninger som ikke kan garanteres. Docker-compose vil øke portabiliteten av applikasjoner og forenkle konfigurasjonen, siden alt foregår i én fil.

Derfor kommer Docker-compose inn som et verktøy hvor detaljer vedrørende konfigurasjon og kjøring for alle komponentene i applikasjon kan uttrykkes i én fil. Denne filen har en såkalt YAML-syntaks, som skal vises i eksemplet som følger.

Filen skal hete docker-compose.yml. Som eksempel på utforming skal vi bruke konfigurasjonen i eksempel nr.2 ovenfor. Innholdet i filen er som vist under:

Syntaksen og mulige informasjonselementer i denne YAML-filer (uttales “jammel”) er ganske omfattende, og en oversikt finner du her. Derfor velger jeg heller å demonstrere ett bestemt kjent tilfelle med en typisk struktur: nettverksforbindelse, volumer, eksponerte porter. Elementene under disse to vil være tilsvarende de parametrene vi tidligere ga på kommandolinjen. For first, legg merke til at build: nå må ha et stinavn til der hvor kildefilene til first ligger.

Syntaksen i den viste docker-compose.yml skal forstås som et hierarki der innrykk av teksten viser en “tilhørighet” til linjen ovenfor med kortere innrykk. Her har vi altså tre hovedkategorier: services, volumes, networks. Før en kontainer kan knyttes til et volum eller nettverk må disse først deklareres på denne måten. Under networks finner vi hvilket nett som skal brukes i applikasjonen og hva dette skal hete.

Under services finner vi de to vi har arbeidet med så langt, bb og firsts. For bb kommer det to linjer knyttet til stdin_open og tty, som har å gjøre med at vi ønsker konsolltilgang til denne kontaineren, noe som i mange tilfeller ikke vil være aktuelt. Derfor vil denne viste filen ha de fleste av de egenskapene du i praksis vil trenge.

La oss nå gi denne filen til Docker-compose og se hva som kommer ut av det. Arbeidskatalogen (current directory) må være den som inneholder docker-compose.yml . Oppstart skjer med kommandoen $ docker-compose up:


Nå er både bb og first started i hver sine kontainere, men vi får ingen konsolltilgang til å skrive kommandoer i. Til forskjell fra eksempel nr.2 må vi skrive dette i tilegg:
$ docker exec -it bb sh
Denne kommandoen utfører kommandoen sh i kontaineren som heter bb. Parameteren -it gjør at sh også kommuniserer med konsollet, så vi får en interaktivt Linux-konsoll. Nå kan vi teste at det er kommunikasjon mellom bb og first:


Dette må dog skrives i et annet CMD-vindu, fordi vinduet der vi startet docker-compose er opptatt. Der kan vi derimot lukke applikasjonen og stanse kontainerne ved å trykke ctrl-C noen ganger:


Kommandoen $ docker ps -a viser en liste over kontainerne og at disse er stoppet. Vi kan nå slette én og en kontainer med kommandoen $ docker rm bb first, men en enklere måte er $ docker container prune, som sletter alle inaktive kontainere under ett. Inaktive kontainere kan okkupere en del ressurser i systemet, og det er sunt å rydde regelmessig.

Docker swarms

I et storskala informasjonssystem vil en web-tjeneste kjøre på mange maskiner i parallell. Da oppnår man at maskinene kan fordele kundetrafikken mellom seg for å oppnå høyere total ytelse (kalt lastfordeling), og at noen av maskinene kan falle ut av drift uten at systemet som sådan blir utilgjengelig (kalt fail-over). Denne formen for ressursorganisering krever en sjef (manager), som fordeler forespørsler mellom arbeiderne (workers) og som holder oversikt ettersom maskiner går inn eller ut av drift.

Docker-arkitekturen egner seg godt til å inngå i en slik storskala arkitektur, siden tilstandsløse komponenter kan dupliseres på flere maskiner og utføre nøyaktig den samme tjenesten. Kunder vil se ett eneste servicepunkt (IP-adresse og port) og ikke merke noe til at tjenesten er fordelt på mange maskiner. La oss derfor se på hvordan Docker-komponenter kan organiseres som en Docker swarm:

Bruk helst Linux-maskiner

Selv om kommandoene for Docker-svermer også finnes på Windows-versjonen av Docker-systemet, er det en del egenskaper som simpelthen ikke virker som forventet. Det er derfor å anbefale at et eksperiment med Docker-svermer kun benytter Linux-maskiner. Husk også at Docker-komponenter ikke er arkitekturnøytrale, de kan ikke kjøre både på X86- og ARM-maskintyper. Ikke blande f.eks. Raspberry Pi (ARM) med ordinære PCer (X86) i en Docker-sverm.

Alt foregår fra sjefen

For å bygge opp en sverm må maskinene kunne nå hverandre gjennom et IP-nettverk, det er ikke tilstrekkelig at sjefen kan nå alle arbeiderne, arbeiderne må også kunne kommunisere med hverandre. For først å bygge opp en sverm gjør vi følgende:

  1. Bestem hvilke maskiner som skal delta, skriv ned deres IP-adresser og lag et kart som viser hvem som er sjef og hvem som er arbeidere. Fra konsollet til sjefen, logg inn (med ssh) på alle arbeiderne i separate konsollvinduer. Alle kommandoer som vises som eksempler må kjøres i sudo-modus (skriv sudo bash for komme dit).
  2. På alle maskinene i svermen, sørg nå for at alle kontainere er stanset ($ docker stop ..., $ docker rm ...) og kontroller resultatet med $ docker ps -a.
  3. På sjefens maskin, sjekk om det allerede er en sverm med $ docker node ls. Dersom det finnes en, fjerne dem med kommandoen $ docker swarm leave --force.
  4. På sjefens maskin, opprett en sverm med kommandoen $ docker swarm init. Reponsen kan se omtrent slik ut:
    Swarm initialized: current node (icv8b9vnemxfmym4lb1gbto7f) is now a manager
    To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-0ezzbg9iw2glu6d7xd6c2shjvhadr7zlemwuf4l9besxl2iogl-a1yngnqehgyub0phopuslysly 192.168.2.116:2377
  5. Ta en kopi av den uthevede teksten, og lim den inn i konsollvinduet til alle arbeiderne slik at kommandoen utføres der. De vil nå kople seg til sjefen (til ip-adressen som er vist i kommandoen) og slutte seg til svermen. Nå vil de etterhvert få opprettet kontainere for applikasjonstjenester og motta klientforespørsler.
  6. Fra sjefens konsoll skriv $docker node ls for å kontrollere at alle arbeiderne er kommet inn i svermen.

Utplassering av en tjeneste i svermen

Nå kan sjefen plassere en tjenesten inn i svermen. Dette betyr, som tidligere nevnt, at en applikasjon blir plassert ut på én eller flere av arbeiderne (inkludert sjefen), og en forespørsel fra en klient blir betjent av én av dem.

Hvilken IP-adresse leder inn til den utplassert tjenesten? Svaret er at alle IP-adressene i svermen (altså endepunktet til alle arbeiderne og sjefen) gir adgang til tjenesten, også de arbeiderne som ikke betjener selve tjenesten. Det eksisterer et eget “overlay” nettverk mellom maskinene som fremforhandler fordelingen av arbeidsoppgaver og som videresender forespørsler fra klienter til en kandidat-arbeider.

Kommandoen skal skrives på sjefens konsoll. Sånn kan den se ut:

$ docker service create --name first --replicas 3 --publish 8080:8080 andfon7a/first

Den eneste nye parameteren siden de tidligere eksperimentene er --replicas som angir det maksimale antall arbeidere (inkl. sjefen) som skal laste denne applikasjonen. Legg også merke til at Docker-komponenten ikke må ligge på sjefens egen maskin, den kan hentes fra Docker-hub om nødvendig (og den må da evt. plasseres der på forhånd med $ docker push andfon7a/first).

Med kommandoen $ netstat -ant på alle maskinene i svermen kan man nå konstatere at port 8080 er “åpen”. Med en web-leser kan man kontake én av maskinenes IP-adresse på port 8080 og konstatere at tjenesten utføres korrekt.

For å inspisere tjenesten bruk kommandoen $ docker service inspect --pretty first. For å fjerne den skrives $ docker service rm first.

Opp- og nedskalering: Det opplagt nyttige i svermen er muligheten for å endre kapasiteten ved å øke eller redusere antall maskiner som deltar i en tjeneste. Det gjøres som følger (i dette eksemplet reduseres antallet fra 3 til 2):

$ docker service scale first=2

Konklusjon

Målet med dette blogginnlegget var å gi en kortfattet, men ukomplett, innføring i hvordan man setter opp nettverk og svermer av Docker-komponenter. Det fulle omfanget av muligheter og detaljer er mye større, men er blitt utelatt fordi det er lagt vekt på å vise konkret hvor enkelt det er å sette opp en grunnleggende tjeneste.

Jeg anbefaler først leseren å gjennomføre disse eksemplene på eget utstyr og så gjøre nødvendige endringer etter egen interesse: F.eks. hvordan en sverm kan operere med permanent lagring gjennom en filtjener eller en SQL-database. Slike komponenter er ikke enkle å replikere i en sverm og vil naturlig ligge i egne tjenester, gjerne i frittstående Docker-kontainere med muligheter for å lagre data i volumes.

Utplassering av Docker-komponenter i Microsoft Azure

 926 total views

Anders Fongen, september 2022

Å konstruere Docker-komponenter og utplassere (eng. deploy) dem i en lokal kontainer på egen maskin er en ganske overkommelig oppgave. Mer omstendelig er det å sette slike komponenter i drift i en sky. De store skytjenestene (f.eks. Amazon AWS, Google Cloud og Microsoft Azure) er svært store og komplekse installasjoner som skal kunne betjene kunder med helt ulike behov. De tilbyr alle en web-tjeneste (portal) som lar kunden laste opp egenutviklet programvare og konfigurere samspillet med de tjenestene som skyen tilbyr (f.eks. sikkerhet, backup, navnekataloger, fillagring og SQL-databaser). For alle disse skyene blir denne portalen svært omfattende og omstendelig å bruke. Kvaliteten på dokumentasjonen er dessuten svært varierende.

Jeg har tatt for meg utvikling, utplassering, feilsøking og drift av Docker-komponenter med påfølgende utplassering i Microsoft Azure. Etter vellykket utplassering er tjenesten tilgjengelig på Internet. Dette blogginnlegget vil vise hvordan det kan skje.

Hvorfor er skyen en god idé?

Visst er det mulig å sette opp en webtjener på ditt eget hjemmenett, bestille fast IP-adresse hos Internet-leverandøren, sette opp port forwarding på hjemmeruteren, og gi hjemmeadressen et DNS-navn. Denne prosessen har jeg forøvrig beskrevet i et eget blogginnlegg.

Med et slik løsning må du selv ta hånd om sikkerhet, backup, reserveløsninger ved systembrudd, brukertillatelser, og fremfor alt skalering. En vellykket tjeneste på Internet kan oppnå en svært rask vekst i kundemengden, og dersom du ikke klarer å opprettholde en akseptabel responstid under slike forhold har du mislykkes.

Disse kravene kan skyen innfri, fordi de besitter store datasentraler med høy nettverkskapasitet, og de kan raskt øke kapasiteten på tjenesten din, de kan endog gjøre det automatisk. Personellet i datasentralene er eksperter på skalering, sikkerhet, overvåking og etterforskning, og de har ingen annen jobb enn å passe på at tjenestene leverer stabilt og raskt. Du har andre oppgaver og vil ikke klare å gjøre denne jobben like godt.

Docker

Programvare som skal kjøres som en skytjeneste må skrives etter bestemte regler, og utplasseringen trenger noen opplysninger som må fremskaffes av kunden. Docker er programvare for å lage og kjøre skytjenester. Jeg skal ikke forklare Docker grundig her, fordi det finnes mye informasjon om nettopp dette på nettet:

Det er nødvendig å gå gjennom noen eksempler på bygging og kjøring av Docker-komponenter på lokal maskin: Når Docker-komponenter kjøres på lokal maskin, f.eks. BusyBox, kan de kommunisere med brukeren gjennom konsollet, dvs. skjermen som brukes for å bygge og starte komponenter. Dette er ikke mulig på Azure, hvor komponenten må kommunisere med omverdenen gjennom nettverket (Internet).

Nettverkstrafikk kan omfatte protokoller som ssh, http, mqtt og det meste annet, men komponenten må selv besørge selve protokollen, Azure besørger bare UDP- og TCP porter, samt en brannvegg for å beskytte mot angrep via nettverket. Vi skal demonstrere bruk av Azure med web-komponenter som benytter http-protokoll.

Installasjon av programvare

For å kommunisere med Azure trengs programvaren Docker Desktop, installert på Windows eller Mac (bruk lenken ovenfor). I CMD-konsollet (Windows) kan man nå skrive $ docker for å utføre diverse kommandoer.

I den påfølgende tekst vil jeg bruke “$”-symbolet for å vise en kommando slik den skal skrives inn i CMD-konsollet. Dollartegnet skal altså ikke skrives inn.

For å utplassere Docker-komponenter på Azure trenger du følgende brukerkontoer:

  1. Hos hub.docker.com. Her plasserer du dine komponenter slik at andre kan laste dem ned til seg og bruke dem. Kontoen er gratis.
  2. Hos portal.azure.com. Her foregår selve kontrollen av Azur-aktivitetene. Duu må registrere et betalingskort, men du får også en romslig kvote gratis som du kan eksperimentere med.

Eksperimentprogrammer

To små Python-programmer blir brukt i denne demonstrasjonen: De ene er et enkelt tilstandsløst web-program som adderer to tall og viser resultatet, det andre web-programmet benytter en tekstfil som lagres på et permanent lager. Skillet mellom disse to programmene er hvordan de bruker permanent lager. Programmene ser slik ut:

Summere to tall (tilstandsløst web-program)

first.py:
import web
urls = ('/','index')

class index:
   def GET(self):
      f = open("first.html","r")
      return f.read()
   def POST(self):
      i = web.input()
      if i.a.isnumeric() and i.b.isnumeric():
         return "Svaret er %d" % (int(i.a)+int(i.b))
      else:
         return "Kun numerisk inndata er lovlig"

if __name__ == '__main__':
   app = web.application(urls,globals())
   app.run()

first.html:
<html><body>
  <h1>Addere to heltall</h1>
  <form method='POST'>
     <input name='a', size='4'>
     <input name='b', size='4'>
     <input type='submit' value='finn sum'>
   </form>
</body></html>

Dockerfile:
FROM python:3.8
WORKDIR /usr/src/app
COPY . .
RUN pip3 install web.py
EXPOSE 8080
CMD ["python","./first.py"]

Dette webprogrammet kan startes direkte fra kommandolinjen og tilby en enkel tilstandsløs tjeneste. Vi kan når som helst restarte tjenesten uten å forstyrre klientene. Vi kan lage en Docker-komponent og kjøre den lokalt på vår egen maskin med disse kommandoene (de tre filene må ligge på samme katalog, og katalogen må være arbeidskatalog (current directory):

$ docker build -t first .
$ docker run --name container1 -d -p 8080:8080 first

Nå er denne webtjenesten tilgjengelig på http://localhost:8080/. For å stoppe og fjerne tjenesten (f.eks. før utplassering av en ny programversjon) kan disse kommandoene skrives:

$ docker ps -a
$ docker stop container1
$ docker rm container1

Dele en liten datafil (tilstandsfylt web-program)

third.py:
import web, os

urls = ('/','index')
os.environ['PORT'] = '80'
app = web.application(urls, locals())
filename = "message.txt"
class index:
   def GET(self):
      if not os.path.isfile(filename):
         f = open(filename,"w")
         f.close()
         message = "Ingen melding"
      else:
         f = open(filename,"r")
         message = f.read()
      resp ="<html><body><h2>Din siste melding var: %s"%(message)
      resp += "<p>Skriv inn melding:<form method='POST'><input name='m' size='20'>"
      resp += "<input type='submit' value='Lagre melding'></form></body></html>"
      return resp
   def POST(self):
      i = web.input()
      f = open(filename,"w")
      f.write(i.m)
      f.close()
      raise web.seeother('/')

if __name__ == '__main__':
   app.run()

Dockerfile:
FROM python:3.8
WORKDIR /usr/src/app
COPY . .
RUN pip3 install web.py
EXPOSE 80
CMD ["python","./third.py"]

Dette web-programmet vil ta imot en tekstlinje og lagre den på en fil, som siden kan hentes frem av denne eller andre klienter. En form for datadeling altså. Filen som lagrer tekstlinjen bør bevares også når programmet stopper og starter igjen. Dersom vi kjører third.py som et vanlig Python-program vil filen ligge på vertsmaskinens filsystem, og vil dermed bevares slik vi ønsker.

Dersom vi lager en Docker-komponent som med forrige eksempel, vil også denne tekstlinjen bevares og utveksles mens Docker-komponenten kjører. Dersom Docker-kontaineren stoppes og startes igjen:

$ docker stop container1
$ docker start container1

(gitt at kontaineren er gitt navnet container1 ved docker run – kommandoen), da vil fortsatt tekstlinjen bevares slik vi ønsker. Dersom vi derimot fjerner kontaineren og lager en ny:

$ docker stop container1
$ docker rm container1
$ docker run --name container1 -d -p 80:80 third

Da har tekstlinjen fått det innholdet den hadde da Docker-komponenten ble laget (evt. tom), og endringer etter det tidspunktet er gått tapt. Altså, ikke slik vi ønsker at webtjenesten vår skal fungere.

Docker-komponenter med volumes

For å kunne bevare data også når docker-kontaineren slettes og skapes på nytt, må dataene lagres utenfor kontaineren, i vertsmaskinens filsystem. Dette er mulig å få til gjennom å montere en del av vertsmaskinens filsystem inne i kontainerens. Vi skal først endre én programsetning i third.py:

filename = "/app/message.txt"

og bygger web-tjenesten på lignende måte:

$ docker build -t third .
$ docker run --name container3 -v textdata:/app -d -p 80:80 third

Da oppnår vi nemlig det vi ønsker, nemlig at innholdet på området textdata i vertskapets filområde (i et nærmere bestemt filområde inne i Docker-installasjonen) vises under katalogen /app inne i kontaineren, og data som lagres under /app blir bevart selv når kontaineren slettes og gjenskapes. Dette kan kontrolleres ganske enkelt i et eksperiment.

Merk at dette permanente lageret kan lagre alle slags objekter, ikke bare tekstfiler: Bilder, lyd og selvfølgelig en databasefil fra f.eks. SQLite.

Litt om tilstander i Azure/Docker

Azure, i likhet med andre skyleverandører, lar Docker-komponenter kjøre i omgivelser som støtter stor skala, høy sikkerhet og dynamisk konfigurasjon. Det vil si at web-tjenesten som kjører kan gis et elastisk ressurstilbud ettersom pågangen øker og minker. Slik automatisk skalering skjer gjennom at Docker-komponenter kjører samtidig på mange maskiner i parallell (kalt flere instanser) og fordeler forespørselene mellom seg, og at antallet samtidige maskiner øker og minsker dynamisk.

Med datalagring inne i komponenten, slik vi har demonstrert med third.py, vil ikke en slik skalering kunne finne sted, fordi alle instansene vil ha ulikt datainnhold siden de betjener ulike forespørsler. Nei, skalering krever at Docker-komponenten benytter en separat lagringstjeneste som behandler skrivbare data (som filen message.txt i third.py ovenfor). Kun lesbare data kan fortsatt legges inne i Docker-komponenten dersom mengden ikke er for stor.

Dette skillet mellom brukerbetjening, forretningslogikk og datalager er en populær og velprøvd konstruksjonsteknikk for store informasjonssystemer. Trafikken mot datalageret (kalt back-end) er ofte lavere enn mot front-end (web-tjenesten) og datalageret kan skaleres med andre teknikker enn en front-end. Derfor ser vi at dette skillet mellom utføring og lagring i adskilte tjenester går igjen hos alle skyleverandører.

Utplassering av third.py i Azure

Vi skal hoppe over utplassering av first.py i Azure fordi det skjer på samme måten som med lokal Docker-plattform med noen små endringer, som vil fremgå av forklaringen som følger. Jeg skal gå gjennom utplassering av third.py i Azure, etter at den ene programsetningen er endret (filename = "/app/message.txt). Trinn for trinn er prosessen denne:

  1. Bygge en lokal Docker-komponent. Navnet på komponenten må prefikses med brukernavnet i Docker-hub, som i mitt tilfelle er andfon7a:
    $ docker build -t andfon7a/third .
  2. Plassere komponenten i Docker-hub. Det er nemlig herfra at Azure skal hente den senere
    $ docker login (Her kreves brukernavn og passord til brukerkontoen din)
    $ docker push andfon7a/third
    $ docker logout
  3. Logge inn i Azure og opprette filområdet
    $ docker login azure (Får du feilmelding “Azure lookup failure”, har du feil
    versjon av Docker installert. Bruk nyeste versjon av Docker desktop)
    $ docker context create aci mycontext (velg “create new resource group”)
    $ docker context use mycontext
    $ docker volume create mydata --storage-account andfon7astorage
  4. Lag en kontainer og start komponenten i den. Den lastes inn fra Docker-hub
    $ docker run -p 80:80 --name third --domainname andfon7a -d -v andfon7astorage/mydata:/app andfon7a/third
  5. Sjekk at kontaineren kjører og hvilket DNS-navn den har fått.
    $ docker ps -a
    – Sjekk nå med en web-leser at du får tak i tjenesten med dette DNS-navnet

Microsofts egen infoside om denne prosessen finner du her. Docker sin tilsvarende veiledning finnes her.

For å stoppe/starte/fjerne kontaineren bruker du kommandoene:
$ docker stop third
$ docker start third
$ docker rm third

Starting av en kontainer kan ta lengre tid enn kun kommandoen i konsollet. Bruk $docker ps -a for å se status i oppstartsarbeidet.

Om DNS-registreringen: For bruk av --domainname parameteren gjelder det at DNS-navnet blir registrert på den tildelte IP-adressen med en TTL-verdi på 300 sekunder. Dersom du under f.eks. eksperimentarbeid statid starter containeren på nytt kan du komme i situasjonen hvor DNS-tjenesten fortsatt en stund returnerer den forrige IP-adressen. Om du utelater --domainname parameteren vil $docker ps vise deg IP-adressen i den samme kolonnen , som alltid er riktig.

Huskn

Når du vil igjen jobbe lokalt eller mot Docker-hub:
$ docker logout azure
$ docker context use default

Docker-tjenester fra Amazon AWS og Google Cloud

Amazon AWS og Google Cloud er to konkurrerende skytjenester som i det store bildet har mye til felles. Begge disse kan enkelt støtte tilstandsløse Docker-komponenter, enklest gjennom egne kontrollprogrammer (à la docker) som installeres i Windows-konsollet (cmd).

  • Google Cloud betjenes av programmet Gcloud som installeres på din lokale maskin. Opplastingen skjer direkte fra lokal maskin med commandoen gcloud run deploy, og nødvendige tilleggsopplysninger legges inn i den påfølgende dialogen.
  • For Amazon AWS skjer utplasseringen ved hjelp av en tilleggsfil som inneholder de nødvendige ekstraopplysningene (som ikke ligger i Dockerfile). For en veiledning for dette, sjekk avsnittet “Docker on AWS” på denne websiden.

Ingen av disse to skytjenestene støtter derimot Volumer, slik vi har vist er mulig i Azure. Derimot har begge database-tjenester som kan brukes av Docker-komponenter for lagring. Dette dekker noe av behovet for permanent lagring, men krever også at programvaren i større grad er tilpasset for dette og blir derfor mindre portabel enn hva som er ønskelig. Databasetjenester er dessuten krevende å konfigurere, sammenlignet med et filsystem.

Operativt sambandsnett med DMR

 2,053 total views,  1 views today

Denne artikkelen vil presentere Digital Mobile Radio (DMR) og hvordan et sambandsnett kan utformes og betjenes med denne teknologien.

Anders Fongen, juni 2021

Innledende om sambandsnett

Med begrepet sambandsnett siktes det til kommunikasjonstjenester til støtte for redningsoperasjoner, katastrofearbeid, krigføring, store bygge- og anleggsprosjekter osv. Operasjonen kan være iverksatt av flere samarbeidende organisasjoner med et ledelseselement og et antall lag under denne ledelsen. Operasjoner er dynamiske, både med tanke på organisasjonens sammensetning av personell og fagområder, og med tanke på det geografiske området som omfattes av operasjonen.

Kommunikasjonsveiene i en operasjon er skjematisk vist på Figur 1 og kan inndeles på følgende måte:

  1. Ordregivning omfatter beskjeder om ledelsesbeslutninger som gis nedover i organisasjonshierarkiet til et antall lag.
  2. Situasjonsrapport omfatter beskjeder fra utførende lag til ledelsen som bygger opp et situasjonsbilde. Situasjonsrapporter kan bestå av posisjonsangivelser, personellopplysninger, ressursanmodninger, geografiske opplysninger osv.
  3. Synkronisering er informasjon som utveksles mellom sideordnede elementer i organisasjonen for å støtte nødvendig samordning.
Figur 1 – Kommunikasjonsveier i en sambandsoperasjon

Innad i lagene er det også et mulig sambandsbehov som følger et lignende mønster, men er ikke vist på Figur 1. Et lag vil typisk ha en lagfører som er kontaktpunktet til ledelseselementet.

Ønskede egenskaper ved et sambandsnett

For å gi tilfredsstillende kommunikasjonstjenester til en dynamisk og sammensatt operasjon er det en del krav som et sambandsnett må oppfylle:

  • Kanatilgjengelighet – Når en melding (tale, data) skal sendes langs en av linjene som vist i Figur 1 er det nødvendig at en kanal er tilgjengelig, og at denne kanalen har kapasitet og egenskaper forøvrig for overføringen. Ofte vil kanalen være opptatt, og det er da av betydning at kanalen blir ledig tidsnok til at meldingen oppfyller sin hensikt.
  • Separasjon av meldingstyper – Dersom alle meldinger blir mottatt av alle radioer i operasjonen vil dette forstyrre uvedkommende personell. Det er et mål at mottatte meldinger skal være relevante og nyttige for mottakeren, og ikke bidra til distraksjoner og svekket fokus på oppgavene. For å oppnå dette må ulike meldingstyper gis ulik utbredelse i sambandsnettet.
  • Tilstrekkelig dekningsområde – Meldinger må kunne sendes til alle aktører i operasjonsområdet i henhold til meldingstype. Dette innebærer ikke at alle meldingstyper skal kunne mottas overalt, men kun av aktuelle mottakere av den gitte meldingstypen. Lag trenger f.eks. ikke å være innen rekkevidde for synkroniseringsmeldinger mellom ledelseselementer.
  • Trygghetsfunksjoner – For bedret personellsikkerhet er det ønskelig at det kan formidle nødsignaler, alarmer, og posisjoner. Slike meldinger har hast og må kunne prioriteres i nettet og være gjenstand for særskilt betjening i radioutstyret (alarm-knapp, “dødmanns”-knapp).
  • Sikring mot misbruk – Det er ønskelig at nettet er beskyttet mot inntrengning og avlytting av uvedkommende. Falske meldinger kan forstyrre operasjonen, og meldinger under en redningsoperasjon kan f.eks. inneholde sensitive helseopplysninger. Det er også ønskelig at alle meldinger vises med identifisert avsender.
  • Posisjonsangivelser – Mange radioer har innebygd GPS-mottaker og mulighet for å formidle sin posisjon på radiosambandet. Slike meldinger kan inngå i “flåtestyring” og i trygghetsfunksjoner.

Analog vs. digital transmisjon

Teknologien som skal presenteres i denne artikkelen benytter såkalt digital transmisjon, i motsetning til tradisjonelle radioer med analog (frekvensmodulert) transmisjon. Forskjellen på disse to formene er vist på Figur 2, hvor utstrålt radioenergi blir plottet langs en horisontal tidsakse.

Figur 2 – Analog vs. digital transmisjon
  • Analog transmisjon representerer et lydsignal, hvor amplituden (energinivået) i signalet kan ha et stort antall verdier. Forstyrrelser som oppstår under overføring vil påvirke det mottatte lydsignalet i form av forstyrrelser og støy. Jo lengre avstand det er mellom sender og mottaker jo dårligere vil kvaliteten på det mottatte lydsignalet være.
  • Digital transmisjon representerer en bitstrøm, og radiosignalet vil representere enten en 0-bit eller 1-bit. Forstyrrelser som oppstår under overføring kan i noen utstrekning rettes opp, fordi det er bare to mulige tilstander av signalet. Dette bidrar til at bitstrømmen overføres riktig også gjennom en dårlig radioforbindelse. Bitstrømmen som overføres kan inneholde et digitalisert lydsignal, tekst, posisjon, et digitalisert bilde m.m. Digital transmisjon kan derfor støtte mange ulike meldingsformer.

Figur 3 viser hvordan digital transmisjon presenterer et utmerket lydsignal mens signalet blir svakere, før det brått blir meget dårligere. Analog transmisjon gir et gradvis dårligere lydsignal etter hvert som avstanden øker.

Figur 3 – Oppfattet lydkvalitet for henholdsvis analog og digital transmisjon.
Kilde: REPORT ITU-R M.2474-0

Digital Mobile Radio i hovedtrekk

Digital Mobile Radio (DMR) betegner en spesifikasjon for et digitalt radiosystem, laget av organisasjonen ETSI. Spesifikasjonen er åpen, dvs. at alle kan produsere radioutstyr uten å betale for en lisens, og det finnes derfor DMR-radioer å kjøpe fra mange ulike produsenter som i noen utstrekning kan brukes om hverandre i samme sambandsnett. Andre nøkkelegenskaper ved DMR er:

  • De bruker radiokanaler med båndbredde på 12.5 kHz. Man kan derfor bruke eksisterende FM-kanaler (altså frekvenser som er tildelt FM-samband) uten videre med DMR-radioer. Dette er en stor fordel for organisasjoner som allerede disponerer FM-kanaler til eksisterende sambandsutstyr. De trenger altså ikke søke om å få bruke andre frekvenser.
  • Hver radiofrekvens kan romme to talekanaler ved at de skiftevis sender/mottar i tidsluker på 30 millisekunder, slik som vist på Figur 4. De kan befordre helt uavhengige samtaler, eller gjøre det mulig å lytte og sende samtidig (kalt full dupleks, slik som i et telefonapparat).
  • Alle sendinger er adressert til enten en enkeltradio (til en DMR-id, se nedenfor) eller til en talegruppe, hvor alle medlemmer i talegruppen mottar sendingen.
  • Et sambandsnett kan basere seg på direkte forbindelser, hvor alle radioene er innen radiorekkevidde av hverandre, eller på bruk av repeatere, hvor radioene kommuniserer via en sentral radio på en fjelltopp el. lign.
  • Konkurranse mellom radioprodusentene presser prisene ned. En DMR-radio koster fra kr. 1000 og oppover.
  • Digital transmisjon åpner opp for mange tilleggstjenester, de er presentert i neste avsnitt.
Figur 4 – Tidsluker på en DMR-frekvens gir to uavhengige kanaler

Tilleggsfunksjoner i DMR

Utover arkitekturegenskapene som ble presentert i forrige avsnitt, spesifiserer DMR også en del funksjoner og tilleggstjenester som i varierende grad blir implementert i radioapparatene:

  • Tekstmeldinger – Radioapparater kan formidle korte tekstmeldinger som skrives inn på nummertastaturet og sendes til enkeltmottagere, evt. også til hele talegrupper. I motsetning til SMS i mobiltelefonnettet er det nødvendig at mottakeren befinner seg innen radiorekkevidde da det ikke er noen lagringstjeneste. Avsender kan spesifisere at meldingen skal kvitteres, og kan på den måten få en indikasjon på om meldingen ble levert.
  • Identifikasjon av radioer – Hver DMR-radio er konfigurert med en unik DMR-id som identifiserer avsender av en melding. Dersom radioen er utstyrt med en adresseliste vil radioen vise det tilhørende navnet, ikke id-nummeret. DMR-id kan også brukes for å gjøre direkte oppkall til enkeltradioer. Mange radioer kan enkelt sette sin egen DMR-id, så den er ikke egnet for å beskytte mot meldinger fra falske avsendere, men bidrar til å øke situasjonsforståelsen for de som deltar i operasjonen.
  • GPS-rapportering – mange radioer har en innebygget GPS-mottaker og kan rapportere sin posisjon på flere ulike måter: Som en ekstraopplysning knyttet til hver sending, eller regelmessig sendt til en bestemt adresse. I begge tilfeller kan dette bidra til bedre situasjonsforståelse: Ledelsen kan ha et kart med angitt posisjon for ulike personellfunksjoner, og ved direkte oppkall kan mottakeren få en angivelse av avstand og retning til avsenderen.
  • Alarm- og trygghetsfunksjoner – Radioene kan konfigureres med en alarmknapp, som sender en forutbestemt melding til en bestemt adresse når den trykkes inn. Radioen kan konfigureres til å la andre aktivisere senderen, slik ledelsen kan lytte inn fra radioen uten at den blir betjent. Radioen kan også utstyres med en “lone worker” funksjon: da må operatøren betjene radioen med jevne mellomrom, ellers sendes en alarmmelding. Dette minner om “dødmannsknappen” i visse typer utstyr.
  • Skjerming mot avlytting – Alle med en DMR-radio kan i prinsippet stille inn frekvens, tidsluker og talegrupper og lytte på sambandet. Dersom det er et behov for å beskytte seg mot dette kan man kryptere trafikken. Da må man legge inn kodenøkler i alle radioer, og kanalene settes opp til å benytte disse kodenøklene for sending og mottak. En uvedkommende DMR-radio uten riktige kodenøkler vil oppfatte disse sendingen kun som støy. Kryptering er egentlig ingen del av DMR-spesifikasjonen og radioprodusentene gjør dette på ulike måter. Kryptert trafikk kan ikke påregnes å fungere mellom radioer av ulikt fabrikat.
  • Datatrafikk – DMR-spesifikasjonen inkluderer støtte til overføring av data med IP-protokollen, men de enkle radioene tilbyr ikke noen mekanisme for å kople til en PC og sette opp et datanettverk via DMR-forbindelser. Det er også verd å merke seg at bitstrømmen i én tidsluke er 3600 bits per sekund, som er ganske lite for å støtte f.eks. overføring av bilder. I praksis må man se etter utstyr som kan spre trafikken på begge tidsluker og flere frekvenser, egenskaper som ikke er realistisk å finne i en håndholdt radio.

Separasjon av trafikktyper

Å ha én talekanal for alle funksjonene i en operasjon, der alle hører alt som blir sendt, er en dårlig idé. Personellet vil da måtte høre på en mengde meldinger som ikke vedkommer dem. Resultatet kan bli redusert oppmerksomhet på de mottatte meldingene, og dessuten distraksjon av det pågående arbeidet. Det er også viktig at kanalene ikke er for sterkt trafikkert, av hensyn til at hastemeldingene skal komme raskt frem.

Derfor skal sambandsnettet separere meldinger slik at de sendes til mottagere som har nytte av dem. Figur 1 viser nettopp hvordan de tre ulike veiene i sambandsnettet faller sammen med meldingstyper, og det er fornuftig å separere meldingtrafikken i henhold til disse skillelinjene.

Sambandsnettet har to kanaler for hver frekvens (delt i to tidsluker), og hver kanal kan logisk deles inn av talegrupper. Trafikk kan gå samtidig på kanalene, mens talegruppene deler kanalen mellom seg. Det betyr at mens en kanal sender melding for en talegruppe vil kanalen være opptatt og vil ikke kunne brukes for andre talegrupper. “Talegruppe” er forøvrig ikke noe som kjennetegner kanalen som sådan, men en adresse som påføres meldingen.

Likestilt med talegrupper er mottakeradresse. Dersom en sending påføres en mottakeradresse (i form av DMR-id) vil den kun mottas av denne mottakeren, men kanalen den sendes over er utilgjengelig for alle andre i dette tidsrommet.

Sambandsdesign innebærer å separere trafikktypene på frekvens, tidsluke, og talegruppe i henhold til krav om tilgjengelighet, trafikkvolum og dekningsområde.

Prinsipper for hvilke kanaler som talegrupper kan legges til:

  • Kanaler for trafikk med krav til kort responstid (hastemeldinger) bærer talegrupper med kortvarig trafikk: Anrop, alarmer, GPS-posisjoner
  • Kanaler for trafikk med krav til stort dekningsområde bærer talegrupper med nasjonal/regional trafikk: Myndighetskontakt, sivil/militær, luftfartøyer
  • Kanaler for trafikk med liten utbredelse (kun én repeater) bærer talegrupper med lokal trafikk: Innad i laget, mellom lagfører og kommandoplass

Utvidelse av dekningsområdet med repeatere

DMR-standarden legger stor vekt på bruk av repeatere i sambandsdesign: En repeater er en dobbel-radio som mottar radiosendinger på én frekvens og sender det samme signalet ut på en annen frekvens. Radioapparatene sender og mottar på motsatte frekvenser.

Slik kan alle radioer innen rekkevidde av repeateren kommunisere via repeateren, selv om de ikke er innen rekkevidde for hverandre. Repeateren kan plasseres høyt og fritt i terrenget og på denne måten skape et stort dekningsområde.

Radioer som er utenfor repeaterens dekningsområde kan derimot ikke kommunisere med hverandre, selv om de er innen rekkevidde av hverandre. Dette skyldes at de ikke lytter på den frekvensen de andre sender på. Man gjør seg dermed helt avhengig av at repeateren er innen rekkevidde og i drift.

Slik som beskrevet hittil er dette identisk med en repeater for et FM-samband. For DMR gjelder det samme prinsippet, men ekstrafunksjonene beskrevet ovenfor kommer i tillegg: Trafikken kan krypteres, deles inn i talegrupper, det kan sendes tekstmeldinger, GPS-posisjoner og alarmer. Og repeateren tilbyr to kanaler på frekvensen, basert på tidslukene.

DMR-radioer kan også settes opp med en funksjon kalt “talk around”. Om denne blir aktivert vil radioen gå over til direkte-forbindelse på repeaterens sendefrekvens (dvs. både motta og sende på denne frekvensen). Da kan andre høre dennes sendinger og selv aktivisere talk-around for å svare. Siden kan alle slå av funksjonen for å returnere til vanlig samband via repeateren. Talk-around lar dermed radioene kommunisere seg i mellom utenfor repeaterens dekningsområde, eller dersom repeateren faller ut av drift.

Linkede repeatere

Repeatere kan i tillegg til lokal utsending av mottatte signaler også videresende gjennom et digitalt nettverk til andre repeatere som igjen sender ut på sine frekvenser og sender videre til enda flere repeatere. Et slikt repeater-nett kan være svitsjet, i den forstand at forbindelsene mellom repeaterne kan koples opp og ned etter behov for større eller mindre dekningsområdet for sendingene.

I et digital nett hvor sendingene er knyttet til talegrupper er det selvsagt mulig å selektere talegrupper som skal videresendes til andre repeatere. På denne måten kan talegrupper gis ulik dekningsområde der hvor det er fornuftig.

Tilsvarende kan en repeater i et digitalt nett utveksle sendinger med andre digitale nett med annen teknologi, fordi det er en velegnet oppgave for en datamaskin å konvertere digitale sendinger, inkludert adresser, kontrollsignaler og digitalisert lyd.

Systemer for å linke repeatere er utviklet av utstyrsleverandørene og i form av åpne standarder og protokoller, som f.eks. Brandmeister og TGIF. Disse løsningene tilbyr både linking mellom DMR-repeatere og til repeatere for D-star, Yaesu System Fusion, Allstarlink, Echolink m.fl.

Enda en nyttig egenskap ved DRM er mulighet for at radioapparatene kan “roame” mellom repeatere, dvs. selv finne en repeater med egnet signalstyrke. Dette er ikke mulig ved bruk av FM-samband, der må operatøren selv finne frem til egnede repeaterfrekvenser. Roaming er en egenskap som konfigureres i radioapparatene, repeaterne har ingen rolle i den prosessen.

På Figur 5 vises hvordan “Repeater site 1” betjener stasjonene på venstre side, men også videresender mottatte signaler til “Repeater site 2” via et nettverk. Overføringen gjennom nettverket kan filtrere talegrupper, og konvertere de digitale dataene til andre systemer. Illustrasjonen viser også hvordan “radio-løse” stasjoner (Stasjon B) kan delta i sambandet gjennom ordinære datamaskiner (såkalt Voice over IP, VoIP)

Figur 5 – Prinsipptegning av linkede repeatere

Single Frequency Repeater

En DMR-repeater er en spesialbygget enhet med to radiomoduler og passbåndfiltre for å hindre forstyrrelser mellom dem, med robust kjøling for å tåle lange sendeperioder og en datamaskin som styrer trafikken mellom radiomodulene.

DMR har som kjent to kanaler som vekselvis sender på samme frekvens i tidsluker. Dette er blitt utnyttet til å tillate en annen form for repeaterfunksjon som kun benytter én frekvens. En såkalt Single Frequency Repeater (SFR) vil lytte på tidsluke 1 og sende ut på tidsluke 2. Den skifter altså mellom mottak og sending hvert 30. millisekund, og fordi den aldri sender og mottar samtidig trengs kun én radiomodul, og spesielle passbåndfiltre er ikke nødvendige. Radioapparatene som skal bruke en SFR må stille inn en direktekanal med sending på tidsluke 1. På en direktekanal vil radioapparatene lytte på begge tidslukene, ikke bare nr.2.

Med SFR må alle talegruppene legges i samme tidsluke, så man har mindre kapasitet til rådighet enn for vanlige repeatere, som videresender begge tidslukene.

En interessant egenskap ved SFR er at dersom radioene kommer utenfor dekningsområdet til repeateren (eller at den faller ut av drift), vil radioene uten videre kunne kommunisere dersom de er innen radiorekkevidde av hverandre (i motsetning til ordinære DMR-repeatere, som krever at “talk-around” funksjonen aktiveres). Dette gjelder fordi radioene lytter på begge tidslukene og vil motta sendinger fra andre radioer på tidsluke 1 såvel som fra repeateren på tidsluke 2.

Fordi en SFR ikke er en spesialbygget radio kan en vanlig DMR-radio bli en SFR dersom den har programvare for det. Denne egenskapen, kombinert med at den har kun én kanal, gjør at en SFR tenkes brukt som en improvisert måte å utvide dekningsområdet på ved at en radio settes til SFR-funksjon og bringes opp på en fjelltopp.

Eksempler på radioer med SFR-funksjon er Hytera PD985GMD (pris ca. 10.000,-, krever tilleggslisens) og Anytone D578UV (pris ca. 4200,-). Merk at slike radioer ikke er dimensjonert for kontinuerlig sending (høy duty cycle) med henblikk på batterkapasitet og kjøling.

Figur 6 viser SFR-prinsippet hvor radioen oppe til venstre mottar signal både på tidsluke 1 fra radioen nede til venstre og fra repeateren på tidsluke 2.

Figur 6 – Single Frequency Repeater omgitt av radioer satt til direkte forbindelser

Konfigurasjon av radioene

Det kan inngå et vesentlig antall radioer i en sambandsoperasjon, og det er nødvendig med en effektiv metode for konfigurasjon av dem. Det er f.eks. ikke ønskelig med individuell konfigurasjon av hver enkelt radio gjennom dens betjeningspanel. Andre krav til konfigurasjonen kan være at operatøren ikke skal kunne gjøre endringer selv, men at servicepersonell utfører denne oppgaven i henhold til en besluttet policy.

DMR-radioer konfigureres i hovedsak gjennom et PC-program kalt Customer Programming Software (CPS), hvor alle konfigurasjonsopplysninger legges inn i et omfattende skjema, som f.eks.

  • Radioens DMR-id (nummer)
  • Kanaler (frekvenser og tidsluker) med angivelse av navn, utstrålt effekt, trygghets- og sikkerhetsfunksjoner m.m.
  • Talegrupper, med gjenkjennelige navn
  • Adresselister, for å gjenkjenne avsendere
  • Tilpasning av betjeningsfunksjonene (f.eks. sperre visse funksjoner)
  • Passord for endring av konfigurasjonen
  • Tidsgrenser for automatisk power-off, maksimal sendetid
  • m.m

Slike opplysninger resulterer i en konfigurasjonsfil kalt Code Plug som lastes til radioen med hjelp av CPS. Det blir dermed mulig å lage en Code Plug for hver radio-“rolle” fremfor en for hver radio. Hvert radiofabrikat har sin egen CPS, og den resulterende Code Plug kan heller ikke brukes på andre radiofabrikater.

Dersom flere radiofabrikater inngår i en sambandsoperasjon er det nødvendig å bruke CPS på en slik måte at det ikke oppstår interoperabilitetsproblemer. Mer om det i neste avsnitt.

Figur 7 viser et skjermbilde fra en bestemt CPS (til en TYT MD-2017). Et omfattende sett av opplysninger inngår i en Code Plug og det knytter seg en viss lærekurve til bruken av CPS fordi en del av konfigurasjonsopplysningene dreier seg om “dype” tekniske detaljer i DMR-protokollene.

Figur 7 – Skjermbilde fra et CPS

Radiomarkedet for DMR

DMR er en åpen spesifikasjon, og det finnes Open Source programvare for DMR-protokollene som tillater produksjon av DMR-radioer uten veldig store utviklingskostnader. Resultatet er at det er mange ulike fabrikater på markedet, inkludert billige kinesiske radioer av varierende kvalitet på elektronikken og programvaren.

DMR-spesifikasjonene tillater teknologien brukt i mange brukstilfeller, men de billige radioene retter seg med sine funksjoner mot enkle samband, direktekontakt eller via repeatere. Funksjoner som f.eks. full dupleks tale (for samtrafikk med telefonnettet) og dataoverføring med IP-protokoll finnes ikke i det billige prissegmentet, men er å finne i dyrere “enterprise”-type produkter. Radioprodusentene er også systemleverandører, og ønsker at avansert bruk av DMR skal tilbys gjennom produkter med høyere pris.

Interoperabilitet

Et vesentlig moment med DMR og dens “åpenhet” knytter seg til interoperabilitetsproblemer, dvs. at ulike radioer implementerer funksjoner på ulik måte, og at slike funksjoner bare virker mellom radioer av samme fabrikat. Et eksempel er sikkerhetsfunksjoner, hvor krypteringsfunksjonene ikke virker på tvers av radiofabrikater.

Det krever mye undersøkelser for å kartlegge interoperabilitetsproblemer. Målet må være å komme frem til et felles sett av funksjoner som dekker behovet til sambandsoperasjonen. I dette arbeidet er CPS-programvaren sentral, siden det er der disse detaljene konfigureres.

Sammendrag

DMR er en digital teknologi med en rekke egenskaper som gjør den svært velegnet for sambandsnett. DMR støtter talegrupper, digitale tilleggstjenester og trygghetsfunksjoner. DMR-standarden er åpen og det er mange utstyrsprodusenter å velge mellom.

Dersom man ønsker å kjøpe DMR radioutstyr av flere fabrikater må man være oppmerksom på mulige interoperabilitetsproblemer. Erfaringsmessig er det mange digitale tilleggstjenester som ikke lar seg benytte i et heterogent system.