Etikettarkiv: Programering

Pi väderstation

Senaste 24 timmars temperatur

Temperaturgraf

Min garderobs Raspberry Pi är nu navet in en väderstation också. Dels har jag kopplat ihop den med hjälp av en USB-sladd till min WH1080. med hjälp av pywws läser jag ut temperaturen och skickar denna till wunderground och openweathermap. Dessutom har jag nu lyckats koppla in två Dallas DS18B20 till den. En som sitter i vårt uterum och en som sitter inne i vardagsrummet.

För att få över väderstationen in i rrdtool låter jag pywws uppdatera en text fil varje gång den får ny data, för detta har jag har denna konfiguration i weather.ini:

[live]
text = ['current.txt']
services = ['underground', 'openweathermap']

och en template ”current.txt” med detta i:

#raw#
N:#temp_in "%.1f"#:#hum_in "%d"#:#temp_out "%.1f"#:#hum_out "%d"#:#wind_dir "%d" "U"#:#wind_ave "%.1f"#:#wind_gust "%.1f"#:#rain "%.0f" "U" "x * 10"#:#rel_pressure "%.0f"#

Att skapa rrd-databaserna är ganska stort kommando, och man måste se till att man har samma data i rrd som pywws skriver ned. För att sätta upp den gjorde jag ett litet script, mest för att det underlättade att ändra på saker, mktemprrd.

Jag kör sedan att par script genom crontab för att hålla allt uppdaterat, gettemp scriptet körs var 150:onde sekund, det läser ut data från owfs och textfilen pywws skapat. Samt lägger in detta i rrdtool. Owfs rapporterar data med lite mellanslag runt sig så jag har lite bash-magi för att ta bort dessa. Samtidigt som detta körs mkgraph scriptet som uppdaterar graferna över de senaste 24 timmarna. Sedan en gång i timman körs mkgraph_hourly som skapar grafer över de senaste veckorna.

Efter det sista scriptet kört använder jag rsync, också från crontab, för att kopiera över allt till mitt webb-hotel. Så att man kan se något från nätet.

Testdriven utveckling: Del 5, Refaktorera koden

Vi har nu i del fyra fått en kod som passerar testen, men det är inte vad man skulle kalla för snygg kod. Det är något som nästa del an Test Driven Utveckling hanterar nu är det dags att göra så mycket refaktorering vi känner för. Det man skall tänka på att det är refatorering. Inte lägga in ny funktionalitet, eller ändra på test resultatet. För varje refaktotering steg kan det vara bra att köra testerna igen.

tests.cpp:

class TimeOfDay {
    public:
        TimeOfDay(int hour, int minute) {}
        int hour() { return 12; }
};

timeofday.h:

class TimeOfDay {
    public:
        TimeOfDay(int hour, int minute) {}
        int hour() { return 12; }
};

Om vi tittar på testerna och koden så ser vi att test frågan 12 och svaret 12 är duplicerat. Det vore en ganska enkel refaktoering att ta bort detta. Genom att spara hour värdet på indatat så sliper vi duplicerade värdet från enhets testerna.

class TimeOfDay {
    private:
        int m_hour;
    public:
        TimeOfDay(int hour, int minute) : m_hour(hour) {}
        int hour() { return m_hour; }
};

Kanske man skulle kunna ta bort upprepningen av konstanten 12 också i test koden.

TEST(timeofday, midnighthavehourzero) {
    int hour = 12;
    TimeOfDay test(hour, 0);
    EXPECT_EQ(hour, test.hour());
}

När jag nu sätter mig ned, tittar på koden känner jag att för detta grundläggande test har vi gjort ungefär så mycket som behövs. Jag tycker nog att testa att minuter fungerar också är en bra ide. De alternativ som kommer runt det testen blir nästa korta del.

Testdriven utveckling: Del 4, Mot grönt

Nu när vi har ett test som fallerar på det sätt vi förväntar oss. Är det dags för näste steg i den testdriva processen. Att på enklast möjliga sätt få testen att passera. Vi jobbar alltså mot grönt ljus i test systemet.

Att bara byta ut retur värdet i hour() från 0 till 12 kan se som det mest triviala sättet att få ett grönt ljus.

class TimeOfDay {
    public:
        TimeOfDay(int hour, int minute) {}
        int hour() { return 12; }
};

Så fort vi har ändrat en test är det dags att provköra.

