środa, 25 marca 2020

Dane Eurostatu nt zgonów/urodzeń

Trzeba coś robić w czasie kwarantanny

## https://b-rodrigues.github.io/modern_R/
## https://gist.github.com/imartinezl/2dc230f33604d5fb729fa139535cd0b3
library("eurostat")
library("dplyr")
library("ggplot2")
library("ggpubr")
## 
options(scipen=1000000)
dformat <- "%Y-%m-%d"

eu28 <- c("AT", "BE", "BG", "HR", "CY", "CZ", "DK",
   "EE", "FI", "FR", "DE", "EL", "HU", "IE", 
   "IT", "LT", "LU", "LV", "MT", "NL", "PL", 
   "PT", "RO", "SK", "SI", "ES", "SE")
eu6 <- c("DE", "FR", "IT", "ES", "PL")

### Demo_mor/ Mortality monthly ### ### ###
dm <- get_eurostat(id="demo_mmonth", time_format = "num");
dm$date <- sprintf ("%s-%s-01", dm$time, substr(dm$month, 2, 3))
str(dm)

## There are 12 moths + TOTAL + UNKN
dm_month <- levels(dm$month)
dm_month

## Only new data
dm28  <- dm %>% filter (geo %in% eu28 & as.Date(date) > "1999-12-31")
str(dm28)
levels(dm28$geo) 

## Limit to DE/FR/IT/ES/PL:
dm6  <- dm28 %>% filter (geo %in% eu6)
str(dm6)
levels(dm6$geo) 

pd1 <- ggplot(dm6, aes(x= as.Date(date, format="%Y-%m-%d"), y=values)) + 
 geom_line(aes(group = geo, color = geo), size=.4) +
 xlab(label="") +
 ##scale_x_date(date_breaks = "3 months", date_labels = "%y%m") +
 scale_x_date(date_breaks = "6 months",
   date_labels = "%m\n%y", position="bottom") +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle("Deaths", subtitle="https://ec.europa.eu/eurostat/data/database (demo_mmonth)")

## Newer data
dm6  <- dm6 %>% filter (as.Date(date) > "2009-12-31")

pd2 <- ggplot(dm6, aes(x= as.Date(date, format="%Y-%m-%d"), y=values)) + 
 geom_line(aes(group = geo, color = geo), size=.4) +
 xlab(label="") +
 scale_x_date(date_breaks = "3 months", date_labels = "%m\n%y", position="bottom") +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle("Deaths", subtitle="https://ec.europa.eu/eurostat/data/database (demo_mmonth)")

ggsave(plot=pd1, file="mort_eu_L.png", width=12)
ggsave(plot=pd2, file="mort_eu_S.png", width=12)
## Live births (demo_fmonth) ### ### ###

dm <- get_eurostat(id="demo_fmonth", time_format = "num");
dm$date <- sprintf ("%s-%s-01", dm$time, substr(dm$month, 2, 3))
str(dm)

## There are 12 moths + TOTAL + UNKN
dm_month <- levels(dm$month)
dm_month

dm28  <- dm %>% filter (geo %in% eu28 & as.Date(date) > "1999-12-31")
str(dm28)
levels(dm28$geo) 

dm6  <- dm28 %>% filter (geo %in% eu6)
str(dm6)
levels(dm6$geo) 

pd1 <- ggplot(dm6, aes(x= as.Date(date, format="%Y-%m-%d"), y=values)) + 
 geom_line(aes(group = geo, color = geo), size=.4) +
 xlab(label="") +
 ##scale_x_date(date_breaks = "3 months", date_labels = "%y%m") +
 scale_x_date(date_breaks = "6 months", date_labels = "%m\n%y", position="bottom") +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle("Births", subtitle="https://ec.europa.eu/eurostat/data/database (demo_fmonth)")

##
dm6  <- dm6 %>% filter (as.Date(date) > "2009-12-31")

pd2 <- ggplot(dm6, aes(x= as.Date(date, format="%Y-%m-%d"), y=values)) + 
 geom_line(aes(group = geo, color = geo), size=.4) +
 xlab(label="") +
 scale_x_date(date_breaks = "3 months", date_labels = "%m\n%y", position="bottom") +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle("Births", subtitle="https://ec.europa.eu/eurostat/data/database (demo_fmonth)")

ggsave(plot=pd1, file="birt_eu_L.png", width=12)
ggsave(plot=pd2, file="birt_eu_S.png", width=12)
## Population (only) yearly ### ### ##
## Population change - Demographic balance and crude rates at national level (demo_gind)
dp <- get_eurostat(id="demo_gind", time_format = "num");
dp$date <- sprintf ("%s-01-01", dp$time)
str(dp)
dp_indic_dic <-  get_eurostat_dic("indic_de")

dp_indic_dic
dp28  <- dp %>% filter (geo %in% eu28 & time > 1999 & indic_de == "JAN")

str(dp28)
dp6  <- dp28 %>% filter (geo %in% eu6)

pdp1 <- ggplot(dp6, aes(x= as.Date(date, format="%Y-%m-%d"), y=values)) + 
        geom_line(aes(group = geo, color = geo), size=.4) +
        xlab(label="") +
        ##scale_x_date(date_breaks = "3 months", date_labels = "%y%m") +
        ##scale_x_date(date_breaks = "6 months", date_labels = "%m\n%y", position="bottom") +
        theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
        ggtitle("Population", subtitle="https://ec.europa.eu/eurostat/data/database (demo_fmonth)")

ggsave(plot=pdp1, file="pdp1", width=12)

wtorek, 24 marca 2020

Dzienne dane dot. wypadków drogowych w Polsce

Na stronie http://policja.pl/pol/form/1,Informacja-dzienna.html udostępniane są dzienne dane dotyczące liczby interwencji, zatrzymanych na gorącym uczynku, zatrzymanych poszukiwanych, pijanych kierujących, wypadków, zabitych w wypadkach, rannych w wypadkach.

Ściągam wszystkie dane:

#!/bin/bash

rm pp.html

for ((i=0; i <= 274; i++)) do 
  if [ ! -f ${i}.html ] ; then
    curl -o ${i}.html "http://policja.pl/pol/form/1,Informacja-dzienna.html?page=${i}" ; 
    grep 'data-label' ${i}.html >> pp.html
    sleep 6
  else 
    grep 'data-label' ${i}.html >> pp.html
    echo done
  fi

done

zamieniam prostymi skryptami na plik CSV, który ma następującą strukturę:

data;interwencje;zng;zp;znk;wypadki;zabici;ranni
2008-12-01;NA;873;344;447;135;1;1

okazuje się że liczba interwencji jest podawana od roku 2018, wcześniej nie była. Nic to wstawiamy NA.

Na przyszłość dane będą aktualizowane w ten sposób, że codziennie (przez odpowiedni wpis w pliku crontab) będzie pobierany plik http://policja.pl/pol/form/1,Informacja-dzienna.html:

