diff --git a/README b/README index bc0815b..979e0bd 100644 --- a/README +++ b/README @@ -28,7 +28,7 @@ Additionally the following communication services are implemented: * German classic 'Zeitansage' (talking clock) * POCSAG transmitter / receiver * Golay/GSC transmitter / receiver - * DCF77 time signal transmitter and receiver + * DCF77 time signal transmitter and receiver with weather info * C-Netz SIM emulator * C-Netz magnetic card emulator diff --git a/docs/index.html b/docs/index.html index 9f1be76..806fb82 100644 --- a/docs/index.html +++ b/docs/index.html @@ -132,6 +132,7 @@ Additional features:
  • POCSAG (paging system)
  • Golay / GSC (paging system)
  • 5-Ton-Ruf (firefighter's pagers and siren control)
  • +
  • DCF77 The German longwave time signal transmitter/receiver
  • diff --git a/src/dcf77/Makefile.am b/src/dcf77/Makefile.am index 22c133d..ce23ac8 100644 --- a/src/dcf77/Makefile.am +++ b/src/dcf77/Makefile.am @@ -6,6 +6,9 @@ bin_PROGRAMS = \ dcf77_SOURCES = \ dcf77.c \ + weather.c \ + cities.c \ + image.c \ main.c dcf77_LDADD = \ $(COMMON_LA) \ diff --git a/src/dcf77/cities.c b/src/dcf77/cities.c new file mode 100644 index 0000000..61aa8a7 --- /dev/null +++ b/src/dcf77/cities.c @@ -0,0 +1,633 @@ +#define _GNU_SOURCE +#include +#include +#include "cities.h" + +static struct city_list { + int region; + const char *name; +} city_list[] = { + { 0, "AGEN" }, + { 0, "AUCH" }, + { 0, "BORDEAUX" }, + { 0, "BRIVE LA GAILLARDE" }, + { 0, "CAHORS" }, + { 0, "MONTAUBAN" }, + { 0, "MONT MARSAN" }, + { 0, "PAU" }, + { 0, "PERIGUEUX" }, + { 0, "TARBES" }, + { 0, "TOULOUSE" }, + { 1, "ANGOULEME" }, + { 1, "LA ROCHELL" }, + { 1, "LA ROCHE S" }, + { 1, "LIMOGES" }, + { 1, "NIORT" }, + { 1, "POITIERS" }, + { 2, "ALENCON" }, + { 2, "AUXERRE" }, + { 2, "BAR LE DUC" }, + { 2, "BLOIS" }, + { 2, "BOBIGNY" }, + { 2, "BOURGES" }, + { 2, "CERGY PONT" }, + { 2, "CHARTRES" }, + { 2, "CRETEIL" }, + { 2, "EVRY" }, + { 2, "LE MANS" }, + { 2, "MELUN" }, + { 2, "NANTERRE" }, + { 2, "NEVERS" }, + { 2, "ORLEANS" }, + { 2, "PARIS" }, + { 2, "REIMS" }, + { 2, "TOURS" }, + { 2, "TROYES" }, + { 2, "VERSAILLES" }, + { 3, "ANGERS" }, + { 3, "BREST" }, + { 3, "CHERBOURG" }, + { 3, "JERSEY" }, + { 3, "LAVAL" }, + { 3, "LORIENT" }, + { 3, "NANTES" }, + { 3, "RENNES" }, + { 3, "ST BRIEUC" }, + { 4, "AURILLAC" }, + { 4, "CLERMON FERRAND" }, + { 4, "FLORAC" }, + { 4, "GUERET" }, + { 4, "MENDE" }, + { 4, "MILLAU" }, + { 4, "MONTLUCON" }, + { 4, "PUY VELAY" }, + { 4, "RODEZ" }, + { 4, "ST-ETIENNE" }, + { 4, "ST FLOUR" }, + { 5, "ALBI" }, + { 5, "BEZIERS" }, + { 5, "CARCASSONN" }, + { 5, "FOIX" }, + { 5, "MONTPELLIER" }, + { 5, "PERPIGNAN" }, + { 6, "AALST" }, + { 6, "ANTWERPEN" }, + { 6, "BOULOGNE" }, + { 6, "BRUGGE" }, + { 6, "BRUSSEL" }, + { 6, "CHARLEROI" }, + { 6, "GENK" }, + { 6, "GENT" }, + { 6, "HALLE" }, + { 6, "HASSELT" }, + { 6, "IXELLES" }, + { 6, "KNOKKE-HEIST" }, + { 6, "KORTRIJK" }, + { 6, "LEUVEN" }, + { 6, "LIEGE" }, + { 6, "LILLE" }, + { 6, "LOKEREN" }, + { 6, "MAASTRICHT" }, + { 6, "MECHELEN" }, + { 6, "MIDDELBURG" }, + { 6, "MONS" }, + { 6, "MOUSCRON" }, + { 6, "NAMUR" }, + { 6, "OOSTENDE" }, + { 6, "ROESELARE" }, + { 6, "SCHAERBEEK" }, + { 6, "TERNEUZEN" }, + { 6, "TOURNAI" }, + { 7, "CHAUMONT" }, + { 7, "DIJON" }, + { 7, "EPINAL" }, + { 7, "LONS LE S" }, + { 7, "METZ" }, + { 7, "NANCY" }, + { 7, "VESOUL" }, + { 8, "ALES" }, + { 8, "AVIGNON" }, + { 8, "MARSEILLE" }, + { 8, "MONTELIMAR" }, + { 8, "NIMES" }, + { 8, "PRIVAS" }, + { 8, "ST TROPEZ" }, + { 8, "TOULON" }, + { 9, "BOURG EN B" }, + { 9, "LYON" }, + { 9, "MACON" }, + { 9, "VALENCE" }, + { 10, "BRIANCON" }, + { 10, "CHAMBERY" }, + { 10, "DIGNE" }, + { 10, "GAP" }, + { 10, "GRENOBLE" }, + { 11, "ANNECY" }, + { 11, "BESANCON" }, + { 11, "DELEMONT" }, + { 11, "LA CHAUX-DE-FONDS" }, + { 12, "ASCHAFFENBURG" }, + { 12, "BAD HOMBURG" }, + { 12, "BAD KREUZNACH" }, + { 12, "DARMSTADT" }, + { 12, "FRANKFURT AM MAIN" }, + { 12, "HEIDELBERG" }, + { 12, "KAISERSLAUTERN" }, + { 12, "KARLSRUHE" }, + { 12, "LANDAU IN DER PFALZ" }, + { 12, "LUDWIGSHAFEN" }, + { 12, "MAINZ" }, + { 12, "MANNHEIM" }, + { 12, "PIRMASENS" }, + { 12, "WORMS" }, + { 13, "BITBURG" }, + { 13, "HAGEN" }, + { 13, "ISERLOHN" }, + { 13, "KOBLENZ" }, + { 13, "LUEDENSCHEID" }, + { 13, "LUXEMBOURG" }, + { 13, "NEUWIED" }, + { 13, "SAARBRUECKEN" }, + { 13, "SEDAN" }, + { 13, "SIEGEN" }, + { 13, "TRIER" }, + { 13, "VERVIERS" }, + { 13, "WIESBADEN" }, + { 14, "AACHEN" }, + { 14, "BIELEFELD" }, + { 14, "BOCHUM" }, + { 14, "BONN" }, + { 14, "DORTMUND" }, + { 14, "DUEREN" }, + { 14, "DUESSELDORF" }, + { 14, "DUISBURG" }, + { 14, "ESSEN" }, + { 14, "GELSENKIRCHEN" }, + { 14, "GUETERSLOH" }, + { 14, "KOELN" }, + { 14, "LINGEN" }, + { 14, "LIPPSTADT" }, + { 14, "MOENCHENGLADBACH" }, + { 14, "MUELHEIM AN DER RUHR" }, + { 14, "MUENSTER" }, + { 14, "NORDHORN" }, + { 14, "OBERHAUSEN" }, + { 14, "OSNABRUECK" }, + { 14, "RECKLINGHAUSEN" }, + { 14, "RHEINE" }, + { 14, "SOLINGEN" }, + { 14, "WESEL" }, + { 14, "WUPPERTAL" }, + { 15, "BRISTOL" }, + { 15, "CARDIFF" }, + { 15, "EXETER" }, + { 15, "HOLYHEAD" }, + { 15, "PLYMOUTH" }, + { 15, "ST DAVIDS" }, + { 15, "SWANSEA" }, + { 16, "BIRMINGHAM" }, + { 16, "BLACKPOOL" }, + { 16, "LEEDS" }, + { 16, "LEICESTER" }, + { 16, "LIVERPOOL" }, + { 16, "MANCHESTER" }, + { 16, "MIDDLESBROUGH" }, + { 16, "NEWCASTLE" }, + { 16, "NOTTINGHAM" }, + { 16, "SHEFFIELD" }, + { 17, "AMIENS" }, + { 17, "BEAUVAIS" }, + { 17, "CAEN" }, + { 17, "EVREUX" }, + { 17, "LAON" }, + { 17, "LE HAVRE" }, + { 17, "ROUEN" }, + { 18, "BOURNEMOUT" }, + { 18, "BRIGHTON" }, + { 18, "CAMBRIDGE" }, + { 18, "DOVER" }, + { 18, "IPSWICH" }, + { 18, "KINGSTON" }, + { 18, "LONDON" }, + { 18, "NORTHAMPTON" }, + { 18, "NORWICH" }, + { 18, "OXFORD" }, + { 18, "PORTSMOUTH" }, + { 18, "READING" }, + { 18, "SOUTHAMPTON" }, + { 19, "BORKUM" }, + { 19, "BREMERHAVEN" }, + { 19, "CUXHAVEN" }, + { 19, "DEN HELDER" }, + { 19, "ELMSHORN" }, + { 19, "EMDEN" }, + { 19, "GRONINGEN" }, + { 19, "HAMBURG" }, + { 19, "LEEUWARDEN" }, + { 19, "NORDERTSTEDT" }, + { 19, "SPIEKEROOG" }, + { 19, "ST PETER ORDING" }, + { 19, "SYLT" }, + { 19, "TEXEL" }, + { 19, "WILHELMSHAVEN" }, + { 20, "ALBORG" }, + { 20, "ESBJERG" }, + { 20, "FREDERIKSHAVN" }, + { 20, "HERNING" }, + { 20, "HOLSTERBRO" }, + { 20, "SKAGEN" }, + { 20, "THISTED" }, + { 20, "THYBOROEN" }, + { 20, "VIBORG" }, + { 21, "ARHUS" }, + { 21, "FREDERICIA" }, + { 21, "HORSENS" }, + { 21, "KOLDING" }, + { 21, "ODENSE" }, + { 21, "RANDERS" }, + { 21, "SILKEBORG" }, + { 21, "VEJLE" }, + { 22, "BRAUNSCHWEIG" }, + { 22, "BREMEN" }, + { 22, "CELLE" }, + { 22, "GOSLAR" }, + { 22, "HAMELN" }, + { 22, "HANNOVER" }, + { 22, "HERFORD" }, + { 22, "HILDESHEIM" }, + { 22, "LUENEBURG" }, + { 22, "MAGDEBURG" }, + { 22, "MINDEN" }, + { 22, "OLDENBURG" }, + { 22, "WOLFSBURG" }, + { 23, "HELSINGOER" }, + { 23, "KALUNDBORG" }, + { 23, "KOEBENHAVN" }, + { 23, "MALMOE" }, + { 23, "NAESTVED" }, + { 23, "ROSKILDE" }, + { 23, "SLAGELSE" }, + { 24, "FEHMARN" }, + { 24, "FLENSBURG" }, + { 24, "GREIFSWALD" }, + { 24, "KIEL" }, + { 24, "LUEBECK" }, + { 24, "ROSTOCK" }, + { 24, "RUEGEN" }, + { 24, "SCHWERIN" }, + { 24, "STRALSUND" }, + { 24, "SZCZECIN" }, + { 24, "WISMAR" }, + { 25, "AUGSBURG" }, + { 25, "DEGGENDORF" }, + { 25, "INGOLSTADT" }, + { 25, "LANDSHUT" }, + { 25, "PASSAU" }, + { 25, "REGENSBURG" }, + { 25, "ULM" }, + { 26, "BURGHAUSEN" }, + { 26, "FRIEDRICHSHAFEN" }, + { 26, "KEMPTEN" }, + { 26, "LINZ" }, + { 26, "MUENCHEN" }, + { 26, "ROSENHEIM" }, + { 26, "SIGMARINGEN" }, + { 26, "WELS" }, + { 27, "BOLZANO" }, + { 27, "MERANO" }, + { 27, "TRENTO" }, + { 28, "ANSBACH" }, + { 28, "BAMBERG" }, + { 28, "BAYREUTH" }, + { 28, "ERLANGEN" }, + { 28, "FUERTH" }, + { 28, "NUERNBERG" }, + { 28, "SCHWEINFURT" }, + { 28, "WEIDEN" }, + { 28, "WERTHEIM" }, + { 28, "WUERZBURG" }, + { 29, "ALTENBURG" }, + { 29, "BAUTZEN" }, + { 29, "CHEMNITZ" }, + { 29, "COTTBUS" }, + { 29, "DESSAU" }, + { 29, "DRESDEN" }, + { 29, "EISENHUETTENSTADT" }, + { 29, "GOERLITZ" }, + { 29, "HALLE" }, + { 29, "HOYERSWERDA" }, + { 29, "LEIPZIG" }, + { 29, "WITTENBERG" }, + { 29, "WROCLAW" }, + { 30, "EISENACH" }, + { 30, "ERFURT" }, + { 30, "GOTHA" }, + { 30, "HOF" }, + { 30, "JENA" }, + { 30, "NORDHAUSEN" }, + { 30, "PLAUEN" }, + { 30, "SUHL" }, + { 30, "WEIMAR" }, + { 30, "ZWICKAU" }, + { 31, "EVIAN" }, + { 31, "FRIBOURG" }, + { 31, "GENEVE" }, + { 31, "LAUSANNE" }, + { 31, "MONTREUX" }, + { 31, "NEUCHATEL" }, + { 32, "AARAU" }, + { 32, "BERN" }, + { 32, "BIENNE" }, + { 32, "FRAUENFELD" }, + { 32, "KONSTANZ" }, + { 32, "LUZERN" }, + { 32, "SCHAFFHAUSEN" }, + { 32, "SOLOTHURN" }, + { 32, "ZUERICH" }, + { 32, "ZUG" }, + { 33, "ADELBODEN" }, + { 33, "GRINDELWALD" }, + { 33, "INTERLAKEN" }, + { 34, "BRIG" }, + { 34, "MARTIGNY" }, + { 34, "SION" }, + { 35, "ALTDORF" }, + { 35, "GLARUS" }, + { 35, "SARNEN" }, + { 35, "SCHWYZ" }, + { 35, "STANS" }, + { 35, "ST.GALLEN" }, + { 36, "CHUR" }, + { 36, "DAVOS" }, + { 37, "FULDA" }, + { 37, "GIESSEN" }, + { 37, "GOETTINGEN" }, + { 37, "KASSEL" }, + { 37, "MARBURG" }, + { 38, "BELLINZONA" }, + { 38, "EDOLO" }, + { 38, "LOCARNO" }, + { 38, "LUGANO" }, + { 39, "AOSTA" }, + { 39, "SESTRIERE" }, + { 40, "ALESSANDRIA" }, + { 40, "BERGAMO" }, + { 40, "BRESCIA" }, + { 40, "MILANO" }, + { 40, "PARMA" }, + { 40, "PIACENZA" }, + { 40, "TORINO" }, + { 40, "VERONA" }, + { 41, "FIRENZE" }, + { 41, "PERUGIA" }, + { 41, "PISA" }, + { 41, "ROMA" }, + { 41, "SIENA" }, + { 41, "VATICANO" }, + { 42, "AMSTERDAM" }, + { 42, "ARNHEM" }, + { 42, "ASSEN" }, + { 42, "DEN HAAG" }, + { 42, "EINDHOVEN" }, + { 42, "HAARLEM" }, + { 42, "LELYSTAD" }, + { 42, "ROTTERDAM" }, + { 42, "S.HERTOGENBOSCH" }, + { 42, "UTRECHT" }, + { 42, "ZWOLLE" }, + { 43, "CANNES" }, + { 43, "GENOVA" }, + { 43, "LA SPEZIA" }, + { 43, "MONACO" }, + { 43, "NICE" }, + { 43, "SAN REMO" }, + { 44, "BOLOGNA" }, + { 44, "NOVA GORIC" }, + { 44, "RIJEKA" }, + { 44, "RIMINI" }, + { 44, "TRIESTE" }, + { 44, "UDINE" }, + { 44, "VENEZIA" }, + { 45, "BASEL" }, + { 45, "BELFORT" }, + { 45, "COLMAR" }, + { 45, "FREIBURG" }, + { 45, "LIESTAL" }, + { 45, "LOERRACH" }, + { 45, "MULHOUSE" }, + { 45, "OFFENBURG" }, + { 45, "STRASBOURG" }, + { 46, "GRAZ" }, + { 46, "KLAGENFURT" }, + { 46, "LIENZ" }, + { 46, "LJUBLJANA" }, + { 46, "MARIBOR" }, + { 46, "SEMMERING" }, + { 46, "VILLACH" }, + { 46, "ZELTWEG" }, + { 47, "BAD GASTEIN" }, + { 47, "INNSBRUCK" }, + { 47, "ISCHGL" }, + { 47, "LANDECK" }, + { 47, "SOELDEN" }, + { 47, "TUXERTAL" }, + { 48, "BAD TOELZ" }, + { 48, "BERCHTESGADEN" }, + { 48, "BISCHOFSHOFEN" }, + { 48, "BREGENZ" }, + { 48, "GARMISCH PARTENKIRCHEN" }, + { 48, "KITZBUEHEL" }, + { 48, "LINDAU" }, + { 48, "SALZBURG" }, + { 48, "SCHLADMING" }, + { 48, "STEYR" }, + { 48, "VADUZ" }, + { 49, "BRATISLAVA" }, + { 49, "EISENSTADT" }, + { 49, "GYOER" }, + { 49, "KLOSTERNEUBURG" }, + { 49, "SOPRON" }, + { 49, "SZOMBATHELY" }, + { 49, "TRENCIN" }, + { 49, "WIEN" }, + { 49, "WIENER NEUSTADT" }, + { 49, "ZALAEGERSZEG" }, + { 50, "BRNO" }, + { 50, "BUDEJOVICE" }, + { 50, "CHEB" }, + { 50, "HAVLICKAV BROD" }, + { 50, "HRADEC/KRA" }, + { 50, "JIHLAVA" }, + { 50, "KARLOVY VARY" }, + { 50, "KLADNO" }, + { 50, "MOST" }, + { 50, "OLOMOUC" }, + { 50, "OSTRAVA" }, + { 50, "PARDUBICE" }, + { 50, "PLZEN" }, + { 50, "PRAHA" }, + { 50, "PREROV" }, + { 50, "PROSTEJOV" }, + { 50, "ST POELTEN" }, + { 50, "ZWETTL" }, + { 51, "CHOMUTOV" }, + { 51, "DECIN" }, + { 51, "LIBEREC" }, + { 51, "OPAVA" }, + { 51, "PIRNA" }, + { 51, "SNIEZKA" }, + { 51, "TEPLICE" }, + { 51, "USTI NAD LABEM" }, + { 51, "WALBRZYCH" }, + { 52, "BERLIN" }, + { 52, "BRANDENBURG" }, + { 52, "FINOW" }, + { 52, "FRANKFURT AN DER ODER" }, + { 52, "GORZOW" }, + { 52, "NEUBRANDENBURG" }, + { 52, "POTSDAM" }, + { 52, "POZNAN" }, + { 52, "STENDAL" }, + { 53, "GOETEBORG" }, + { 53, "HALMSTAD" }, + { 54, "GAEVLE" }, + { 54, "NYKOPING" }, + { 54, "STOCKHOLM" }, + { 54, "UPPSALA" }, + { 54, "VAESTERAS" }, + { 55, "BORGHOLM" }, + { 55, "BORNHOLM" }, + { 55, "KALMAR" }, + { 55, "LINKOEPING" }, + { 55, "RONNE" }, + { 55, "VISBY" }, + { 56, "BORAS" }, + { 56, "JOENKOEPING" }, + { 56, "KARLSTAD" }, + { 56, "OEREBRO" }, + { 57, "BADEN-BADEN" }, + { 57, "DONAUESCHINGEN" }, + { 57, "FREUDENSTADT" }, + { 57, "VILLINGEN-SCHWENNINGEN" }, + { 58, "DRAMMEN" }, + { 58, "FREDRIKSTADEN" }, + { 58, "OSLO" }, + { 58, "TOENSBERG" }, + { 59, "AALEN" }, + { 59, "GOEPPINGEN" }, + { 59, "HEILBRONN" }, + { 59, "PFORZHEIM" }, + { 59, "REUTLINGEN" }, + { 59, "SCHWAEBISCH GMUEND" }, + { 59, "STUTTGART" }, + { 59, "TUEBINGEN" }, + { 60, "NAPOLI" }, + { 61, "ANCONA" }, + { 61, "PESCARA" }, + { 61, "SAN MARINO" }, + { 62, "BARI" }, + { 62, "FOGGIA" }, + { 62, "LECCE" }, + { 63, "BEKESCSABA" }, + { 63, "BRANSKA" }, + { 63, "BUDAPEST" }, + { 63, "DEBRECEN" }, + { 63, "DUNAIJVAROS" }, + { 63, "EGER" }, + { 63, "ERD" }, + { 63, "HODMEZOVASARHELY" }, + { 63, "KAPOSVAR" }, + { 63, "KECSKEMET" }, + { 63, "KOSICE" }, + { 63, "MISKOLC" }, + { 63, "NAGYKANIZSA" }, + { 63, "NYIREGYHAZA" }, + { 63, "OZD" }, + { 63, "PECS" }, + { 63, "SALGOTARJAN" }, + { 63, "SIOFOK" }, + { 63, "SZEGED" }, + { 63, "SZEKESFEEVAR" }, + { 63, "SZOLNOK" }, + { 63, "TATABANYA" }, + { 63, "VESZPREM" }, + { 64, "MADRID" }, + { 65, "BILBAO" }, + { 66, "CATANIA" }, + { 66, "COSENZA" }, + { 66, "MESSINA" }, + { 66, "PALERMO" }, + { 66, "REGGIO CALABRIA" }, + { 67, "IBIZA" }, + { 67, "MAHON" }, + { 67, "PALMA DE MALLORCA" }, + { 68, "VALENCIA" }, + { 69, "BARCELONA" }, + { 69, "FIGUERES" }, + { 69, "GIRONA" }, + { 69, "LLORET DE MAR" }, + { 70, "ANDORRA LA VELLA" }, + { 71, "SEVILLA" }, + { 72, "LISBOA" }, + { 73, "AJACCIO" }, + { 73, "BASTIA" }, + { 73, "CAGLIARI" }, + { 73, "SASSARI" }, + { 74, "GIJON" }, + { 75, "CORK" }, + { 75, "GALWAY" }, + { 75, "LIMERICK" }, + { 76, "BELFAST" }, + { 76, "DUBLIN" }, + { 77, "ABERDEEN" }, + { 77, "EDINBURGH" }, + { 77, "GLASGOW" }, + { 77, "ISLE OF MAN" }, + { 78, "BERGEN" }, + { 78, "STAVANGER" }, + { 79, "TRONDHEIM" }, + { 80, "SUNDSVALL" }, + { 81, "GDANSK" }, + { 81, "OLSZTYN" }, + { 81, "SLUPSK" }, + { 82, "BIALYSTOK" }, + { 82, "BYDGOSZCZ" }, + { 82, "LODZ" }, + { 82, "LUBLIN" }, + { 82, "TORUN" }, + { 82, "WARSZAWA" }, + { 83, "BIELSKO" }, + { 83, "KATOWICE" }, + { 83, "KIELCE" }, + { 83, "KRAKOW" }, + { 83, "OPOLE" }, + { 83, "RZESZOW" }, + { 83, "ZAKOPANE" }, + { 84, "UMEA" }, + { 85, "FALUN" }, + { 85, "OESTERSUND" }, + { 86, "SAMEDAN" }, + { 87, "OSIJEK" }, + { 87, "ZAGREB" }, + { 88, "ZERMATT" }, + { 89, "SPLIT" }, + { 24, "Doerphof" }, + { -1, NULL } +}; + +void display_city(const char *search) +{ + int i; + int found = 0; + + for (i = 0; city_list[i].name; i++) { + if (strcasestr(city_list[i].name, search)) { + found++; + printf("City %s is located in region %d.\n", city_list[i].name, city_list[i].region); + } + } + if (!found) { + printf("No city found for '%s', try larger city.\n", search); + } +} + diff --git a/src/dcf77/cities.h b/src/dcf77/cities.h new file mode 100644 index 0000000..2e512fe --- /dev/null +++ b/src/dcf77/cities.h @@ -0,0 +1,3 @@ + +void display_city(const char *search); + diff --git a/src/dcf77/dcf77.c b/src/dcf77/dcf77.c index 81b41e4..5314803 100644 --- a/src/dcf77/dcf77.c +++ b/src/dcf77/dcf77.c @@ -1,5 +1,5 @@ -/* implementation of DCF77 transmitter and receiver +/* implementation of DCF77 transmitter and receiver, including weather * * (C) 2022 by Andreas Eversberg * All Rights Reserved @@ -28,6 +28,7 @@ #include #include "../libdebug/debug.h" #include "dcf77.h" +#include "weather.h" double get_time(void); @@ -35,7 +36,6 @@ double get_time(void); #define TEST_FREQUENCY 1000 #define CARRIER_BANDWIDTH 10.0 #define SAMPLE_CLOCK 1000 -#define CLOCK_1S 1.0 #define CLOCK_BANDWIDTH 0.1 #define REDUCTION_FACTOR 0.15 #define REDUCTION_TH 0.575 @@ -43,6 +43,9 @@ double get_time(void); #define level2db(level) (20 * log10(level)) +/* uncomment to speed up transmission by factor 10 and feed the data to the receiver */ +//#define DEBUG_LOOP + static int fast_math = 0; static float *sin_tab = NULL, *cos_tab = NULL; @@ -50,6 +53,292 @@ const char *time_zone[4] = { "???", "CEST", "CET", "???" }; const char *week_day[8] = { "???", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; const char *month_name[13] = { "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +const char *datasets_0_59[8] = { + "Maximum values 1st day (today)", + "Minimum values 1st day (today)", + "Maximum values 2nd day (tomorrow)", + "Minimum values 2nd day (tomorrow)", + "Maximum values 3rd day (in two days)", + "Minimum values 3rd day (in two days)", + "Maximum values 4th day (in three days)", + "Wind and weather anomaly 4th day (in three days)", +}; + +const char *datasets_60_89[2] = { + "Maximum values 1st day (following day)", + "Maximum values 2nd day (2nd following day)", +}; + +const char *region[90] = { + "F - Bordeaux, Aquitaine (Suedwestfrankreich)", + "F - La Rochelle, Poitou-Charentes (Westkueste Frankreichs)", + "F - Paris, Ile-de-France (Pariser Becken)", + "F - Brest, Bretagne", + "F - Clermont-Ferrand (Massif Central), Auvergne (Zentralmassiv)", + "F - Beziers, Languedoc-Roussillon", + "B - Bruxelles, Brussel (Benelux)", + "F - Dijon (Bourgogne), Bourgogne (Ostfrankreich / Burgund)", + "F - Marseille, Provence-Alpes-Côte d'Azur", + "F - Lyon (Rhone-Alpes), Rhone-Alpes (Rhonetal)", + "F - Grenoble (Savoie), Rhone-Alpes (Franz. Alpen)", + "CH - La Chaux de Fond, Jura", + "D - Frankfurt am Main, Hessen (Unterer Rheingraben)", + "D - Trier, Westliches Mittelgebirge", + "D - Duisburg, Nordrhein-Westfalen", + "GB - Swansea, Wales (Westl. England / Wales)", + "GB - Manchester, England (Noerdliches England)", + "F - le Havre, Haute-Normandie (Normandie)", + "GB - London, England (Suedostengland / London)", + "D - Bremerhaven, Bremen (Nordseekueste)", + "DK - Herning, Ringkobing (Nordwestliches Juetland)", + "DK - Arhus, Arhus (Oestliches Juetland)", + "D - Hannover, Niedersachsen (Norddeutschland)", + "DK - Kobenhavn, Staden Kobenhaven (Seeland)", + "D - Rostock, Mecklenburg-Vorpommern (Ostseekueste)", + "D - Ingolstadt, Bayern (Donautal)", + "D - Muenchen, Bayern (Suedbayern)", + "I - Bolzano, Trentino-Alto Adige (Suedtirol)", + "D - Nuernberg, Bayern (Nordbayern)", + "D - Leipzig, Sachsen", + "D - Erfurt, Thueringen", + "CH - Lausanne, Genferseeregion (Westl. Schweizer Mitteland)", + "CH - Zuerich (Oestl. Schweizer Mittelland)", + "CH - Adelboden (Westl. Schweizer Alpennordhang)", + "CH - Sion, Wallis", + "CH - Glarus, Oestlicher Schweizer Alpennordhang", + "CH - Davos, Graubuenden", + "D - Kassel, Hessen (Mittelgebirge Ost)", + "CH - Locarno, Tessin", + "I - Sestriere, Piemont. Alpen", + "I - Milano, Lombardia (Poebene)", + "I - Roma, Lazio (Toskana)", + "NL - Amsterdam, Noord-Holland (Holland)", + "I - Genova, Liguria (Golf von Genua)", + "I - Venezia, Veneto (Pomuendung)", + "F - Strasbourg, Alsace (Oberer Rheingraben)", + "A - Klagenfurt, Kaernten (Oesterreich. Alpensuedhang)", + "A - Innsbruck, Tirol (Inneralpine Gebiete Oesterreichs)", + "A - Salzburg, Bayr. / Oesterreich. Alpennordhang", + "SK (Oesterreich / Slovakia) - Wien / Bratislava", + "CZ - Praha, Prag (Tschechisches Becken)", + "CZ - Decin, Severocesky (Erzgebirge)", + "D - Berlin, Ostdeutschland", + "S - Goeteborg, Goeteborgs och Bohus Laen (Westkueste Schweden)", + "S - Stockholm, Stockholms Laen (Stockholm)", + "S - Kalmar, Kalmar Laen (Schwedische Ostseekueste)", + "S - Joenkoeping, Joenkoepings Laen (Suedschweden)", + "D - Donaueschingen, Baden-Wuerttemberg (Schwarzwald / Schwaebische Alb)", + "N - Oslo", + "D - Stuttgart, Baden-Wuerttemberg (Noerdl. Baden Wuerttemberg)", + "I - Napoli", + "I - Ancona", + "I - Bari", + "HU - Budapest", + "E - Madrid", + "E - Bilbao", + "I - Palermo", + "E - Palma de Mallorca", + "E - Valencia", + "E - Barcelona", + "AND - Andorra", + "E - Sevilla", + "P - Lissabon", + "I - Sassari, (Sardinien / Korsika)", + "E - Gijon", + "IRL - Galway", + "IRL - Dublin", + "GB - Glasgow", + "N - Stavanger", + "N - Trondheim", + "S - Sundsvall", + "PL - Gdansk", + "PL - Warszawa", + "PL - Krakow", + "S - Umea", + "S - Oestersund", + "CH - Samedan", + "CR - Zagreb", + "CH - Zermatt", + "CR - Split", +}; + +const char *weathers_day[16] = { + "Reserved", + "Sunny", + "Partly clouded", + "Mostly clouded", + "Overcast", + "Heat storms", + "Heavy Rain", + "Snow", + "Fog", + "Sleet", + "Rain shower", + "Light rain", + "Snow showers", + "Frontal storms", + "Stratus cloud", + "Sleet storms", +}; + +const char *weathers_night[16] = { + "Reserved", + "Clear", + "Partly clouded", + "Mostly clouded", + "Overcast", + "Heat storms", + "Heavy Rain", + "Snow", + "Fog", + "Sleet", + "Rain shower", + "Light rain", + "Snow showers", + "Frontal storms", + "Stratus cloud", + "Sleet storms", +}; + +const char *extremeweathers[16] = { + "None", + "Heavy Weather 24 hrs.", + "Heavy weather Day", + "Heavy weather Night", + "Storm 24hrs.", + "Storm Day", + "Storm Night", + "Wind gusts Day", + "Wind gusts Night", + "Icy rain morning", + "Icy rain evening", + "Icy rain night", + "Fine dust", + "Ozon", + "Radiation", + "High water", +}; + +const char *probabilities[8] = { + "0 %", + "15 %", + "30 %", + "45 %", + "60 %", + "75 %", + "90 %", + "100 %", +}; + +const char *winddirections[16] = { + "North", + "Northeast", + "East", + "Southeast", + "South", + "Southwest", + "West", + "Northwest", + "Changeable", + "Foen", + "Biese N/O", + "Mistral N", + "Scirocco S", + "Tramont W", + "reserved", + "reserved", +}; + +const char *windstrengths[8] = { + "0", + "0-2", + "3-4", + "5-6", + "7", + "8", + "9", + ">=10", +}; + +const char *yesno[2] = { + "No", + "Yes", +}; +const char *anomaly1[4] = { + "Same Weather", + "Jump 1", + "Jump 2", + "Jump 3", +}; +const char *anomaly2[4] = { + "0-2 hrs", + "2-4 hrs", + "5-6 hrs", + "7-8 hrs", +}; + +/* show a list of weather data values */ +void list_weather(void) +{ + time_t timestamp, t; + struct tm *tm; + int i, j; + + /* get time stamp of this day, but at 22:00 UTC */ + timestamp = floor(get_time()); + timestamp -= timestamp % 86400; + timestamp += 79200; + + printf("\n"); + printf("List of all regions\n"); + printf("-------------------\n"); + for (i = 0; i < 90; i++) { + printf("Region: %2d = %s\n", i, region[i]); + for (j = 0; j < 8; j++) { + /* get local time where transmission starts */ + if (i < 60) { + t = timestamp + 180 * i + 10800 * j; + tm = localtime(&t); + printf(" -> Transmission at %02d:%02d of %s\n", tm->tm_hour, tm->tm_min, datasets_0_59[j]); + } else if (j < 2) { + t = timestamp + 180 * (i - 60) + 10800 * 7 + 5400 * j; + tm = localtime(&t); + printf(" -> Transmission at %02d:%02d of %s\n", tm->tm_hour, tm->tm_min, datasets_60_89[j]); + } + } + } + + printf("\n"); + printf("List of all weathers\n"); + printf("--------------------\n"); + for (i = 0; i < 16; i++) { + if (i == 1) + printf("Weather: %2d = %s (day) %s (night)\n", i, weathers_day[i], weathers_night[i]); + else + printf("Weather: %2d = %s (day and night)\n", i, weathers_day[i]); + } + printf("\n"); + + printf("List of all extreme weathers\n"); + printf("----------------------------\n"); + for (i = 1; i < 16; i++) { + printf("Extreme: %2d = %s\n", i, extremeweathers[i]); + } + printf("\n"); +} + +static const char *show_bits(uint64_t value, int bits) +{ + static char bit[128]; + int i; + + for (i = 0; i < bits; i++) + bit[i] = '0' + ((value >> i) & 1); + sprintf(bit + i, "(%" PRIu64 ")", value); + + return bit; +} + /* global init */ int dcf77_init(int _fast_math) { @@ -82,6 +371,7 @@ void dcf77_exit(void) } } +/* instance creation */ dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone) { dcf77_t *dcf77 = NULL; @@ -150,6 +440,7 @@ dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone) dcf77->dmp_input_level = display_measurements_add(&dcf77->dispmeas, "Input Level", "%.0f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -100.0, 0.0, -INFINITY); dcf77->dmp_signal_level = display_measurements_add(&dcf77->dispmeas, "Signal Level", "%.0f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -100.0, 0.0, -INFINITY); dcf77->dmp_signal_quality = display_measurements_add(&dcf77->dispmeas, "Signal Qualtiy", "%.0f %%", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 100.0, -INFINITY); + dcf77->dmp_current_second = display_measurements_add(&dcf77->dispmeas, "Current Second", "%.0f", DISPLAY_MEAS_LAST, DISPLAY_MEAS_LEFT, 0.0, 59.0, -INFINITY); } if (tx->enable) @@ -157,9 +448,17 @@ dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone) if (rx->enable) PDEBUG(DDCF77, DEBUG_INFO, "DCF77 receiver has been created.\n"); +#if 0 + void rx_frame_test(dcf77_t *dcf77, const char *string); + rx_frame_test(dcf77, "00001000011111100100101101001010000110000110011100001010000"); + rx_frame_test(dcf77, "00101010111111000100111101000010000110000110011100001010000"); + rx_frame_test(dcf77, "00011000101111000100100011000010000110000110011100001010000"); +#endif + return dcf77; } +/* instance destruction */ void dcf77_destroy(dcf77_t *dcf77) { if (dcf77) { @@ -171,33 +470,264 @@ void dcf77_destroy(dcf77_t *dcf77) PDEBUG(DDCF77, DEBUG_INFO, "DCF77 has been destroyed.\n"); } -/* set inital time stamp at the moment the stream starts */ -void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp) +static void display_weather_temperature(const char *desc, uint32_t weather) +{ + int value; + + value = (weather >> 16) & 0x3f; + switch (value) { + case 0: + printf("%s%s = < -21 degrees C\n", desc, show_bits(value, 6)); + break; + case 63: + printf("%s%s = > 40 degrees C\n", desc, show_bits(value, 6)); + break; + default: + printf("%s%s = %d degrees C\n", desc, show_bits(value, 6), value - 22); + } +} + +/* + * TX part + */ + +/* Adjust given time stamp to the time stamp when the weather of the given + * region istransmitted. An offset is used to start several minutes before + * the transmission of the region starts, so the receiver has time to sync + * to the signal first, so it will not miss that weather info. + * + * Note that this will only set the start of transmitting weather of the + * current day (and day temperature). + */ +time_t dcf77_start_weather(time_t timestamp, int region, int offset) +{ + int hour, min; + + /* hour+min at UTC */ + if (region < 60) { + /* first dataset starts at 22:00 UTC */ + hour = (22 + region / 20) % 24; + } else { + /* seventh dataset starts at 19:00 UTC */ + hour = (19 + (region - 60) / 20) % 24; + } + min = (region % 20) * 3; + PDEBUG(DDCF77, DEBUG_INFO, "Setting UTC time for region %d to %02d:%02d minutes.\n", region, hour, min); + + /* reset to 0:00 UTC at same day */ + timestamp -= (timestamp % 86400); + + /* add time to start */ + timestamp += hour * 3600 + min * 60; + + /* substract offset */ + PDEBUG(DDCF77, DEBUG_INFO, "Setting timestamp offset to %d minutes.\n", offset); + timestamp -= 60 * offset; + + return timestamp; +} + +/* set weather to transmit on all regions */ +void dcf77_set_weather(dcf77_t *dcf77, int weather_day, int weather_night, int extreme, int rain, int wind_dir, int wind_bft, int temperature_day, int temperature_night) { dcf77_tx_t *tx = &dcf77->tx; - double now; - time_t t; - /* get time stamp */ - if (timestamp < 0) - now = get_time(); + tx->weather = 1; + tx->weather_day = weather_day; + tx->weather_night = weather_night; + tx->rain = rain; + tx->extreme = extreme; + tx->wind_dir = wind_dir; + tx->wind_bft = wind_bft; + tx->temperature_day = temperature_day; + tx->temperature_night = temperature_night; +} + +/* generate weather frame from weather data */ +static uint64_t generate_weather(time_t timestamp, int minute, int utc_hour, int weather_day, int weather_night, int extreme, int rain, int wind_dir, int wind_bft, int temperature_day, int temperature_night) +{ + int dataset = ((utc_hour + 2) % 24) * 20 + (minute / 3); /* data sets since 22:00 UTC */ + struct tm *tm; + uint64_t key; + uint32_t weather; + int value, temperature; + int i; + int best, diff; + + /* generate key from time stamp of next minute */ + timestamp += 60; + tm = localtime(×tamp); + key = 0; + key |= (uint64_t)(tm->tm_min % 10) << 0; + key |= (uint64_t)(tm->tm_min / 10) << 4; + key |= (uint64_t)(tm->tm_hour % 10) << 8; + key |= (uint64_t)(tm->tm_hour / 10) << 12; + key |= (uint64_t)(tm->tm_mday % 10) << 16; + key |= (uint64_t)(tm->tm_mday / 10) << 20; + key |= (uint64_t)((tm->tm_mon + 1) % 10) << 24; + key |= (uint64_t)((tm->tm_mon + 1) / 10) << 28; + if (tm->tm_wday > 0) + key |= (uint64_t)(tm->tm_wday) << 29; else - now = timestamp; - t = floor(now); + key |= (uint64_t)(7) << 29; + key |= (uint64_t)(tm->tm_year % 10) << 32; + key |= (uint64_t)((tm->tm_year / 10) % 10) << 36; + + /* generate weather data */ + timestamp -= 120; + weather = 0; + PDEBUG(DFRAME, DEBUG_INFO, "Encoding weather for dataset %d/480\n", dataset); + printf("Peparing Weather INFO\n"); + printf("---------------------\n"); + printf("Time (UTC): %02d:%02d\n", (int)(timestamp / 3600) % 24, (int)(timestamp / 60) % 60); + /* dataset and region for 0..59 */ + printf("Dataset: %s\n", datasets_0_59[dataset / 60]); + value = dataset % 60; + printf("Region: %d = %s\n", value, region[value]); + /* calc. weather of region */ + if (weather_day < 0 || weather_day > 15) + weather_day = 1; + weather |= weather_day << 0; + if (weather_night < 0 || weather_night > 15) + weather_night = 1; + weather |= weather_night << 4; + /* calc. temperature of region 0..59 (day/night) or region 60..89 (day) */ + if (((dataset / 60) & 1) == 0 || (dataset / 60) == 7) + temperature = temperature_day + 22; + else + temperature = temperature_night + 22; + if (temperature < 0) + temperature = 0; + if (temperature > 63) + value = 63; + weather |= temperature << 16; + /* show weather of region 0..59 */ + if ((dataset / 60) < 7) { + printf("Weather (day): %s = %s\n", show_bits(weather_day, 4), weathers_day[weather_day]); + printf("Weather (night): %s = %s\n", show_bits(weather_night, 4), weathers_night[weather_night]); + } + /* show extreme/wind/rain of region 0..59 */ + if (((dataset / 60) & 1) == 0) { + /* even datasets, this is 'Day' data */ + if (extreme < 0 || extreme > 15) + value = 0; + else + value = extreme; + printf("Extreme weather: %s = %s\n", show_bits(value, 4), extremeweathers[value]); + weather |= value << 8; + best = 0; + for (i = 0; i < 8; i++) { + diff = abs(atoi(probabilities[i]) - rain); + if (i == 0 || diff < best) { + best = diff; + value = i; + } + } + printf("Rain Probability: %s = %s (best match for %d)\n", show_bits(value, 3), probabilities[value], rain); + weather |= value << 12; + value = 0; + printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]); + weather |= value << 15; + display_weather_temperature("Temperature (day): ", weather); + } else { + /* odd datasets, this is 'Night' data */ + if (wind_dir < 0 || wind_dir > 15) + value = 8; + else + value = wind_dir; + printf("Wind direction: %s = %s\n", show_bits(value, 4), winddirections[value]); + weather |= value << 8; + if (wind_bft < 1) + value = 0; + else + if (wind_bft < 7) + value = (wind_bft + 1) / 2; + else + if (wind_bft < 10) + value = wind_bft - 3; + else + value = 7; + printf("Wind strength: %s = %s (best match for %d)\n", show_bits(value, 3), windstrengths[value], wind_bft); + weather |= value << 12; + value = 0; + printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]); + weather |= value << 15; + value = temperature_night + 22; + if (value < 0) + value = 0; + if (value > 63) + value = 63; + weather |= value << 16; + if ((dataset / 60) < 7) + display_weather_temperature("Temperature (night): ", weather); + } + /* show weather and temperature of of region 60..89 */ + if ((dataset / 60) == 7) { + printf("Dataset: %s\n", 60 + datasets_60_89[(dataset % 60) / 30]); + value = 60 + (dataset % 30); + printf("Region: %d = %s\n", value, region[value]); + printf("Weather (day): %s = %s\n", show_bits(weather_day, 4), weathers_day[weather_day]); + printf("Weather (night): %s = %s\n", show_bits(weather_night, 4), weathers_night[weather_night]); + display_weather_temperature("Temperature: ", weather); + } + + /* the magic '10' bit string */ + weather |= 0x1 << 22; + + /* encode */ + return weather_encode(weather, key); +} + +/* transmit chunk of weather data for each minute */ +static uint16_t tx_weather(dcf77_tx_t *tx, time_t timestamp, int minute, int hour, int zone) +{ + int index = (minute + 2) % 3; + int utc_hour; + uint16_t chunk; + + if (index == 0) { + /* convert hour to UTC */ + utc_hour = hour - 1; + if (zone & 1) + utc_hour--; + if (utc_hour < 0) + utc_hour += 24; + /* in index 0 we transmit minute + 1 (next minute), so we substract 1 */ + tx->weather_cipher = generate_weather(timestamp, (minute + 59) % 60, utc_hour, tx->weather_day, tx->weather_night, tx->extreme, tx->rain, tx->wind_dir, tx->wind_bft, tx->temperature_day, tx->temperature_night); + PDEBUG(DFRAME, DEBUG_INFO, "Transmitting first chunk of weather info.\n"); + chunk = (tx->weather_cipher & 0x3f) << 1; /* bit 2-7 */ + chunk |= (tx->weather_cipher & 0x0fc0) << 2; /* bit 9-14 */ + tx->weather_cipher >>= 12; + return chunk; + } + + PDEBUG(DFRAME, DEBUG_INFO, "Transmitting %s chunk of weather info.\n", (index == 1) ? "second" : "third"); + chunk = tx->weather_cipher & 0x3fff; + tx->weather_cipher >>= 14; + return chunk; +} + +/* set inital time stamp at the moment the stream starts */ +void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp, double sub_sec) +{ + dcf77_tx_t *tx = &dcf77->tx; + /* current second within minute */ - tx->second = t % 60; + tx->second = timestamp % 60; /* time stamp of next minute */ - tx->timestamp = t - tx->second + 60; + tx->timestamp = timestamp - tx->second + 60; /* wave within current second */ - tx->wave = floor(fmod(now, 1.0) * (double)tx->waves_sec); + tx->wave = sub_sec * (double)tx->waves_sec; /* silence until next second begins */ tx->symbol = 'm'; tx->level = 0; } +/* transmit one symbol = one second */ static char tx_symbol(dcf77_t *dcf77, time_t timestamp, int second) { dcf77_tx_t *tx = &dcf77->tx; char symbol; + int i, j; /* generate frame */ if (second == 0 || !tx->data_frame) { @@ -205,18 +735,19 @@ static char tx_symbol(dcf77_t *dcf77, time_t timestamp, int second) int isdst_next_hour, wday, zone; uint64_t frame = 0, p; + /* get DST next hour */ timestamp += 3600; tm = localtime(×tamp); timestamp -= 3600; - if (!tm) { -error_tm: - PDEBUG(DDCF77, DEBUG_ERROR, "Failed to get local time of time stamp!\n"); - return 'm'; - } isdst_next_hour = tm->tm_isdst; tm = localtime(×tamp); - if (!tm) - goto error_tm; + + /* get weather data */ + if (tx->weather) { + frame |= tx_weather(tx, timestamp, tm->tm_min, tm->tm_hour, (tm->tm_isdst > 0) ? 1 : 2) << 1; + /* calling tx_weather() destroys tm, because it is a pointer to a global variable. now we fix it */ + tm = localtime(×tamp); + } if (tm->tm_wday > 0) wday = tm->tm_wday; @@ -228,7 +759,7 @@ error_tm: else zone = 2; - PDEBUG(DDCF77, DEBUG_NOTICE, "The time transmitting: %s %s %d %02d:%02d:00 %s %02d\n", week_day[wday], month_name[tm->tm_mon + 1], tm->tm_mday, tm->tm_hour, tm->tm_min, time_zone[zone], tm->tm_year + 1900); + PDEBUG(DDCF77, DEBUG_NOTICE, "The time transmitting: %s %s %d %02d:%02d:%02d %s %02d\n", week_day[wday], month_name[tm->tm_mon + 1], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, time_zone[zone], tm->tm_year + 1900); if ((tm->tm_isdst > 0) != (isdst_next_hour > 0)) frame |= (uint64_t)1 << 16; @@ -270,19 +801,40 @@ error_tm: frame |= (uint64_t)(p & 1) << 58; tx->data_frame = frame; + + for (i = 0, j = 0; i < 59; i++) { + if (i == 1 || i == 15 || i == 21 || i == 29 || i == 36 || i == 42 || i == 45 || i == 50) + tx->data_string[j++] = ' '; + tx->data_string[j++] = '0' + ((frame >> i) & 1); + } + tx->data_string[j] = '\0'; + PDEBUG(DDSP, DEBUG_INFO, "Start transmission of frame:\n"); + PDEBUG(DDSP, DEBUG_INFO, "0 Wetterdaten Info 1 Minute P StundeP Tag WoT Monat Jahr P\n"); + PDEBUG(DDSP, DEBUG_INFO, "%s\n", tx->data_string); } if (second == 59) symbol = 'm'; - else symbol = ((tx->data_frame >> second) & 1) + '0'; + else + symbol = ((tx->data_frame >> second) & 1) + '0'; PDEBUG(DDSP, DEBUG_DEBUG, "Trasmitting symbol '%c' (Bit %d)\n", symbol, second); return symbol; } +static void rx_symbol(dcf77_t *dcf77, char symbol); + +/* encode one minute */ void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length) { +#ifdef DEBUG_LOOP + /* mute and speed up by factor 20 */ + memset(samples, 0, sizeof(*samples) * length); + sample_t test_sample[length * 20]; + samples = test_sample; + length *= 20; +#endif dcf77_tx_t *tx = &dcf77->tx; double carrier_phase, test_phase; int i; @@ -310,6 +862,9 @@ void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length) tx->timestamp += 60; } tx->symbol = tx_symbol(dcf77, tx->timestamp, tx->second); +#ifdef DEBUG_LOOP + rx_symbol(dcf77, tx->symbol); +#endif } switch (tx->symbol) { case '0': @@ -345,7 +900,146 @@ void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length) tx->test_phase = test_phase; } -static void rx_frame(uint64_t frame) +/* + * RX part + */ + +/* display weather data from weather frame */ +static void display_weather(uint32_t weather, int minute, int utc_hour) +{ + int dataset = ((utc_hour + 2) % 24) * 20 + (minute / 3); /* data sets since 22:00 UTC */ + int value; + + PDEBUG(DFRAME, DEBUG_INFO, "Decoding weather for dataset %d/480\n", dataset); + printf("Received Weather INFO\n"); + printf("---------------------\n"); + printf("Time (UTC): %02d:%02d\n", utc_hour, minute); + printf("Dataset: %s\n", datasets_0_59[dataset / 60]); + value = dataset % 60; + printf("Region: %d = %s\n", value, region[value]); + if ((dataset / 60) < 7) { + value = (weather >> 0) & 0xf; + printf("Weather (day): %s = %s\n", show_bits(value, 4), weathers_day[value]); + value = (weather >> 4) & 0xf; + printf("Weather (night): %s = %s\n", show_bits(value, 4), weathers_night[value]); + } + if (((dataset / 60) & 1) == 0) { + /* even datasets, this is 'Day' data */ + if (((weather >> 15) & 1) == 0) { + value = (weather >> 8) & 0xf; + printf("Extreme weather: %s = %s\n", show_bits(value, 4), extremeweathers[value]); + } else { + value = (weather >> 8) & 0x3; + printf("Relative weather: %s = %s\n", show_bits(value, 2), anomaly1[value]); + value = (weather >> 10) & 0x3; + printf("Sunshine: %s = %s\n", show_bits(value, 2), anomaly1[value]); + } + value = (weather >> 12) & 0x7; + printf("Rain Probability: %s = %s\n", show_bits(value, 3), probabilities[value]); + value = (weather >> 15) & 0x1; + printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]); + display_weather_temperature("Temperature (day): ", weather); + } else { + /* odd datasets, this is 'Night' data */ + if (((weather >> 15) & 1) == 0) { + value = (weather >> 8) & 0xf; + printf("Wind direction: %s = %s\n", show_bits(value, 4), winddirections[value]); + } else { + value = (weather >> 8) & 0x3; + printf("Relative weather: %s = %s\n", show_bits(value, 2), anomaly1[value]); + value = (weather >> 10) & 0x3; + printf("Sunshine: %s = %s\n", show_bits(value, 2), anomaly1[value]); + } + value = (weather >> 12) & 0x7; + printf("Wind strength: %s = %s\n", show_bits(value, 3), windstrengths[value]); + value = (weather >> 15) & 0x1; + printf("Anomaly: %s = %s\n", show_bits(value, 1), yesno[value]); + if ((dataset / 60) < 7) + display_weather_temperature("Temperature (night): ", weather); + } + if ((dataset / 60) == 7) { + printf("Dataset: %s\n", 60 + datasets_60_89[(dataset % 60) / 30]); + value = 60 + (dataset % 30); + printf("Region: %d = %s\n", value, region[value]); + value = (weather >> 0) & 0xf; + printf("Weather (day): %s = %s\n", show_bits(value, 4), weathers_day[value]); + value = (weather >> 4) & 0xf; + printf("Weather (night): %s = %s\n", show_bits(value, 4), weathers_night[value]); + display_weather_temperature("Temperature: ", weather); + } +} + +/* reset weather frame */ +static void rx_weather_reset(dcf77_rx_t *rx) +{ + rx->weather_index = 0; + rx->weather_cipher = 0; + rx->weather_key = 0; +} + +/* receive weather chunk */ +static void rx_weather(dcf77_rx_t *rx, int minute, int hour, int zone, uint64_t frame) +{ + int index = (minute + 2) % 3; + int utc_hour; + int32_t weather; + + if (rx->weather_index == 0 && index != 0) { + PDEBUG(DFRAME, DEBUG_INFO, "Skipping weather info chunk, waiting for new start of weather info.\n"); + return; + } + + if (index == 0) { + rx_weather_reset(rx); + rx->weather_cipher |= (frame >> 2) & 0x3f; /* bit 2-7 */ + rx->weather_cipher |= (frame >> 3) & 0x0fc0; /* bit 9-14 */ + rx->weather_index++; + if (((frame & 0x0002)) || ((frame & 0x0100)) || !rx->weather_cipher) { + PDEBUG(DFRAME, DEBUG_INFO, "There is no weather info in this received minute.\n"); + rx_weather_reset(rx); + return; + } + PDEBUG(DFRAME, DEBUG_INFO, "Got first chunk of weather info.\n"); + return; + } + if (rx->weather_index == 1 && index == 1) { + PDEBUG(DFRAME, DEBUG_INFO, "Got second chunk of weather info.\n"); + rx->weather_cipher |= (frame << 11) & 0x3fff000; /* bit 1-14 */ + rx->weather_key |= (frame >> 21) & 0x7f; + rx->weather_key |= ((frame >> 29) & 0x3f) << 8; + rx->weather_key |= ((frame >> 36) & 0x3f) << 16; + rx->weather_key |= ((frame >> 45) & 0x1f) << 24; + rx->weather_key |= ((frame >> 42) & 0x07) << 29; + rx->weather_key |= ((frame >> 50) & 0xff) << 32; + rx->weather_index++; + return; + } + if (rx->weather_index == 2 && index == 2) { + PDEBUG(DFRAME, DEBUG_INFO, "Got third chunk of weather info.\n"); + rx->weather_cipher |= (frame << 25) & 0xfffc000000; /* bit 1-14 */ + weather = weather_decode(rx->weather_cipher, rx->weather_key); + if (weather < 0) + PDEBUG(DFRAME, DEBUG_NOTICE, "Failed to decrypt weather info, checksum error.\n"); + else { + /* convert hour to UTC */ + utc_hour = hour - 1; + if (zone & 1) + utc_hour--; + if (utc_hour < 0) + utc_hour += 24; + /* in index 2 we transmit minute + 3 (next minute), so we substract 3 */ + display_weather(weather, (minute + 57) % 60, utc_hour); + } + rx_weather_reset(rx); + return; + } + + rx_weather_reset(rx); + PDEBUG(DFRAME, DEBUG_INFO, "Got weather info chunk out of order, waiting for new start of weather info.\n"); +} + +/* decode time from received data */ +static void rx_frame(dcf77_rx_t *rx, uint64_t frame) { int zone; int minute_one, minute_ten, minute = -1; @@ -428,15 +1122,34 @@ static void rx_frame(uint64_t frame) PDEBUG(DFRAME, DEBUG_INFO, "Year : %02d\n", year); } - if (minute >= 0 && hour >= 0 && day >= 0 && wday >= 0 && month >= 0 && year >= 0) + if (minute >= 0 && hour >= 0 && day >= 0 && wday >= 0 && month >= 0 && year >= 0) { PDEBUG(DDCF77, DEBUG_NOTICE, "The received time is: %s %s %d %02d:%02d:00 %s 20%02d\n", week_day[wday], month_name[month], day, hour, minute, time_zone[zone], year); - else + rx_weather(rx, minute, hour, zone, frame); + } else { PDEBUG(DDCF77, DEBUG_NOTICE, "The received time is invalid!\n"); + rx_weather_reset(rx); + } } +/* test routing for test data */ +void rx_frame_test(dcf77_t *dcf77, const char *string) +{ + uint64_t frame = 0; + int i; + + puts(string); + for (i = 0; i < 59; i++) { + frame |= (uint64_t)(string[i] & 1) << i; + } + + rx_frame(&dcf77->rx, frame); +} + +/* receive one symbol = one second */ static void rx_symbol(dcf77_t *dcf77, char symbol) { dcf77_rx_t *rx = &dcf77->rx; + double second = -NAN; PDEBUG(DDSP, DEBUG_DEBUG, "Received symbol '%c'\n", symbol); @@ -445,33 +1158,48 @@ static void rx_symbol(dcf77_t *dcf77, char symbol) PDEBUG(DDSP, DEBUG_INFO, "Reception of frame has started\n"); rx->data_receive = 1; rx->data_index = 0; + rx->string_index = 0; + second = 0; } } else { if (symbol == 'm') { if (rx->data_index == 59) { - rx->data_string[rx->data_index] = '\0'; + rx->data_string[rx->string_index] = '\0'; rx->data_index = 0; - PDEBUG(DDSP, DEBUG_INFO, "Received complete frame: %s (0x%016" PRIx64 ")\n", rx->data_string, rx->data_frame); - rx_frame(rx->data_frame); + rx->string_index = 0; + PDEBUG(DDSP, DEBUG_INFO, "Received complete frame:\n"); + PDEBUG(DDSP, DEBUG_INFO, "0 Wetterdaten Info 1 Minute P StundeP Tag WoT Monat Jahr P\n"); + PDEBUG(DDSP, DEBUG_INFO, "%s\n", rx->data_string); + rx_frame(rx, rx->data_frame); + second = 0; } else { PDEBUG(DDSP, DEBUG_INFO, "Short read, frame too short\n"); rx->data_index = 0; + rx->string_index = 0; + rx_weather_reset(rx); } } else { if (rx->data_index == 59) { PDEBUG(DDSP, DEBUG_INFO, "Long read, frame too long\n"); rx->data_receive = 0; + rx_weather_reset(rx); } else { - rx->data_string[rx->data_index++] = symbol; + if (rx->data_index == 1 || rx->data_index == 15 || rx->data_index == 21 || rx->data_index == 29 || rx->data_index == 36 || rx->data_index == 42 || rx->data_index == 45 || rx->data_index == 50) + rx->data_string[rx->string_index++] = ' '; + rx->data_string[rx->string_index++] = symbol; + rx->data_index++; rx->data_frame >>= 1; rx->data_frame |= (uint64_t)(symbol & 1) << 58; + second = rx->data_index; } } } + display_measurements_update(dcf77->dmp_current_second, second, 0.0); } //#define DEBUG_SAMPLE +/* decode radio wave and extract each bit / second */ void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length) { dcf77_rx_t *rx = &dcf77->rx; @@ -481,6 +1209,9 @@ void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length) display_wave(&dcf77->dispwav, samples, length, 1.0); +#ifdef DEBUG_LOOP + return; +#endif if (!rx->enable) return; diff --git a/src/dcf77/dcf77.h b/src/dcf77/dcf77.h index 123e081..d129f27 100644 --- a/src/dcf77/dcf77.h +++ b/src/dcf77/dcf77.h @@ -15,7 +15,18 @@ typedef struct dcf77_tx { int second; char symbol; uint64_t data_frame; + char data_string[100]; /* 60 digits + spaces + '\0' */ int test_tone; + int weather; + int weather_day; + int weather_night; + int extreme; + int rain; + int wind_dir; + int wind_bft; + int temperature_day; + int temperature_night; + uint64_t weather_cipher; } dcf77_tx_t; typedef struct dcf77_rx { @@ -29,9 +40,12 @@ typedef struct dcf77_rx { int clock_count; double value_level, value_short, value_long; /* measured values */ int data_receive, data_index; - char data_string[60]; /* 59 digits + '\0' */ + char data_string[100]; /* 60 digits + spaces + '\0' */ + int string_index; uint64_t data_frame; - iir_filter_t clock_lp[2]; /* filters received carrier signal */ + int weather_index; + uint64_t weather_cipher; + uint64_t weather_key; } dcf77_rx_t; typedef struct dcf77 { @@ -43,6 +57,7 @@ typedef struct dcf77 { dispmeasparam_t *dmp_input_level; dispmeasparam_t *dmp_signal_level; dispmeasparam_t *dmp_signal_quality; + dispmeasparam_t *dmp_current_second; /* wave */ dispwav_t dispwav; /* display wave form */ @@ -52,6 +67,10 @@ int dcf77_init(int _fast_math); void dcf77_exit(void); dcf77_t *dcf77_create(int samplerate, int use_tx, int use_rx, int test_tone); void dcf77_destroy(dcf77_t *dcf77); -void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp); +void dcf77_tx_start(dcf77_t *dcf77, time_t timestamp, double sub_sec); void dcf77_encode(dcf77_t *dcf77, sample_t *samples, int length); void dcf77_decode(dcf77_t *dcf77, sample_t *samples, int length); + +void list_weather(void); +time_t dcf77_start_weather(time_t timestamp, int region, int offset); +void dcf77_set_weather(dcf77_t *dcf77, int weather_day, int weather_night, int extreme, int rain, int wind_dir, int wind_bft, int temperature_day, int temperature_night); diff --git a/src/dcf77/image.c b/src/dcf77/image.c new file mode 100644 index 0000000..e6ce671 --- /dev/null +++ b/src/dcf77/image.c @@ -0,0 +1,16 @@ +#include + +const char *aaimage[] = { + "", + "@W * DCF77 with weather info *", + "", + " @Y\\__/", + " @w_______@Y/ \\__", + " @w/ ____\\_@Y/@w_", + " \\___/@r10:08:33@w\\", + " @B/ @w\\________/", + " @B/ @W*@B/ @W*@B/ @W*@B/ @W*@B/", + " @W*@B/ @W*@B/ @W*@B/ @W*@B/", + "", + NULL +}; diff --git a/src/dcf77/main.c b/src/dcf77/main.c index 8f7ab45..c9d4dcc 100755 --- a/src/dcf77/main.c +++ b/src/dcf77/main.c @@ -27,11 +27,14 @@ #include #include #include +#include #include "../libdebug/debug.h" #include "../liboptions/options.h" #include "../libsample/sample.h" #include "../libsound/sound.h" +#include "../libaaimage/aaimage.h" #include "dcf77.h" +#include "cities.h" int num_kanal = 1; dcf77_t *dcf77 = NULL; @@ -40,7 +43,17 @@ static const char *dsp_device = ""; static int dsp_samplerate = 192000; static int dsp_buffer = 50; static int rx = 0, tx = 0; -static time_t timestamp = -1; +static double timestamp = -1; +static int weather = 0; +static int weather_day; +static int weather_night; +static int extreme; +static int rain; +static int wind_dir; +static int wind_bft; +static int temperature_day; +static int temperature_night; +static int region = -1, region_advance; static int double_amplitude = 0; static int test_tone = 0; static int dsp_interval = 1; /* ms */ @@ -110,8 +123,6 @@ static time_t feierabend_time() t = get_time(); tm = localtime(&t); - if (!tm) - return -1; tm->tm_hour = 17; tm->tm_min = 0; @@ -148,7 +159,7 @@ void print_help(void) printf(" -R --rx\n"); printf(" Receive time signal\n"); printf(" -F --fake\n"); - printf(" Use given time stamp: .\n"); + printf(" Use given time stamp: .\n"); printf(" All values have to be numerical. The year must have 4 digits.\n"); printf(" --feierabend\n"); printf(" --end-of-working-day\n"); @@ -156,6 +167,29 @@ void print_help(void) printf(" --geburtstag\n"); printf(" --birthday\n"); printf(" Use fake time stamp that equals birth of the author.\n"); + printf(" -W --weather \n"); + printf(" Send these weather info for all regions / all days.\n"); + printf(" See -L for infos on values.\n"); + printf(" weather = 1..15 for common day and night weather.\n"); + printf(" weather = 1..15,1..15 for specific day and night weather.\n"); + printf(" extreme = 0..15 for extreme weather conditions.\n"); + printf(" rain = 0..100 for rain/show probability. (closest is used)\n"); + printf(" wind dir = N | NE | E | SE | S | SW | W | NW | 0 for wind direction.\n"); + printf(" wind bft = for wind speed in bft. (closest is used)\n"); + printf(" temerature = for common min and max temperature.\n"); + printf(" temerature = , for specific min and max temperature.\n"); + printf(" --beach-party\n"); + printf(" Beach weather, equivalent to -W 1 0 0 0 2 35,20\n"); + printf(" --santa-claus\n"); + printf(" --muenster-2005\n"); + printf(" Deep snow, equivalent to -W 7 1 100 E 3 1,-1\n"); + printf(" -A --at-region \n"); + printf(" Alter time, so that the weather of the given region is transmitted.\n"); + printf(" To allow the receiver to sync, give time to advance in minutes.\n"); + printf(" -L --list\n"); + printf(" List all regions / weather values.\n"); + printf(" -C --city \n"); + printf(" Search for city (case insensitive) and display its region code.\n"); printf(" -D --double-amplitude\n"); printf(" Transmit with double amplitude by using differential stereo output.\n"); printf(" --test-tone\n"); @@ -173,8 +207,11 @@ void print_help(void) #define OPT_F2 1002 #define OPT_G1 1003 #define OPT_G2 1004 -#define OPT_TEST_TONE 1005 -#define OPT_FAST_MATH 1006 +#define OPT_BEACH 1005 +#define OPT_SANTA 1006 +#define OPT_MUENSTER 1007 +#define OPT_TEST_TONE 1008 +#define OPT_FAST_MATH 1009 static void add_options(void) { @@ -190,15 +227,25 @@ static void add_options(void) option_add(OPT_F2, "end-of-working-day", 0); option_add(OPT_G1, "geburtstag", 0); option_add(OPT_G2, "birthday", 0); - option_add(OPT_TEST_TONE, "test-tone", 0); + option_add('W', "weather", 6); + option_add(OPT_BEACH, "beach-party", 0); + option_add(OPT_SANTA, "santa-claus", 0); + option_add(OPT_MUENSTER, "muenster-2005", 0); + option_add('A', "at-region", 2); + option_add('L', "list", 0); + option_add('C', "city", 1); option_add('D', "double-amplitude", 0); + option_add(OPT_TEST_TONE, "test-tone", 0); option_add('r', "realtime", 1); option_add(OPT_FAST_MATH, "fast-math", 0); } +static const char *wind_dirs[8] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; + static int handle_options(int short_option, int argi, char **argv) { - int rc; + char *string, *string1; + int rc, i; switch (short_option) { case 'h': @@ -233,7 +280,6 @@ static int handle_options(int short_option, int argi, char **argv) break; case 'F': timestamp = parse_time(argv + argi); - printf("%ld\n",timestamp); if (timestamp < 0) { fprintf(stderr, "Given time stamp is invalid, please use -h for help.\n"); return -EINVAL; @@ -247,6 +293,79 @@ static int handle_options(int short_option, int argi, char **argv) case OPT_G2: timestamp = 115099200 - 70; break; + case 'W': + if (weather) { +no_multiple_weathers: + fprintf(stderr, "You cannot define more than one weather situation.\n"); + return -EINVAL; + } + weather = 1; + string = options_strdup(argv[argi++]); + string1 = strsep(&string, ","); + weather_day = atoi(string1); + if (string) + weather_night = atoi(string); + else + weather_night = weather_day; + extreme = atoi(argv[argi++]); + rain = atoi(argv[argi++]); + /* if wind is not found, wind 8 (changable) is selected */ + string = options_strdup(argv[argi++]); + for (i = 0; i < 8; i++) { + if (!strcasecmp(string, wind_dirs[i])) + break; + } + wind_dir = i; + wind_bft = atoi(argv[argi++]); + string = options_strdup(argv[argi++]); + string1 = strsep(&string, ","); + temperature_day = atoi(string1); + if (string) + temperature_night = atoi(string); + else + temperature_night = temperature_day; + break; + case OPT_BEACH: + if (weather) + goto no_multiple_weathers; + weather = 1; + weather_day = 1; + weather_night = 1; + extreme = 0; + rain = 0; + wind_dir = 8; + wind_bft = 2; + temperature_day = 35; + temperature_night = 20; /* tropical night >= 20 */ + break; + case OPT_SANTA: + case OPT_MUENSTER: + if (weather) + goto no_multiple_weathers; + weather = 1; + weather_day = 7; + weather_night = 7; + extreme = 0; + rain = 100; + wind_dir = 6; + wind_bft = 3; + temperature_day = 1; + temperature_night = -1; /* freezing a little */ + break; + case 'A': + region = atoi(argv[argi++]); + if (region < 0 || region > 89) { + fprintf(stderr, "Given region number is is invalid, please use -L for list of valid regions.\n"); + return -EINVAL; + } + region_advance = atoi(argv[argi++]); + break; + case 'L': + list_weather(); + return 0; + case 'C': + display_city(argv[argi++]); + return 0; case OPT_TEST_TONE: test_tone = 1; break; @@ -407,8 +526,20 @@ int main(int argc, char *argv[]) fprintf(stderr, "Failed to create \"DCF77\" instance. Quitting!\n"); goto error; } + if (weather) + dcf77_set_weather(dcf77, weather_day, weather_night, extreme, rain, wind_dir, wind_bft, temperature_day, temperature_night); - printf("\n"); + /* no time stamp given, so we use our clock */ + if (tx) { + if (timestamp < 0 && region < 0) + printf("No alternative time given, so you might not notice the difference between our transmission and the real DCF77 transmission.\n"); + if (timestamp < 0) + timestamp = get_time(); + if (region >= 0 && weather) + timestamp = dcf77_start_weather((time_t)timestamp, region, region_advance); + } + + print_aaimage(); printf("DCF77 ready.\n"); /* prepare terminal */ @@ -437,7 +568,7 @@ int main(int argc, char *argv[]) soundif_start(); if (tx) - dcf77_tx_start(dcf77, timestamp); + dcf77_tx_start(dcf77, (time_t)timestamp, fmod(timestamp, 1.0)); while (!quit) { int w; diff --git a/src/dcf77/weather.c b/src/dcf77/weather.c new file mode 100644 index 0000000..41b7824 --- /dev/null +++ b/src/dcf77/weather.c @@ -0,0 +1,582 @@ + +/* based on code found at: +https://github.com/FroggySoft/AlarmClock/blob/master/dcf77.cpp +https://github.com/tobozo/esp32-dcf77-weatherman/blob/master/dcf77.cpp +*/ + +#include +#include +#include +#include "../libdebug/debug.h" +#include "weather.h" + +/// Container zum Konvertieren zwischen 4 Bytes und Uint. +union ByteUInt { + struct { +# if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t Byte0; + uint8_t Byte1; + uint8_t Byte2; + uint8_t Byte3; +# elif __BYTE_ORDER == __BIG_ENDIAN + uint8_t Byte3; + uint8_t Byte2; + uint8_t Byte1; + uint8_t Byte0; +# else +#error unsupported bitorder, please fix +# endif + } s; + uint32_t FullUint; +}; + +/// bit pattern for 0D,0E from 0B-0D +static const uint32_t mUintArrBitPattern12[12] = { + 0x80000, //0b10000000000000000000 / 0D.3 + 0x00010, //0b00000000000000010000 / 0B.4 + 0x00008, //0b00000000000000001000 / 0B.3 + 0x00100, //0b00000000000100000000 / 0C.0 + 0x00080, //0b00000000000010000000 / 0B.7 + 0x01000, //0b00000001000000000000 / 0C.4 + 0x00800, //0b00000000100000000000 / 0C.3 + 0x10000, //0b00010000000000000000 / 0D.0 + 0x08000, //0b00001000000000000000 / 0C.7 + 0x00001, //0b00000000000000000001 / 0B.0 + 0x00000, //0b00000000000000000000 / xxxx + 0x00000 //0b00000000000000000000 / xxxx +}; + +/// 12-15 from 16-19 (time) +static const uint32_t mUintArrBitPattern30_1[30] = { + 0x00000200, //0b00000000000000000000001000000000 / 17.1 + 0x00000020, //0b00000000000000000000000000100000 / 16.5 + 0x02000000, //0b00000010000000000000000000000000 / 19.1 + 0x00000000, //0b00000000000000000000000000000000 / 1A.3 + 0x00000000, //0b00000000000000000000000000000000 / 1A.5 + 0x00000080, //0b00000000000000000000000010000000 / 16.7 + 0x40000000, //0b01000000000000000000000000000000 / 19.6 + 0x01000000, //0b00000001000000000000000000000000 / 19.0 + + 0x04000000, //0b00000100000000000000000000000000 / 19.2 + 0x00000000, //0b00000000000000000000000000000000 / 1A.4 + 0x00010000, //0b00000000000000010000000000000000 / 18.0 + 0x00000000, //0b00000000000000000000000000000000 / 1A.2 + 0x00400000, //0b00000000010000000000000000000000 / 18.6 + 0x00000010, //0b00000000000000000000000000010000 / 16.4 + 0x00200000, //0b00000000001000000000000000000000 / 18.5 + 0x00080000, //0b00000000000010000000000000000000 / 18.3 + + 0x00004000, //0b00000000000000000100000000000000 / 17.6 + 0x00000000, //0b00000000000000000000000000000000 / 1A.6 + 0x00020000, //0b00000000000000100000000000000000 / 18.1 + 0x00100000, //0b00000000000100000000000000000000 / 18.4 + 0x00008000, //0b00000000000000001000000000000000 / 17.7 + 0x00000040, //0b00000000000000000000000001000000 / 16.6 + 0x00001000, //0b00000000000000000001000000000000 / 17.4 + 0x00000400, //0b00000000000000000000010000000000 / 17.2 + + 0x00000001, //0b00000000000000000000000000000001 / 16.0 + 0x80000000, //0b10000000000000000000000000000000 / 19.7 + 0x00000008, //0b00000000000000000000000000001000 / 16.3 + 0x00000002, //0b00000000000000000000000000000010 / 16.1 + 0x00040000, //0b00000000000001000000000000000000 / 18.2 + 0x10000000 //0b00010000000000000000000000000000 / 19.4 +}; + +/// bit pattern for 12-15 from 1A (time2) +static const uint32_t mUintArrBitPattern30_2[30] = { + 0x00, //0b00000000, /* 17.1 + 0x00, //0b00000000, /* 16.5 + 0x00, //0b00000000, /* 19.1 + 0x08, //0b00001000, /* 1A.3 + 0x20, //0b00100000, /* 1A.5 + 0x00, //0b00000000, /* 16.7 + 0x00, //0b00000000, /* 19.6 + 0x00, //0b00000000, /* 19.0 + + 0x00, //0b00000000, /* 19.2 + 0x10, //0b00010000, /* 1A.4 + 0x00, //0b00000000, /* 18.0 + 0x04, //0b00000100, /* 1A.2 + 0x00, //0b00000000, /* 18.6 + 0x00, //0b00000000, /* 16.4 + 0x00, //0b00000000, /* 18.5 + 0x00, //0b00000000, /* 18.3 + + 0x00, //0b00000000, /* 17.6 + 0x40, //0b01000000, /* 1A.6 + 0x00, //0b00000000, /* 18.1 + 0x00, //0b00000000, /* 18.4 + 0x00, //0b00000000, /* 17.7 + 0x00, //0b00000000, /* 16.6 + 0x00, //0b00000000, /* 17.4 + 0x00, //0b00000000, /* 17.2 + + 0x00, //0b00000000, /* 16.0 + 0x00, //0b00000000, /* 19.7 + 0x00, //0b00000000, /* 16.3 + 0x00, //0b00000000, /* 16.1 + 0x00, //0b00000000, /* 18.2 + 0x00 //0b00000000, /* 19.4 +}; + +/// 12-14 from 1C-1E (result from F) +static const uint32_t mUintArrBitPattern20[20] = { + 0x000004, //0b000000000000000000000100 / 1C.2 + 0x002000, //0b000000000010000000000000 / 1E.5 + 0x008000, //0b000000001000000000000000 / 1E.7 + 0x400000, //0b010000000000000000000000 / 1D.6 + 0x000100, //0b000000000000000100000000 / 1E.0 + 0x100000, //0b000100000000000000000000 / 1D.4 + 0x000400, //0b000000000000010000000000 / 1E.2 + 0x800000, //0b100000000000000000000000 / 1D.7 + + 0x040000, //0b000001000000000000000000 / 1D.2 + 0x020000, //0b000000100000000000000000 / 1D.1 + 0x000008, //0b000000000000000000001000 / 1C.3 + 0x000200, //0b000000000000001000000000 / 1E.1 + 0x004000, //0b000000000100000000000000 / 1E.6 + 0x000002, //0b000000000000000000000010 / 1C.1 + 0x001000, //0b000000000001000000000000 / 1E.4 + 0x080000, //0b000010000000000000000000 / 1D.3 + + 0x000800, //0b000000000000100000000000 / 1E.3 + 0x200000, //0b001000000000000000000000 / 1D.5 + 0x010000, //0b000000010000000000000000 / 1D.0 + 0x000001 //0b000000000000000000000001 / 1C.0 +}; + +/// bit pattern for 12-15 from 16-19 (1/3) +static const uint64_t mByteArrLookupTable1C_1[8] = { + 0xBB0E22C573DFF76D, 0x90E9A1381C844A56, + 0x648D280BD1BA9352, 0x1CC5A7F0E97F364E, + 0xC1773DB3AAE00C6F, 0x1488F62BD2995E45, + 0x1F7096D3B30BFCEE, 0x8142CA34A5582967 +}; + +/// bit pattern for 12-15 from 16-19 (2/3) +static const uint64_t mByteArrLookupTable1C_2[8] = { + 0xAB3DFC7465E60E4F, 0x9711D85983C2BA20, + 0xC51BD2584937017D, 0x93FAE02F66B4AC8E, + 0xB7CC43FF5866EB35, 0x822A99DD007114AE, + 0x4EB1F7701852AA9F, 0xD56BCC3D0483E926 +}; + +/// bit pattern for 12-15 from 16-19 (3/3) +static const uint64_t mByteArrLookupTable1C_3[8] = { + 0x0A02000F06070D08, 0x030C0B050901040E, + 0x0209050D0C0E0F08, 0x06070B01000A0403, + 0x08000D0F010C0306, 0x0B0409050A07020E, + 0x030D000C09060F0B, 0x010E080A02070405 +}; + +/// Container, which contains all former global vars +typedef struct DataContainer { + /// Registers R12 to R15 + union ByteUInt mByteUint1; + /// Registers R08 to R0A + union ByteUInt mByteUint2; + /// Registers R0B to R0E + union ByteUInt mByteUint3; + /// Registers R1C to R1E + union ByteUInt mByteUint4; + + uint8_t mByteUpperTime2;//, mByteR1B; + uint32_t mUintLowerTime; +} DataContainer_t; + +static int32_t GetWeatherFromPlain(uint8_t *PlainBytes) +{ + uint32_t result; + uint32_t checkSum; + + checkSum = PlainBytes[2] & 0x0f; + checkSum <<= 8; + checkSum |= PlainBytes[1]; + checkSum <<= 4; + checkSum |= PlainBytes[0] >> 4; + if (checkSum != 0x2501) + return -1; + + result = PlainBytes[0] & 0x0f; + result <<= 8; + result |= PlainBytes[4]; + result <<= 8; + result |= PlainBytes[3]; + result <<= 4; + result |= PlainBytes[2] >> 4; + + return result; +} + +static uint8_t *GetPlainFromWeather(uint32_t weather) +{ + static uint8_t result[5]; + weather <<= 4; + result[1] = 0x50; + result[2] = (weather & 0xf0) | 0x02; + weather >>= 8; + result[3] = weather & 0xff; + weather >>= 8; + result[4] = weather & 0xff; + weather >>= 8; + result[0] = (weather & 0x0f) | 0x10; + return result; +} + +static void CopyTimeToByteUint(uint8_t *data, uint8_t *key, DataContainer_t *container) +{ + int i; + + for (i = 0; i < 4; i++) + { + container->mUintLowerTime <<= 8; + container->mUintLowerTime |= key[3 - i]; + } + container->mByteUpperTime2 = key[4]; + + // copy R + container->mByteUint3.s.Byte0 = data[2]; + container->mByteUint3.s.Byte1 = data[3]; + container->mByteUint3.s.Byte2 = data[4]; + container->mByteUint3.FullUint >>= 4; + + // copy L + container->mByteUint2.s.Byte0 = data[0]; + container->mByteUint2.s.Byte1 = data[1]; + container->mByteUint2.s.Byte2 = (uint8_t)(data[2] & 0x0F); +} + +static void ShiftTimeRight(int round, DataContainer_t *container) +{ + int count; + uint8_t tmp; + + if ((round == 16) || (round == 8) || (round == 7) || (round == 3)) + count = 2; + else + count = 1; + + while (count-- != 0) + { + tmp = 0; + if ((container->mUintLowerTime & 0x00100000) != 0) // save time bit 20 + tmp = 1; + + container->mUintLowerTime &= 0xFFEFFFFF; + if ((container->mUintLowerTime & 1) != 0) + container->mUintLowerTime |= 0x00100000; // copy time bit 0 to time bit 19 + container->mUintLowerTime >>= 1; // time >>= 1 + + if ((container->mByteUpperTime2 & 1) != 0) + container->mUintLowerTime |= 0x80000000; + container->mByteUpperTime2 >>= 1; + if (tmp != 0) + container->mByteUpperTime2 |= 0x80; // insert time bit 20 to time bit 39 + } + +} + +static void ShiftTimeLeft(int round, DataContainer_t *container) +{ + int count; + uint8_t tmp; + + if ((round == 16) || (round == 8) || (round == 7) || (round == 3)) + count = 2; + else + count = 1; + + while (count-- != 0) + { + tmp = 0; + if ((container->mByteUpperTime2 & 0x80) != 0) + tmp = 1; + container->mByteUpperTime2 <<= 1; + + if ((container->mUintLowerTime & 0x80000000) != 0) + container->mByteUpperTime2 |= 1; + + container->mUintLowerTime <<= 1; + if ((container->mUintLowerTime & 0x00100000) != 0) + container->mUintLowerTime |= 1; + + container->mUintLowerTime &= 0xFFEFFFFF; + if (tmp != 0) + container->mUintLowerTime |= 0x00100000; + } +} + +static void ExpandR(DataContainer_t *container) +{ + uint32_t tmp; + int i; + + container->mByteUint3.FullUint &= 0x000FFFFF; // clear 0D(4-7),0E + tmp = 0x00100000; // and set bits form 0B-0D(0-3) + for (i = 0; i < 12; i++) + { + if ((container->mByteUint3.FullUint & mUintArrBitPattern12[i]) != 0) + container->mByteUint3.FullUint |= tmp; + tmp <<= 1; + } +} + +static void ExpandL(DataContainer_t *container) +{ + uint32_t tmp; + int i; + + container->mByteUint2.FullUint &= 0x000FFFFF; // clear 0D(4-7),0E + tmp = 0x00100000; // and set bits form 0B-0D(0-3) + for (i = 0; i < 12; i++) + { + if ((container->mByteUint2.FullUint & mUintArrBitPattern12[i]) != 0) + container->mByteUint2.FullUint |= tmp; + tmp <<= 1; + } +} + +static void CompressKey(DataContainer_t *container) +{ + uint32_t tmp; + int i; + + container->mByteUint1.FullUint = 0; // clear 12-15 + tmp = 0x00000001; // and set bits from 16-1A (time) + for (i = 0; i < 30; i++) + { + if ((container->mUintLowerTime & mUintArrBitPattern30_1[i]) != 0 || (container->mByteUpperTime2 & mUintArrBitPattern30_2[i]) != 0) + container->mByteUint1.FullUint |= tmp; + tmp <<= 1; + } +} + +static void DoSbox(DataContainer_t *container) +{ + uint8_t tmp, helper; //mByteR1B; + int i; + + helper = container->mByteUint1.s.Byte3; // R1B = R15; + container->mByteUint1.s.Byte3 = container->mByteUint1.s.Byte2; // R15 = R14 + + // INNER LOOP + for (i = 5; i > 0; i--) + { + if ((i & 1) == 0) // round 4,2 + { + tmp = (uint8_t)(container->mByteUint1.s.Byte0 >> 4); // swap R12 + tmp |= (uint8_t)((container->mByteUint1.s.Byte0 & 0x0f) << 4); + container->mByteUint1.s.Byte0 = tmp; + } + container->mByteUint1.s.Byte3 &= 0xF0; // set R1C + tmp = (uint8_t)((container->mByteUint1.s.Byte0 & 0x0F) | container->mByteUint1.s.Byte3); + + if ((i & 4) != 0) + tmp = mByteArrLookupTable1C_1[(tmp & 0x38) >> 3] >> (56 - (tmp & 0x07) * 8); + + if ((i & 2) != 0) + tmp = mByteArrLookupTable1C_2[(tmp & 0x38) >> 3] >> (56 - (tmp & 0x07) * 8); + + else if (i == 1) + tmp = mByteArrLookupTable1C_3[(tmp & 0x38) >> 3] >> (56 - (tmp & 0x07) * 8); + + if ((i & 1) != 0) + container->mByteUint4.s.Byte0 = (uint8_t)(tmp & 0x0F); + else + container->mByteUint4.s.Byte0 |= (uint8_t)(tmp & 0xF0); + + if ((i & 1) == 0) // copy 14->13->12, 1C->1E->1D + { + tmp = container->mByteUint1.s.Byte3; + container->mByteUint1.FullUint >>= 8; + container->mByteUint1.s.Byte3 = tmp; + container->mByteUint4.FullUint <<= 8; + } + + container->mByteUint1.s.Byte3 >>= 1; // rotate R1B>R15 twice + if ((helper & 1) != 0) + container->mByteUint1.s.Byte3 |= 0x80; + helper >>= 1; + + container->mByteUint1.s.Byte3 >>= 1; + if ((helper & 1) != 0) + container->mByteUint1.s.Byte3 |= 0x80; + helper >>= 1; + } // end of inner loop +} + +static void DoPbox(DataContainer_t *container) +{ + uint32_t tmp; + int i; + + container->mByteUint1.FullUint = 0xFF000000; // clear 12-14 + tmp = 0x00000001; // and set bits from 1C-1E (result from F) + for (i = 0; i < 20; i++) + { + if ((container->mByteUint4.FullUint & mUintArrBitPattern20[i]) != 0) + container->mByteUint1.FullUint |= tmp; + tmp <<= 1; + } +} + +/* modified DES decrypt using strings */ +static uint8_t *Decrypt(uint8_t *cipher, uint8_t *key) +{ + DataContainer_t container; + int i; + + static uint8_t plain[5]; + CopyTimeToByteUint(cipher, key, &container); + + // OUTER LOOP 1 + for (i = 16; i > 0; i--) + { + ShiftTimeRight(i, &container); + ExpandR(&container); + CompressKey(&container); + + // expR XOR compr.Key + container.mByteUint1.FullUint ^= container.mByteUint3.FullUint; // 12-15 XOR 0B-0E + container.mByteUint3.s.Byte2 &= 0x0F; // clear 0D(4-7) + + DoSbox(&container); + DoPbox(&container); + + // L XOR P-Boxed Round-Key (L') + container.mByteUint1.FullUint ^= container.mByteUint2.FullUint; + + // L = R + container.mByteUint2.FullUint = container.mByteUint3.FullUint & 0x00FFFFFF; + + // R = L' + container.mByteUint3.FullUint = container.mByteUint1.FullUint & 0x00FFFFFF; + } // end of outer loop 1 + + container.mByteUint3.FullUint <<= 4; + container.mByteUint2.s.Byte2 &= 0x0F; + container.mByteUint2.s.Byte2 |= (uint8_t)(container.mByteUint3.s.Byte0 & 0xF0); + + plain[0] = container.mByteUint2.s.Byte0; + plain[1] = container.mByteUint2.s.Byte1; + plain[2] = container.mByteUint2.s.Byte2; + plain[3] = container.mByteUint3.s.Byte1; + plain[4] = container.mByteUint3.s.Byte2; + + return plain; +} + +/* modified DES encrypt using strings */ +static uint8_t *Encrypt(uint8_t *plain, uint8_t *key) +{ + static uint8_t cipher[5]; + DataContainer_t container; + int i; + + CopyTimeToByteUint(plain, key, &container); + + // OUTER LOOP 1 + for (i = 1; i < 17; i++) + { + ExpandL(&container); + CompressKey(&container); + + // expR XOR compr.Key + container.mByteUint1.FullUint ^= container.mByteUint2.FullUint; // L' XOR compr.Key + container.mByteUint3.s.Byte2 &= 0x0F; // clear 0D(4-7) + + DoSbox(&container); + DoPbox(&container); + + // L XOR P-Boxed Round-Key (L') + container.mByteUint1.FullUint ^= container.mByteUint3.FullUint; + // L = R + container.mByteUint3.FullUint = container.mByteUint2.FullUint & 0x00FFFFFF; + // R = L' + container.mByteUint2.FullUint = container.mByteUint1.FullUint & 0x00FFFFFF; + + ShiftTimeLeft(i, &container); + } // end of outer loop 1 + + container.mByteUint3.FullUint <<= 4; + container.mByteUint2.s.Byte2 &= 0x0F; + container.mByteUint2.s.Byte2 |= (uint8_t)(container.mByteUint3.s.Byte0 & 0xF0); + + cipher[0] = container.mByteUint2.s.Byte0; + cipher[1] = container.mByteUint2.s.Byte1; + cipher[2] = container.mByteUint2.s.Byte2; + cipher[3] = container.mByteUint3.s.Byte1; + cipher[4] = container.mByteUint3.s.Byte2; + + return cipher; +} + +//#define DEBUG_CIPER + +/* decode given crypted frame and key + * return the weather info or -1 on checksum error + */ +int32_t weather_decode(uint64_t cipher, uint64_t key) +{ + uint8_t CipherBytes[5]; + uint8_t KeyBytes[5]; + uint8_t *PlainBytes; + int32_t weather; + int i; + + for (i = 0; i < 5; i++) + CipherBytes[i] = cipher >> (i * 8); + + for (i = 0; i < 5; i++) + KeyBytes[i] = key >> (i * 8); + + PlainBytes = Decrypt(CipherBytes, KeyBytes); + + weather = GetWeatherFromPlain(PlainBytes); + +#ifdef DEBUG_CIPER + printf("cipher=%s\n", debug_hex(CipherBytes, 5)); + printf("key =%s\n", debug_hex(KeyBytes, 5)); + printf("plain =%s\n", debug_hex(PlainBytes, 5)); + if (weather < 0) + printf("weather=error\n"); + else + printf("weather=%06x\n", weather); + + weather_encode(weather, key); +#endif + + return weather; +} + +/* encode given weather info and key + * return crypted frame + */ +uint64_t weather_encode(uint32_t weather, uint64_t key) +{ + uint8_t KeyBytes[5]; + uint8_t *PlainBytes; + uint8_t *CipherBytes; + uint64_t cipher = 0; + int i; + + PlainBytes = GetPlainFromWeather(weather); + + for (i = 0; i < 5; i++) + KeyBytes[i] = key >> (i * 8); + + CipherBytes = Encrypt(PlainBytes, KeyBytes); + +#ifdef DEBUG_CIPER + printf("plain =%s\n", debug_hex(PlainBytes, 5)); + printf("key =%s\n", debug_hex(KeyBytes, 5)); + printf("cipher=%s\n", debug_hex(CipherBytes, 5)); +#endif + + for (i = 0; i < 5; i++) + cipher |= (uint64_t)(CipherBytes[i]) << (i * 8); + + return cipher; +} + diff --git a/src/dcf77/weather.h b/src/dcf77/weather.h new file mode 100644 index 0000000..1daef97 --- /dev/null +++ b/src/dcf77/weather.h @@ -0,0 +1,3 @@ + +int32_t weather_decode(uint64_t cipher, uint64_t key); +uint64_t weather_encode(uint32_t weather, uint64_t key);