balp@silvara:~/localsrc/tdd-blog$ ( cd build && cmake .. && make && ./testprogram )
-- Configuring done
-- Generating done
-- Build files have been written to: /home/balp/localsrc/tdd-blog/build
[ 33%] Built target gtest
[ 66%] Built target gtest_main
[100%] Built target testprogram
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from timeofday
[ RUN      ] timeofday.midnighthavehourzero
[       OK ] timeofday.midnighthavehourzero (0 ms)
[----------] 1 test from timeofday (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

Som vi ser passerar testen. Denna enkla lösning att bara fuska till svaret är något som är svårt för många programmerare att göra, man vill ta höjd för alla kommande problem, redan från början skriva in kod som löser massa svåra special fall. Man om man håller sig lite till man skriver en test som kör specialfallen blir det oftast en mycket bättre förståelse och testad kod.

Testdriven utveckling: Del 3, Ett inledande test

En kort tillbaka blick till hur man genomför Testdriven utveckling. Det är tre viktiga steg, Skapa en test, Rött, Få koden att klara testen, Grönt, Refaktorera koden. Nu när vi har en miljö där vi kan köra tester, är det dags att skapa vårt första test. Den första viktiga delen  är att titta på problemet. Vad skall vi försöka lösa, vilka krav finns det och så vidare. jag kommer att jobba med ett litet övning exempel. Jag kommer att skapa en enkel klass för tid.

Problemet

Skapa an klass för tid på dyngnet, upplösningen behöver vara i minuter. Klassen skall kunna få ut timmar, minuter samt vilken minut på dygnet det är. Klockan skall klara att addera och subtrahera två tidpunkter.

Första testen

Vi böjar med det enklaste faller, varför klockan 12. och kollar att det går att skapa ett objekt samt få ut timman 12. Medan vi skriver testet börjar vi också konstruera det gränssnitt vi vill ha på klasserna. Jag börjar med att testfilen, tests.cpp som vi skapade i förra delen lägga in ett test.

#include "timeofday.h"
#include "gtest/gtest.h"

TEST(timeofday, midnighthavehourzero) {
        TimeOfDay test(12, 0);
        EXPECT_EQ(12, test.hour());
}

VI har nu skapat ett enkelt gränssnitt, en funktions som returnerar timmen, testen är dock inte klar ännu. Om vi testar att kompilera vår test kod kommer vi att se att den inte bygger.

[100%] Building CXX object CMakeFiles/testprogram.dir/tests.cpp.o
/home/balp/localsrc/tdd-blog/tests.cpp:1:23: ödesdigert fel: timeofday.h: Filen eller katalogen finns inte

För att vi skall veta att testen är det vi tror att den är skall det bygga och kunna köras med det resultat vi förväntar oss. Vi måste alltså skapa oss en klass för tiden, skapa timeofday.h och skriv in följande.

class TimeOfDay {
    public:
        TimeOfDay(int hour, int minute) {}
        int hour() { return 0; }
};
VI kan nu köra våra tester och vi ser att vi har nått det förväntade resultatet.

1: Test command: /home/balp/localsrc/tdd-blog/build/testprogram
1: Test timeout computed to be: 1500
1: Running main() from gtest_main.cc
1: [==========] Running 1 test from 1 test case.
1: [----------] Global test environment set-up.
1: [----------] 1 test from timeofday
1: [ RUN ] timeofday.midnighthavehourzero
1: /home/balp/localsrc/tdd-blog/tests.cpp:6: Failure
1: Value of: test.hour()
1: Actual: 0
1: Expected: 12
1: [ FAILED ] timeofday.midnighthavehourzero (0 ms)
1: [----------] 1 test from timeofday (1 ms total)
1:
1: [----------] Global test environment tear-down
1: [==========] 1 test from 1 test case ran. (1 ms total)
1: [ PASSED ] 0 tests.
1: [ FAILED ] 1 test, listed below:
1: [ FAILED ] timeofday.midnighthavehourzero
1:
1: 1 FAILED TEST

Efter som vi nu har ett rött test fall, är det dags att lämna skapa test lägen och gå in på nästa del, att få testet att passera. Men det blir nästa blogpostning.

Koden för detta exempel finns på github.

Testdriven utveckling: Del 2, En bra byggmiljö

För att fortsätta på ämnet testdriven utveckling tänkte jag gå ned på detaljerna, först kommer jag med lite tankar och tips på hut man skriver bra tester. Kanske det svåraste steget, speciellt svårt är det om man har mycket befintlig kod och få tester. Jag rekommenderar att man skriver testerna med i ett befintligt testharnesk. För att det går oftast snabbare och de finns mycket bra funktionalitet i dem. Skriver man kod i Java är JUnit det självklara valet, i Python, unittest. För C++ finns det lite mer variation, jag har länge jobbat med ccpunit. Men det är mycket jobba att komma igång med det systemet. Det är inflexibelt och inte mycket har uppdaterats på många år. Just nu är mitt favorit system Google Test.

Byggmiljö

Det första man måste göra för att kunna testa är att sätta upp en byggmiljö, jag kör min i Ubuntu 11.10 Oneiric, för C++ behöver jag en kompilator, make, jag vill ha cmake för att det blir så mycket enklare att bygga då. Jag vill alltid ha ett fungerade versions hanterings system så som git. Skall man bygga på ubuntu/debian måste man också se till att man har alla de -dev paket som behövs installerade. Saknar man dessa får man ofta spännande problem. Bygger man på MacOS behöver man bara installera git, cmake och xcode.

Projektet

Börja med att skapa en katalog för projektet, och initiera git för versions kontroll.

balp@silvara:~/localsrc$ mkdir tdd-blog
balp@silvara:~/localsrc$ cd tdd-blog/
balp@silvara:~/localsrc/tdd-blog$ git init
Initialized empty Git repository in /home/balp/localsrc/tdd-blog/.git/

Efter detta lägger vi till Google test som ett git subreposotory. Jag har skapar en mirror av googles svn för att kunna göra det här smidigt på github.com.

balp@silvara:~/localsrc/tdd-blog$ git submodule add git://github.com/balp/googletest.git googletest
Cloning into googletest...
remote: Counting objects: 3489, done.
remote: Compressing objects: 100% (583/583), done.
remote: Total 3489 (delta 2704), reused 3457 (delta 2672)
Receiving objects: 100% (3489/3489), 1.14 MiB | 463 KiB/s, done.
Resolving deltas: 100% (2704/2704), done.

Nu har vi alla grundkomponenter på plats och vi kan sätta igång. Det första vi skall göra när vi programmerar är att skapa en test som fallerar. Låt oss skapa en tom test fil, och editera vår byggfil.

balp@silvara:~/localsrc/tdd-blog$ touch tests.cpp
balp@silvara:~/localsrc/tdd-blog$ vim CMakeLists.txt

CMakeLists.txt behöver ett minimalt cmake projekt som bygger och kör testerna. Skriv in följande i filen.

cmake_minimum_required(VERSION 2.8)
project(BlogExamples)
enable_testing()

add_subdirectory(googletest)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/googletest/include)
add_executable(testprogram tests.cpp)
target_link_libraries(testprogram gtest_main )
add_test(NAME test COMMAND $<TARGET_FILE:testprogram> )