#!/usr/bin/perl
use LWP::Simple;

$PP="http://policja.pl/pol/form/1,Informacja-dzienna.html";
$PPBase="pp.csv";

$content = get("$PP");

$content =~ s/\r//g; # dla pewności usuń

@content = split (/\n/, $content);

foreach (@content) { chomp();
  unless ($_ =~ m/data-label=/ ) { next }

  if ($_ =~ m/Data statystyki/ ) { $d = clean($_); }
  elsif ($_ =~ m/Interwencje/ )  { $i = clean($_); }
  elsif ($_ =~ m/Zatrzymani na g/ ) { $zg = clean($_); }
  elsif ($_ =~ m/Zatrzymani p/ ) { $zp = clean($_); }
  elsif ($_ =~ m/Zatrzymani n/ ) { $zn = clean($_); }
  elsif ($_ =~ m/Wypadki d/ ) { $w = clean($_);  }
  elsif ($_ =~ m/Zabici/ )  { $z = clean($_);  }
  elsif ($_ =~ m/Ranni/ ) { $r = clean($_);
    $l = "$d;$i;$zg;$zp;$zn;$w;$z;$r";
    $last_line = "$l"; $last_date = "$d";
    ## pierwszy wpis powinien zawierać dane dotyczące kolejnego dnia
    ## więc po pobraniu pierwszego można zakończyć
    last;
 }
}

### read the database
open (PP, "<$PPBase") || die "cannot open $PPBase/r!\n" ;

while (<PP>) { chomp(); $line = $_; @tmp = split /;/, $line; }

close(PP);

### append the database (if new record)
open (PP, ">>$PPBase") || die "cannot open $PPBase/w!\n" ;

unless ("$tmp[0]" eq "$last_date") { print PP "$last_line\n" }
else {print STDERR "nic nowego nie widzę!\n"}

close(PP);

sub clean  {
 my $s = shift;
 $s =~ s/<[^<>]*>//g;
 $s =~ s/[ \t]//g;

 return ($s);
}

Zaktualizowana baza jest wysyłana na githuba. Tutaj jest: https://github.com/hrpunio/Nafisa/tree/master/PP

Agregacja do danych tygodniowych okazała się nietrywialna

Niektóra lata zaczynają się od tygodnia numer 0 a inne od 1. Okazuje się, że tak ma być (https://en.wikipedia.org/wiki/ISO_week_date#First_week):

If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in W01. If it is on a Friday, it is part of W53 of the previous year. If it is on a Saturday, it is part of the last week of the previous year which is numbered W52 in a common year and W53 in a leap year. If it is on a Sunday, it is part of W52 of the previous year.

Nie bawię się w subtelności tylko tygodnie o numerze zero dodaję do tygodnia z poprzedniego roku.

Sprawdzam czy jest OK i się okazuje że niektóre tygodnie mają 8 dni. W plikach html są błędy:

Błędne daty 2019-10-30 winno być 2019-09-30; podobnie błędne 2019-03-28 (winno być 2019-02-28), 2018-11-01 (2018-12-01), 2018-12-01 (2017-12-01), 2016-04-30 (2016-03-30), 2009-08-31 (2009-07-31). Powtórzone daty: 2016-03-10, 2010-07-25, 2010-01-10 (zdublowane/różne/arbitralnie usuwamy drugi) Ponadto brak danych z następujących dni: 2015-12-04--2015-12-07, 2015-04-17--2015-04-20, 2014-10-02--2014-10-05, 2014-01-23 i jeszcze paru innych (nie chcialo mi się poprawiać starych.)

Teraz jest OK, plik ppw.csv ma nast strukturę:

rok;nrt;interwencje;in;zng;zngn;zp;zpn;znk;znkn;wypadki;wn;zabici;zn;ranni;rn;d1;d7 coś co się kończy na `n' to liczba tego co jest w kolumnie poprzedniej, np zn to liczba dni tygodnia dla kolumny zabici. Generalnie kolumny kończące się na `n' zawierają 7 :-) Kolumna d1 to pierwszy dzień tygodnia a kolumna d7 ostatni.

maxY <- max (d$zabici)
pz <- ggplot(d, aes(x= as.factor(nrt), y=zabici )) + 
 geom_bar(fill="steelblue", stat="identity")  +
 xlab(label="") +
 ggtitle("Wypadki/zabici (Polska/2020)", subtitle="policja.pl/pol/form/1,Informacja-dzienna.html") 

W sumie agregacja jest niepotrzebna, bo można ją zrobić na poziomie R używając funkcji stat_summary:

pw <- ggplot(d, aes(x= week, y=wypadki)) + 
 stat_summary(fun.y = sum, geom="bar", fill="steelblue") +
 scale_x_date( labels = date_format("%y/%m"), breaks = "2 months") +
 xlab(label="") +
 #theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle("Wypadki (Polska/2018--2020)", subtitle="policja.pl/pol/form/1,Informacja-dzienna.html") 

albo najpierw agregując dane a potem wykreślając wykres szeregu zagregowanego. Drugi sposób pozwala na przykład na dodanie linii oznaczających poziomy zagregowanego zjawiska/etykiety słupków w sposób `inteligentny'. Dodajemy etykiety (z numerem tygodnia) tylko dla słupków poniżej/powyżej Q1/Q3:

## agregowanie do danych tygodniowych kolumn ranni, zabici, wypadki
dw <- d %>% group_by ( YrWeek) %>% summarise_at ( vars(ranni,zabici,wypadki), sum )

## Obliczanie mediany i kwartyli
median.zw <- median(dw$zabici)
q1.zw <- quantile(dw$zabici, 1/4) 
q3.zw <- quantile(dw$zabici, 3/4) 

## YrWeekZZ to numer tygodnia, dla tygodni w których liczba wypadków jest typowa
## numer jest pusty (żeby nie był drukowany); taki trick może jest lepszy
dw$YrWeekZZ <- substr(dw$YrWeek,4,5)
dw$YrWeekZZ[ (dw$zabici > q1.zw) & (dw$zabici < q3.zw) ] <- ""

pz.2 <- ggplot(dw, aes(x= YrWeek, y=zabici)) + 
 geom_bar(stat="identity", fill = "steelblue") +
 geom_text(data=dw, aes(label=sprintf("%s", YrWeekZZ), x=YrWeek, y= zabici), vjust=-0.9, size=3 ) +
 geom_hline(yintercept=median.zw, linetype="solid", color = "violet", size=.4) +
 geom_hline(yintercept=q3.zw, linetype="solid", color = "red", size=.4) +
 geom_hline(yintercept=q1.zw, linetype="solid", color = "red", size=.4) +
 xlab(label="rok/tydzień") +
 ylab(label="zabici") +
 scale_x_discrete(breaks=c("18/01", "18/10", "18/20",  "18/30", "18/40",
          "19/01", "19/10", "19/20",  "19/30", "19/40", "20/01", "20/10"))  +
          #  labels = c("/18/01", "18/10", "18/20", "")) ## tutaj niepotrzebne
 ggtitle("Wypadki/zabici (Polska/2018--2020)", 
  subtitle="Linie poziomie: q1/me/q3 (źródło: policja.pl/pol/form/1,Informacja-dzienna.html)") 

niedziela, 22 marca 2020

TP-Link TL-WN722N v2

Stare Pi usiłuję zamienić na kamerę w ogródku:

## co to za wersja Pi?
less /proc/cpuinfo
...
Model : Raspberry Pi Model A Rev 2

Problemem jest zasięg WiFi (od routera do kamery jest jakieś 8 metrów przez szybę, żadnych murów). Do tego Pi ma tylko jedno złącze USB co utrudnia sprawę, bo ujawnia się złośliwość przedmiotów martwych (ZPM): z dwóch moich rezerwowych hubów żaden nie działa z tym konkretnym RPi. Podłączam hub z PCeta--ten działa...

Żeby było lepiej (z łącznością) zakupiłem TP-Link TL-WN722N (v2), ale okazało się że akurat wersja 2 nie jest rozpoznawana przez fabryczny Raspbian. Pech albo ZPM2.

Najpierw usiłowałem zainstalować stosowny sterownik wg wskazówek ze strony https://github.com/lwfinger/rtl8188eu:

git clone https://github.com/lwfinger/rtl8188eu.git
sudo apt-get install raspberrypi-kernel-headers
sudo ln -s /usr/src/linux-headers-$(uname -r) /lib/modules/$(uname -r)/build
cd rtl8188eu/
make all
sudo make install
sudo reboot

Nie działa dalej, a moduł się skompilował, zainstalował i nawet jest ładowany...

Zadziałał ten przepis (https://www.raspberrypi.org/forums/viewtopic.php?t=250911#p1532103):

You can download the driver from http://downloads.fars-robotics.net/wifi-drivers/8188eu-drivers/. Choose the driver that matches the output of command uname -a for the correct kernel version:

uname -a
# Linux aisara 4.19.97+ #1294 Thu Jan 30 13:10:54 GMT 2020 armv6l GNU/Linux
mkdir Temp && cd Temp
wget http://downloads.fars-robotics.net/wifi-drivers/8188eu-drivers/8188eu-4.19.97-1294.tar.gz
tar -zxvf 8188eu-4.19.97-1294.tar.gz
./install.sh

BTW Installing my driver will disable the built in r8188eu driver. To re-enable the built in driver you will need to run the following commands:

sudo rm /etc/modprobe.d/8188eu.conf
sudo rm /lib/modules/4.19.69-v7l+/kernel/drivers/net/wireless/8188eu.ko
sudo depmod -a

Kamera

Miałem już ze starych czasów puszkę z Raspberry w środku, z kamerą przyczepioną do ścianki, zrobioną dziurą na obiektyw i kablem doprowadzającym zasilanie przez piny P2+P6 (też tak można). Zasilacz w domu, poza oknem już 5V. Na wszelki wypadek -- żeby mi kogoś nie zabiło przez przypadek (a nawet czegoś, bo teraz czasy takie że jakby dzika poraziło, to też by była afera) -- wolałem nie ciągnąć 230V z mieszkania. Dlatego też zasilanie jest po zwykłym dwużyłowym kablu a nie przez USB (bo tak mi się wydawało prościej).

Znowu coś nie tak od pierwszego strzału (ZPM3):

mmal: mmal_component_create_core: could not create component 'vc.ril.camera' (1)

Ze starych czasów pamiętam, że problem może być z połączeniem/uszkodzeniem (taśmy na przykład). Zmieniłem kamerę i taśmę na inną. Działa. Musiała być uszkodzone widocznie.

Kamerą będę fotografował mój blok od tyłu, że tak powiem. Z drzewkiem brzoskiwni na pierwszym planie. Zdjęcia wysyłał na Twittera co 3 godziny na przykład:

  0 6,9,12,15,18,21 * * * /home/pi/bin/mk1photo.sh

Na zdjęciu widać czujnik ruchu, bo faktycznie takowy też dokleiłem do obudowy. Że niby miał zdjęcia robić jak ruch wykryje, ale ponieważ to w zasadzie nie działało, więc teraz ten czujnik wprawdzie jest, ale nie podłączony. Atrapa...

poniedziałek, 16 marca 2020

Dane nt #Covid19 podsumowanie

Dane pierwotne: Center for Systems Science and Engineering (CSSE/Johns Hopkins University) https://github.com/CSSEGISandData/COVID-19 (także słynna wizualizacja: https://gisanddata.maps.arcgis.com/apps/opsdashboard/index.html#/bda7594740fd40299423467b48e9ecf6.) European Centre for Disease Prevention and Control https://ecdc.europa.eu/en/geographical-distribution-2019-ncov-cases.

Dane agregowane od innych: Our World in Data/Coronavirus Source Data/WHO Situation Reports https://ourworldindata.org/coronavirus-source-data . Są też tzw. dane w czasie rzeczywistym: https://worldometers.info/coronavirus/, ale ich wiarygodność jest podejrzana, bo w przeciwieństwie do tych wyżej opisanych nie wiadomo jak są zbierane i/lub skąd agregowane (więc nie ma klikalnego linku).

Dane nt rozwoju epidemii covid19

Na stronie https://ourworldindata.org/coronavirus-source-data są dane nt liczby przypadków/zgonów z powodu zarażenia wirusem covid19, których źródłem są 'Raporty Sytuacyjne WHO' (https://www.who.int/emergencies/diseases/novel-coronavirus-2019/situation-reports/). Raporty są w formacie PDF więc bezpośrednio nie można korzystać z publikowanych tam danych. No ale uprzejmi ludzie z ourworldindata.org już te raporty zamienili na csv i są one gotowe do pobrania:

wget -N https://covid.ourworldindata.org/data/full_data.csv

Za pomocą prostych skryptów Perla modyfikuję plik full_data.csv tak, żeby poszczególne kolumny zawierały: date;id;country;newc;newd;totalc;totald (data, ISO-kod kraju, nazwa-kraju, nowe-przypadki, nowe-zgony, wszystkie-przypadki, wszystkie-zgony)

Dla wybranych krajów rysują wykresy liniowe (wykorzystując R):

library("dplyr")
library("ggplot2")
library("ggpubr")
##
today <- Sys.Date()
tt<- format(today, "%d/%m/%Y")

d <- read.csv("covid19.csv", sep = ';',  header=T, na.string="NA");

d <- d %>% filter(as.Date(date, format="%Y-%m-%d") > "2020-02-15") %>% as.data.frame

##
c1 <- c('ITw', 'DEw', 'ESw', 'UKw', 'FRw', 'DKw', 'SEw')
# date;id;country;newc;newd;totalc;totald
d1 <- d %>% filter (id %in% c1) %>% as.data.frame
t1 <- d1 %>% group_by(id) %>%  summarise(cc = sum(newc, na.rm=T), dd=sum(newd, na.rm=T))

lab1c <- toString(paste (sep=" = ", t1$id, t1$cc))
lab1d <- toString(paste (sep=" = ", t1$id, t1$dd))

str(d1)

pc1 <- ggplot(d1, aes(x= as.Date(date, format="%Y-%m-%d"), y=newc)) + geom_line(aes(group = id, color = id), size=.8) +
 xlab(label="") +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle(sprintf("COVID19: new confirmed cases (%s)", tt), subtitle=sprintf("Total: %s\n%s", lab1c, surl))

pd1 <- ggplot(d1, aes(x= as.Date(date, format="%Y-%m-%d"), y=newd)) + geom_line(aes(group = id, color = id), size=.8) +
 xlab(label="") +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 ggtitle(sprintf ("COVID19: deaths (%s)", tt), subtitle=sprintf("Total: %s\n%s", lab1d, surl))

c2 <- c('PLw', 'CZw', 'SKw', 'HUw', 'ROw', 'BGw', 'ELw')
d2 <- d %>% filter (id %in% c2) %>% as.data.frame
t2 <- d2 %>% group_by(id) %>%  summarise(cc = sum(newc, na.rm=T), dd=sum(newd, na.rm=T))

str(d2)

lab2c <- toString(paste (sep=" = ", t2$id, t2$cc))
lab2d <- toString(paste (sep=" = ", t2$id, t2$dd))

pc2 <- ggplot(d2, aes(x= as.Date(date, format="%Y-%m-%d"), y=newc)) + geom_line(aes(group = id, color = id), size=.8) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 xlab(label="") +
 ggtitle(sprintf("COVID19: new confirmed cases (%s)", tt), subtitle=sprintf("Total: %s\n%s", lab2c, surl))

pd2 <- ggplot(d2, aes(x= as.Date(date, format="%Y-%m-%d"), y=newd)) + geom_line(aes(group = id, color = id), size=.8) +
 theme(plot.subtitle=element_text(size=8, hjust=0, color="black")) +
 xlab(label="") +
 scale_y_continuous(breaks=c(1,2,3,4,5,6,7,8,9)) +
 ggtitle(sprintf ("COVID19: deaths (%s)", tt), subtitle=sprintf("Total: %s\n%s", lab2d, surl))

p1 <- ggarrange(pc1,pd1, ncol=2, nrow=1)
p2 <- ggarrange(pc2,pd2, ncol=2, nrow=1)
ggsave(plot=p1, "Covid19_1w.png", width=15)
ggsave(plot=p2, "Covid19_2w.png", width=15)

Zatem: Liczba przypadków/zgonów z powodu zarażenia wirusem covid19 na podstawie danych ourworldindata.org/WHO:

UE, która jest powszechnie krytykowana, że nic nie robi w sprawie, okazuje się że coś tobi -- też udostępnia jakieś dane w temacie (https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide). Wprawdzie nie jest określone skąd te dane pochodzą, ale sądząc po ich zawartości źródło jest to samo (WHO).

wget -N https://www.ecdc.europa.eu/sites/default/files/documents/COVID-19-geographic-disbtribution-worldwide-2020-03-15.xls\
  -O covid19.csv

Ponieważ dane, że tak powiem, unijne są w formacie xls zamieniam je na csv, wykorzystując do tego LibreOffice:

## zamień wszystkie pliki z bieżącego katalogu na csv ze ; (59) jako znakiem separacji:
soffice --convert-to csv:"Text - txt - csv (StarCalc)":59,,0,1,1 --outdir . *.xls

Rysuję wykresy liniowe zmodyfikowanym z dokładnością do pliku z danymi R-skryptem. Wyniki są prawie takie same. Może bym nawet nie zwrócił uwagi, że się różnią gdyby nie podejrzane załamanie liczby przypadków dla Włoch dla 15.03.2020 (z 2,5 tys na 90).

Drążąc temat wyrysowałem wykresy dla wybranych czterech krajów w dwóch wariantach danych (dane z ourworldindata.org oznaczone literką w). W szczególności i niestety Włochy 15/3/2020 odnotowały ponad 3497 nowych przypadków a nie 90 jak podano w bazie Unijnej. Są też mniejsze różnice w innych miejscach:

Wszystko to robione jest automatem co pobiera/zamienia/rysuje/wstawia na githuba (https://github.com/hrpunio/Nafisa/tree/master/Covid19) oraz wysyła na twittera (https://twitter.com/tprzechlewski). Automat działa na RaspberryPi zresztą...

sobota, 7 marca 2020

Moja nowa stacja pogody: podsumowanie


HP1000SE_PRO.jpg

Ersatz konsola

SDS011

DP200

Dla przypomnienia moja nowa stacja to konsola WiFi o symbolu DP1500 kupiona u Niemca (czyli w sklepie froggit.de): ze standardowym zestawem czujników + czujnik pyłu zawieszonego DP200 (też Froggita). BTW DP1500 to to samo co GW1000 firmy Ecowitt. Zaś DP 200 Froggita to WH41 Ecowitta (http://www.ecowitt.com/wifi_weather/83.html), albo nawet PM25 firmy AcuWeather (https://www.ambientweather.com/ampm25.html)

Zamiast konsoli kupiłem na OLX 5 calowy ekran (110 PLN) i podłączyłem do Raspberry Pi (bezproblemowo).

# konfiguruję (logowanie bez hasła)
Boot Options -> Desktop/CLI -> Desktop Autologin/Desktop GUI
#
# start chromium-browser załadowanie pliku
vi ~/.config/lxsession/LXDE-pi/autostart
## wpisuję
@/usr/bin/chromium-browser --disable-restore-session-state \
  file:///var/www/html/DP1500_live.html

Ekran jest dotykowy. Jak chcę obejrzeć dane pogodowe to dotykam i się wyświetla.

Że jednak brakowało konsoli, to w końcu jednak kupiłem Froggit HP 1000SE PRO (która jest klonem Ecowitt HP 2551). Froggit sprzedaje konsolę jako ersatz, bez niezbędnego specjalnego czujnika temperatury/wilgotności/ciśnienia. Sama stacja jest bowiem dumb -- nic nie mierzy. Ja miałem czujnik temperatury/wilgotności DP50, ale on nie mierzy ciśnienia. Musiałem dokupić oddzielnie ten specjalny, bo się nie dogadałem a dokumentacja/informacja jest w tym aspekcie mało jasna.

Konfigurowanie HP 1000SE PRO to już nie był żaden problem, bo stacja ma aż 8 klawiszy. Połączyłem ją z routerem a potem z dwoma serwisami: ecowitt.net (www.ecowitt.net; oglądanie wymaga zarejestrowania się w sieci ecowitt.net) oraz z WOW ( wow.metoffice.gov.uk)

Kiedyś kupiłem też inny czujnik pyłu zawieszonego (Nova SDS011) i też go podłączyłem do raspberry (bezproblemowo). Teraz przykręciłem go na ścianie obok DP 200 z zamiarem porównania odczytów.

Reasumując posiadam: starą stację WH 2080 działającą od +10 lat. Nową stację DP1500/WH1000SE PRO oraz czujnik SDS101. WH 2080 podłączona jest przez kabel USB do Sheevaplug (to też zaszłość, planuję docelowo przejść na Raspberry) a obsługiwania jest przez pywws. DP1500/WH1000SE podłączona jest przez router, przy czym DP1500 skonfigurowana jest na serwer lokalny/weewx, a WH1000SE wysyła dane od razu do WOW/ecowitt.net.

Całość kosztował ponad 2500 PLN (wliczając starą stację WH2080). W sumie mogłem kupić HP1000SE PRO + DP200 za 330 EUR, a kupiłem na raty powyższe plus dodatkowy czujnik DP50/DP200 za 450 EUR. Przepłaciłem...

poniedziałek, 24 lutego 2020

Pierwszy skazany w sprawie zabójstwa Adamowicza


Zaproszenie na własny pogrzeb?

Dariusz S., pracownik Agencji Ochrony Tajfun, która 13 stycznia 2019 r. zabezpieczała imprezę Wielkiej Orkiestry Świątecznej Pomocy, tuż po zdarzeniu zeznał, że nożownik, który zaatakował Adamowicza, wszedł na scenę posługując się plakietką z napisem `Media'.

`Oskarżony był pod wpływem bardzo silnych emocji, to nie było zaplanowane' -- uznał w piątek Sąd Rejonowy w Gdańsku, skazując 37-letniego mężczyznę na karę roku więzienia w zawieszeniu na trzy lata. Wymierzył mu też 7,5 tys. złotych grzywny oraz obciążył kosztami procesu w kwocie ponad 32 tys. zł. Wyrok nie jest prawomocny.

Sąd zwrócił uwagę, że u wszystkich, którzy widzieli tragiczne wydarzenie z 13 stycznia 2019 r. `rodziło się pytanie, jak to się stało, że sprawca zabójstwa prezydenta Gdańska po prostu wkroczył sobie na scenę, dokonał tego czynu, po czym jeszcze przechwycił mikron i wygłosił swoje oświadczenie'.

[TVPInfo 21.02.2020, 09:52 |aktualizacja: 10:34]

Koncert 27. Finału Wielkiej Orkiestry Świątecznej Pomocy na Targu Węglowym w Gdańsku rozpoczął się w niedzielę o godz. 15. `Światełko do nieba' zaplanowano jak w całej Polsce na godz. 20.

Organizatorzy -- stowarzyszenie Regionalne Centrum Wolontariatu w Gdańsku -- zgłosili, że w koncercie weźmie udział 5 tys. osób. Taka impreza, jak każda powyżej tysiąca osób, mogłaby zostać zarejestrowana jako masowa. Finał WOŚP zgłoszono jednak jako zajęcie pasa drogowego. Na podstawie takich przepisów organizowano ją w Gdańsku od początku, tak jak wiele innych wydarzeń sportowych czy kulturalnych. Tym razem też uzyskała zgodę magistratu [czyli zamordowanego -- moje] i komendanta wojewódzkiego policji.

Regionalne Centrum Wolontariatu wynajęło w tym celu Agencję Ochrony `Tajfun' z Gdańska. Jak zapewniają organizatorzy, miała ona potraktować wydarzenie tak jak imprezę masową.

Agencja Tajfun działa od 2008 roku. Ma licencję. Na Targu Węglowym pracowało w niedzielę 50 ochroniarzy.

-- Pod względem formalnym i praktycznym w naszej ocenie wszystko zostało dochowane -- mówił w poniedziałek na konferencji prasowej Łukasz Isenko, pełnomocnik firmy.

[https://trojmiasto.wyborcza.pl/; Anna Dobiegała, współpraca Aleksandra Brzezińska 16 stycznia 2019 | 05:02]

[Ten S. to nie jest szeregowy ochroniarz tylko nieformalny szef firmy zresztą -- moje]: Tajfun działa od 2008 roku. Posiada licencję wystawioną na nazwisko Gracjana Zwolaka, ale w portalach społecznościowych jako dyrektor zarządzający występuje Dariusz Sokołowski. To były policjant, kurator sądowy, a także właściciel firmy windykacyjnej. Tajfunem zarządzać ma od półtora roku. [WirtualnaPolska]

Dariusz S. to były policjant. Pracował m.in. w Komendzie Miejskiej w Gdańsku. Jak ustalił portal tvn24.pl, odszedł ze służby ze względu na bójkę, do której doszło w Gdańsku 29 lipca 2016 roku. Akt oskarżenia trafił do sądu w sierpniu 2017 r., ale proces jeszcze się nie rozpoczął. Z nieoficjalnych informacji portalu wynika, że Dariusza S. zawieszono w obowiązkach policjanta, gdy usłyszał zarzuty.

[WirtualnaPolska 21.01.2019, Dariusz S., który ochraniał tragiczny finał WOŚP, był oskarżony o pobicie i groźby. Musiał odejść z policji]

Teraz dostał wyrok w zawieszeniu, bo miał zdaniem sądu: poświadczony dokumentami wzorowy przebieg służby w policji.

To chyba wszystko.

niedziela, 16 lutego 2020

Wycieczka do Paryża

Lądowanie w piątek na lotnisku w Beauvais, za 13 EUR (od osoby) jedziemy mikrobusem na stację metra Porte Maillot. Tu kupujemy bilety (i takie ciastko co wygląda jak sernik, ale jest z przesłodzonego budyniu) i jedziemy zwiedzać Luwr. Około 21 jedziemy (także metrem) na kwaterę wynajętą przez AirBnB w 13 dzielnicy. Od stacji Olympiades nasza kwatera jest może 300 m. W Luwrze to najbardziej mi się podobają eksponaty z bliskiego i środkowego wschodu (Syria/Iran/Irak) oraz (bo lubię) kolekcja malarstwa holenderskiego (Breugel itp.)

Rano pobudka i około 9:00 idziemy zwiedzać miasto rozpoczynając od Notre Dame, które jest oczywiście po pożarze zamknięte. Oglądamy to co widać zza parkanu. W centrum koncentracja policji w pełnym rynsztunku bojowy, ale się nie biją. Chodzimy, oglądamy około 15:00 idziemy do muzeum Orsay oglądać francuski symbolizm. Oglądamy do 18:00 czyli do zamknięcia. Potem na kwaterę. Odkrywamy, że w pobliżu naszego bloku jest m.in supermarket, piekarnia i chińska restauracja. Decydujemy się na kolację w tej restauracji wydając prawie 40 EUR.

Game day czyli niedziela. Rozpoczynamy od oglądania Sorbony (nic ciekawego), potem idziemy pod Panteon. Wejście płatne więc my nie wchodzimy, ale Janek ma za darmo jako osobnik małoletni (26 lat). Czekamy na niego w kościele St Etienne du Mont załapując się na mszę. Msza jest na wysokim poziomie (w sensie formy--organista wirtuoz, śpiew pod kierunkiem zawodowej śpiewaczki ewidentnie itp). Kościół pełny i sporo młodych ludzi, w większości białych. Po mszy idziemy na naleśnika, zwiedzamy coś tam jeszcze i jedziemy metrem do Saint Denis.

Metro pełne ludzi. Jadą na mecz rugby Francja-Włochy. Wysiadamy, idziemy z kibicami oglądać stadion. Potem pod Bazylikę, bo to blisko. Wejście do kościoła jest za darmo, ale do krypty z Karolem Młotem już nie (10 EUR). Przed Bazyliką karuzela. Hmmm coś jakby ktoś na Wawelu przed kryptą królów postawił wesołe miasteczko. Zresztą następnego dnia nasz Francuz z kwatery pyta gdzie byliśmy. -- W Saint Denis, -- Na meczu? -- Nie oglądać stadion i katedrę -- Jaką katedrę?

Ostatni dzień: Pola Elizejskie, Łuk Triumfalny, Pałac Macrona, plac Trocadero (jeszcze jeden naleśnik, tym razem nie na talerzu, ale z foodtrucka), Wieża Eiffla, pałac Inwalidów (ale tylko z zewnątrz; zwłoki Napoleona sobie darowaliśmy a na muzeum Armii nie było czasu). Coś tam jeszcze zwiedzamy i jedziemy na stację metra Porte Maillot na 17:00. Zresztą od 16:00 już pada. Dwie godziny jedziemy na lotnisko, bo są korki, ale samolot do GDA też się spóźnił. Odlatujemy z Paryża o 9:00 wieczorem z 30 minutowym opóźnieniem.

Podsumowanie: Z muzeów to zwiedziliśmy Luwr i Orsay. Obejrzeliśmy z 10 kościołów w tym Bazylikę Saint Denis i stadion. Paryż zrobił na mnie nadspodziewanie dobre wrażenie. Sprawne metro, ciekawe muzea, w których zbiory są przyjaźnie wyeksponowane dla zwiedzających, że tak powiem: żadnych szyb czy barierek (za wyjątkiem kilku już naprawdę unikatowych eksponatów takich jak Mona Lisa. można podjeść i palec wsadzić w płótno vanGogha czy Breugla). Nie ma wałęsających się uchodźców jak w Mediolanie a wszyscy bezdomi jakich widzieliśmy to biali. Dużo policji/wojska z bronią maszynową ma ulicach.


Od lewej: pałac Dariusza w Luwrze/regaty w Molesey/kibice Włoscy/St. Denis (grobowce i katedra)

Koszt (3 osoby): 900 PLN kwatera, luwr+orsay 230+125 PLN, metro 200 PLN, przejazd z/do Paryża 300 PLN, bilety lotnicze 800. Razem wychodzi jakieś 2500 PLN. Do tego jeszcze kilkaset złotych na jedzenie. W 3 kaflach się zmieściliśmy myślę...

Do pobrania ślady kml ze zdjęciami; album ze zdjęciami.

czwartek, 13 lutego 2020

Moja nowa stacja pogody: DP 1500 Froggit


DP 1500

DP 200

All-in-One outdoor sensor

All-in-One outdoor sensor

Po długich namysłach kupiłem nową stację (trzeba się rozwijać) pn DP 1500 SmartHub wifi Gateway (za 50 EUR). Jest to konsola bez ekranu z czujnikiem temperatury/ciśnienia i wilgotności (na metrowym kablu); przedmiot wielkości dużego pudełka od zapałek. Do tego pudełka dokupiłem:

  • czujnik temperatury i wilgotności powietrza HP3000 (aka DP 50)
  • maszt z zestawem czujników ('WH4000SE All-in-One outdoor sensor) do pomiaru prędkości/kierunku wiatru, opadów, indeksu UV, promieniowania słonecznego (solar radiation)
  • czujnik pyłów PM2.5 DP 200

Cenowo to wyszło tak (por www.froggit.de), że maszt + DP 1500 jest w zestawie za 135 EUR (czyli sam maszt kosztuje 85 EUR); czujnik DP 50 to następne 13 EUR (kupiłem dwa na wszelki wypadek); czujnik DP 200 aż 90 EUR. Razem wyszło około 250EUR czyli ponad 1100 PLN z wysyłką i płatnością przez PayPal, bo innej opcji nie ma (nikt nie mówił że będzie tanio)

Wszystko w niemieckiej firmie Froggit. BTW Froggit to klon produktów bardziej znanej firmy Ecowitt. To co Froggit sprzedaje jako DP1500 u Ecowitta nazywa się GW 1000. Są jeszcze inne firmy, które robią ten sprzęt pod inną marką.

W stacjach pogodowych standardem jest teraz wysyłanie danych w chmurę. Nie ma natomiast możliwości bezpośredniego pobierania danych z urządzenia. Co najwyżej można wysłać na własną chmurę. Do tego konfiguruje się urządzenie przez smartfona za pomocą dedykowanej aplikacji pn. WSView. Ot tak to sobie branża wymyśliła. Średnio mi się to podoba, ale po prostu urządzeń, z których można pobrać bezpośrednio dane w starym stylu się nie produkuje (no nie do końca jest to prawdą: produkuje się to co mam od 10 lat, ale ja nie chę kupować jeszcze raz tej samej stacji). Skoro trzeba mieć własną chmurę, to trzeba ją założyć. Na szczęście ponieważ potrzeba jest matką wynalazków, to w tej sprawie inni ludzie już przygotowali stosowne rozwiązania.

Program weewx, który tradycyjnie obsługiwał stacje, że tak powiem kablowe został uzupełniony o weewx-interceptor, który przechwytuje dane wysyłane na (pseudo) własną chmurę.

No więc rozpakowałem to co przyszło z Niemiec. Skręciłem maszt z czujnikami wiatru/deszczu, nasłonecznienia. Czujnik PM2.5 przykręciłem do ściany we wnęce okiennej, żeby deszcz na niego nie kapał. Czujnik DP 50 wstawiłem do klatki meteo. Maszt umieściłem na dachu budynku 3 kondygnacyjnego (nie ma żadnych problemów z zasięgiem, a mieszkam na parterze.)

Ściągnąłem przez Google Play aplikację WSView. Postępując zgodnie z procedurą (opisaną w podręczniku) połączyłem się z konsolą, która zaczęła pokazywać dane z czujników (na smartfonie). Uwaga: WSView dość wolno się łączy i na dokładkę wysyła mylące komunikaty o odrzuconych połączeniach, co powoduje pewien niepokój. Nie należy w tym momencie wykonywać nerwowych ruchów, tylko poczekać...

Instalowanie weeWX (na RaspberryPi)

Mając ustawione połączenie konsola-router można pójść dalej. Znowu jest to dokładnie opisane i co więcej opis jest zgodny ze stanem faktycznym (cf github.com/weewx/weewx/wiki/gw1000-recipe):

Instalujemy weeWX (wybierając 'Simulator' jako 'station type'). Później należy zmienić 'station type' na 'Interceptor' uruchamiając wee_config --reconfigure:

wget -qO - http://weewx.com/keys.html | sudo apt-key add -
wget -qO - http://weewx.com/apt/weewx.list | sudo tee /etc/apt/sources.list.d/weewx.list
sudo apt-get update
sudo apt-get install weewx

# shut down weeWX
sudo /etc/init.d/weewx stop

# install weewx-interceptor extension and enable the driver
git clone https://github.com/matthewwall/weewx-interceptor.git
sudo wee_extension --install weewx-interceptor
sudo wee_config --reconfigure

Sprawdzenie czy interceptor przechwytuje dane. W oknie terminala uruchamiamy interceptor.py ze wskazaniem na port 8000:

PYTHONPATH=/usr/share/weewx python /usr/share/weewx/user/interceptor.py \
 --device=fineoffset-bridge --port 8000 --debug

Uruchamiamy przeglądarkę, wpisujemy następujący URL (192.168.xx.xx to IP komputera z weeWXem):

http://192.168.xx.xx:8000/data/report?PASSKEY=XXX&stationtype=GW1000B_V1.5.5
 &dateutc=2019-12-29+16:27:27&tempinf=67.1&humidityin=39
 &baromrelin=30.138&baromabsin=30.138&freq=915M&model=GW1000

W oknie terminala z uruchomionym interceptor.py powinno się pojawić:

raw data: PASSKEY=XXX&stationtype=GW1000B_V1.5.5&dateutc=2019-12-29+16:27:27&tempinf=67.1
&humidityin=39&baromrelin=30.138&baromabsin=30.138&freq=915M&model=GW1000
raw packet: {'humidity_in': 39.0, 'temperature_in': 67.1, 'barometer': 30.138, 'usUnits': 1,
'dateTime': 1577636847} mapped packet: {'inHumidity': 39.0, 'barometer': 30.138, 'inTemp': 67.1,
'usUnits': 1, 'dateTime': 1577636847} 

Konfigurowanie GW1000

W aplikacji WSView przechodzimy do strony 'Weather Services'. Klikamy 'Next' aż wyświetli się 'Customized'. Na tej stronie wpisujemy IP komputera z zainstalowanym weeWXem jako wartość pola 'Server IP/Hostname'. Wpisujemy '/' jako wartość pola 'Path' oraz 8000 jako wartość pola 'Port'. Domyślny upload jest ustawiony na 60 sekund (zmieniłem na 300).

Ustawienia weeWX są w pliku /etc/weewx/weewx.conf. W szczególności sekcja Interceptor powinna wyglądać następująco:

[Interceptor]
    driver = user.interceptor
    device_type = fineoffset-bridge
    port = 8000

Uruchamianie/zatrzymanie/sprawdzanie usługi:

# Start weewx (as daemon)
sudo /etc/init.d/weewx start
# Stop weewx
# sudo /etc/init.d/weewx stop
# Check status
# /etc/init.d/weewx status
# systemctl status -l weewx
# ls -l /etc/rc2.d

WeeWX zapisuje dane do bazy /var/lib/weewx/weewx.sdb. Można je oglądać uruchamiając:

sqlite3 /var/lib/weewx.sdb

## wyświetl wszystkie tabele
.tables
## wyświetl schemat tabeli archive
.schema archive
PRAGMA table_info(archive);
## zawartość archive
select * from archive;
## zakończ
.q

Główną tabelą danych weeWXa jest archive:

sqlite> PRAGMA table_info(archive);
select * from archive

0|dateTime|INTEGER|1||1
1|usUnits|INTEGER|1||0
2|interval|INTEGER|1||0
3|barometer|REAL|0||0
4|pressure|REAL|0||0
5|altimeter|REAL|0||0
6|inTemp|REAL|0||0
7|outTemp|REAL|0||0
8|inHumidity|REAL|0||0
9|outHumidity|REAL|0||0
10|windSpeed|REAL|0||0
11|windDir|REAL|0||0
12|windGust|REAL|0||0
13|windGustDir|REAL|0||0
14|rainRate|REAL|0||0
15|rain|REAL|0||0
16|dewpoint|REAL|0||0
17|windchill|REAL|0||0
18|heatindex|REAL|0||0
19|ET|REAL|0||0
20|radiation|REAL|0||0
21|UV|REAL|0||0
22|extraTemp1|REAL|0||0
23|extraTemp2|REAL|0||0
24|extraTemp3|REAL|0||0
25|soilTemp1|REAL|0||0
26|soilTemp2|REAL|0||0
27|soilTemp3|REAL|0||0
28|soilTemp4|REAL|0||0
29|leafTemp1|REAL|0||0
30|leafTemp2|REAL|0||0
31|extraHumid1|REAL|0||0
32|extraHumid2|REAL|0||0
33|soilMoist1|REAL|0||0
34|soilMoist2|REAL|0||0
35|soilMoist3|REAL|0||0
36|soilMoist4|REAL|0||0
37|leafWet1|REAL|0||0
38|leafWet2|REAL|0||0
39|rxCheckPercent|REAL|0||0
40|txBatteryStatus|REAL|0||0
41|consBatteryVoltage|REAL|0||0
42|hail|REAL|0||0
43|hailRate|REAL|0||0
44|heatingTemp|REAL|0||0
45|heatingVoltage|REAL|0||0
46|supplyVoltage|REAL|0||0
47|referenceVoltage|REAL|0||0
48|windBatteryStatus|REAL|0||0
49|rainBatteryStatus|REAL|0||0
50|outTempBatteryStatus|REAL|0||0
51|inTempBatteryStatus|REAL|0||0

Nie ma kolumny PM2.5 w szczególności.

Wysyłanie danych na ecowitt.net

To się samo robi. Trzeba tylko zarejestrować stację na ecowitt.net podając MAC-adres urządzenia, ale ecowitt.net nie udostępnia danych publicznie. Żeby oglądać zawartość ecowitt.net trzeba się zalogować. No a żeby się zalogować, to trzeba mieć konto. Inna sprawa że nie trzeba mieć stacji, żeby mieć konto. Jest też stronka pod adresem www.weewx.com/stations.html z wykazem zarejestrowanych stacji obsługiwanych przez WeeWX. Żeby tam zaistnieć trzeba wstawić do /etc/weewx/weewx.conf:

register_this_station = True
station_url = http://pinkaccordions.homelinux.org/

Co jest opisane w dokumentacji WeeWXhttp://www.weewx.com/docs/usersguide.htm#station_registry

Dodanie czyjnika PM2.5

Nie ma kolumny PM2.5, ale interceptor.py pobiera co trzeba bo na ecowitt.net są dane dotyczące PM2.5 tylko lokalnie nie są rejestrowane w bazie. Wychodzi na to, że to WeeWx 'nie widzi' ekstra danych.

Jest oficjalny sposób na rozszerzenie schematu bazy (http://www.weewx.com/docs/customizing.htm#add_archive_type) ale ja póki co zrobiłem to na szybko w ten sposób, że zmodyfikowałem działanie /usr/share/weewx/user/interceptor.py, który nie tylko wypisuje co przechwycił (co zapewne przetwarza dalej weeWX), ale także zapisuje przechwycony rekord do pliku tekstowego. W tym celu dodałem takie coś:

def genLoopPackets(self):
    last_ts = 0
    while True:
      try:
        data = self._device.get_queue().get(True, self._queue_timeout)

        logdbg('raw data: %s' % data)

        ## LOG every 15 minutes
        nowMM = time.strftime("%M", time.gmtime())
        if (nowMM == '00' or nowMM == "15" or nowMM == "30" or nowMM == "45"):
          ffex = open("/var/log/weewx_exlog.txt", "a+")
          ffex.write ('[WEEWX#RAW] %s\n' % data)
          ffex.close()
          ##loginf('[WEEWX#RAW]: %s' % data)

Co 15 minut, a dokładniej w 15/30/45 oraz zerowej minucie każdej godziny zapisywany jest rekord danych do pliku /var/log/weewx_exlog.txt.

cp  interceptor.py interceptor_orig.py
## po zmodyfikowaniu interceptor.py w wyżej opisany sposób
python -m py_compile interceptor.py

Prezentacja wyników

Standardowe raporty WeeWXa są w katalogu: /var/www/html/weewx. W sumie z nich nie korzystam, robię raport po swojemu z pliku /var/log/weewx_exlog.txt

Dokładniej raport jest tworzony na komputerku bez dostępu do internetu a następnie co godzinę kopiowany na ten, który dostęp ma czyli na pinkaccordions.homelinux.org

W planach jest zakup ekranu LCD 5--7 cali do raspberry co będzie robił za wyświetlacz mojej stacji.

Różne rzeczy

Minimalizowanie liczby zapisów (przeciwdziałanie zużyciu się karty SDHC) https://github.com/weewx/weewx/wiki/Minimize-writes-on-SD-cards.

niedziela, 26 stycznia 2020

Ciasto karobowe

Karob w postaci strąków zobaczyłem pierwszy raz w życiu w zeszłym roku w Adanie (zapakowany w sklepie, nie na drzewie). Indagowany na okoliczność co to jest, poznany tam Turek, powiedział mi, że oni to żują, ale można to też jeść. Na spróbowanie kupiłem jedną paczkę, która długo leżała w szafie, ale w końcu ją zjadłem i całkiem mi smakował. Zachęcony, zbadałem głębiej temat przy pomocy google no i natrafiłem m.in. na przepis na proste ciasto z karobu (tutaj):

Składniki (na jedną keksówkę 20x11cm): 1/2 szklanki i dwie łyżki (160g) mąki pszennej; 1/3 szklanki karobu (50g); 1 duży banan; 1/4 szklanki brązowego cukru (ja dałem 40g ksylitolu); 1/2 szklanki oleju (80g); 1/3 szklanki mleka roślinnego (ja lałem tyle mleka/wody żeby ciasto miało właściwą konsystencję); szklanka orzechów włoskich*; pół szklanki żurawiny; łyżeczka proszku do pieczenia; łyżeczka sody.

Drobna uwag: autor cytowanego bloga BTW rąbnął się i raz pisze laskowych a raz włoskich. Ja dałem włoskich m.in. dlatego, że w moim rankingu orzechów laskowe są na samym końcu.

Przygotowanie: zmieszać wszystko za wyjątkiem żurawin/orzechów, a banana zmienić na miazgę uprzednio. Dodać żurawiny i orzechy od masy. Zamieszać. Piec przez 30--35 minut (ja tam zawsze piekę na papierze, bo nie chce mi się myć formy:-)

Czyli super prosto a ciasto jest bardzo dobre...

sobota, 4 stycznia 2020

Podsumowanie poprzedniego roku rowerowego

Podsumowanie zacząłem od wsadzenia (wreszcie) całego dorobku na githuba. Są tam detaliczne dane od 1993roku (27 lat, nie bójmy się słów); do tego w latach 1990--92 przejechałem 20,760, ale ten wyczyn nie jest, aż tak detalicznie udokumentowany. Razem wychodzi w latach 1990--2019 (30 lat) 242,355 km. Czyli do 250 tysięcy zostało 7645 km. Dam radę (jak zdrowie nie przeszkodzi)...

Na githubie jest w szczególności plik opus_magnum.csv, który zawiera cztery kolumny: dd;mm;yyyy;dist (dzień, miesiąc, rok oraz dystans; czasami dni mogą się powtarzać--to nie jest błąd, tylko oznacza, że jeździłem kilka razy dziennie):

require(ggplot2)
require(dplyr)

d <- read.csv("opus_magnum.csv", sep = ';', dec = ",",  header=T, na.string="NA");

dm <- d %>% mutate(cat = factor(yyyy)) %>%  group_by (cat) %>%
  summarise( tdist = sum(dist, na.rm=TRUE) /1000 ) %>%
  as.data.frame
  
yyyy.first <- first(dm$cat)
yyyy.last <- last(dm$cat)

time.period <- sprintf ("%s--%s", yyyy.first, yyyy.last)

p.m <- ggplot(dm, aes(x = cat, y = tdist )) +
    ggtitle(sprintf ("Distance by year (%s)", time.period)) +
    xlab("year") + ylab("ths km") +
    geom_bar(position = 'dodge', stat = 'identity', fill = "steelblue") +
    geom_text(data=dm, aes(label=sprintf("%.2f", tdist), y= tdist),
    vjust=1.5, color="darkblue", size=3 )
ggsave(p.m, file="opus_magnum.pdf", width=12)
### ###
cy <- 2019
d <- d %>% filter (yyyy == cy) %>% as.data.frame

rides = nrow (d)
total = sum (d$dist)
mride = mean (d$dist)

dm <- d %>% mutate(cat = factor(mm)) %>% group_by (cat) %>%
  summarise( ss = sum(dist, na.rm=TRUE)) %>%
  as.data.frame

p.m <- ggplot(dm, aes(x = cat, y = ss )) +
ggtitle(sprintf ("Cycling in %i (total: %.1f kms/ %i rides/ %.1f kms per ride)",
   cy, total, rides,  mride)) +
    xlab("month") + ylab("km") +
    geom_bar(position = 'dodge', stat = 'identity', fill = "steelblue", alpha=.5) +
    geom_text(data=dm, aes(label=sprintf("%.0f", ss), y= ss),
    vjust=1.5, color="white" )

p.m

outfile <- sprintf ("opus_%i.pdf", cy)
ggsave(p.m, file=outfile, width=10)

Wyniki obok.