include(CTest)

Bygg och testa med den tomma test filen, jag brukar när jag jobbar från kommando raden sätta samman byggkommandona i ett subshell.

( rm -rf build/ ; mkdir build && cd build && cmake .. && make all test )

Fungerar detta som det skall så kommer man att se något i denna stil.

-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Found PythonInterp: /usr/bin/python2.7
-- Looking for include files CMAKE_HAVE_PTHREAD_H
-- Looking for include files CMAKE_HAVE_PTHREAD_H - found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /home/balp/localsrc/tdd-blog/build
Scanning dependencies of target gtest
[ 33%] Building CXX object googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o
Linking CXX static library libgtest.a
[ 33%] Built target gtest
Scanning dependencies of target gtest_main
[ 66%] Building CXX object googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
Linking CXX static library libgtest_main.a
[ 66%] Built target gtest_main
Scanning dependencies of target testprogram
[100%] Building CXX object CMakeFiles/testprogram.dir/tests.cpp.o
Linking CXX executable testprogram
[100%] Built target testprogram
UpdateCTestConfiguration from :/home/balp/localsrc/tdd-blog/build/DartConfiguration.tcl
Parse Config file:/home/balp/localsrc/tdd-blog/build/DartConfiguration.tcl
UpdateCTestConfiguration from :/home/balp/localsrc/tdd-blog/build/DartConfiguration.tcl
Parse Config file:/home/balp/localsrc/tdd-blog/build/DartConfiguration.tcl
Test project /home/balp/localsrc/tdd-blog/build
Constructing a list of tests
Done constructing a list of tests
Checking test dependency graph...
Checking test dependency graph end
test 1
 Start 1: test
1: Test command: /home/balp/localsrc/tdd-blog/build/testprogram
1: Test timeout computed to be: 1500
1: Running main() from gtest_main.cc
1: [==========] Running 0 tests from 0 test cases.
1: [==========] 0 tests from 0 test cases ran. (0 ms total)
1: [ PASSED ] 0 tests.
1/1 Test #1: test ............................. Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.04 sec

Vi har nu ett bra startläge att börja skriva tester.