1 /* Fine Offset Weather Station Reader - Main file
2
3 (C) Arne-Jørgen Auberg (arne.jorgen.auberg@gmail.com)
4
5 This application reads WH1080 compatible devices using the USB port.
6 Compatible with all USB stations that can use the EasyWeather app (www.foshk.com)
7
8 The application is written with inspiration from the following projects:
9
10 1) WeatherStation.py - The pywws poject. http://pywws.googlecode.com
11
12 2) usbsnoop2libusb.pl - The usbsnoop log file.
13 The latest version of the script should be in http://iki.fi/lindi/usb/usbsnoop2libusb.pl
14
15 3) wwsr.c - Wireless Weather Station Reader
16 Michael Pendec (michael.pendec@gmail.com)
17
18 28.05.13 Josch (Josch at abwesend dot de) CUSB_Close(): ret-Auswertung korrigiert, assert entfernt
19 log-Pfad einstellbar zur Compilezeit, Warnungen beseitigt, Option -v, Option -f
20 01.06.13 Josch neues Format WS3600
21 15.06.13 Josch option-Handling komplett �berarbeitet
22 17.06.13 Josch Meldungslog mit versch. Levels wahlweise auf Console und/oder in Datei
23 02.07.13 Josch CWS_signed_short() korrigiert
24 05.07.13 Josch bei -c nur aktuellen Datensatz ausgeben
25 12.07.13 Josch Check return codes in CUSB_read_block()
26 16.07.13 Josch CWS_print_decoded_data() vereinfacht
27 23.07.13 Josch Log Levels ge�ndert
28 19.08.13 Josch Dougs barometer correction from 27.09.12 included
29 10.09.13 Josch cache file nur schreiben bei gueltigem Zeitstempel, Typ von CUSB_read_block() geaendert
30 Test auf falschen Zeiger und Lesefehler in CWS_Read()
31 */
32
33 #define VERSION "V2.0.130910"
34
35 #include <stdio.h>
36 #include <stdarg.h>
37 #include <getopt.h>
38 #include <string.h>
39 #include <signal.h>
40 #include <math.h>
41 #include <usb.h>
42
43 /***************** macros ****************************************************/
44 // WORKPATH default path for all outputfiles (log, dat, msg)
45 // path for dat can be changed with option -n
46 // LOGPATH default full name for weather log files
47 /* original, but bad */
48 //#define WORKPATH "//var/tmp//
49
50 /* on PC e.g. Ubuntu */
51 //#define WORKPATH "//home//user//FOWSR//fowsr-read-only//fowsr-1.0//"
52
53 /* on Raspberry */
54 #define WORKPATH "//var/tmp//log//fowsr//"
55
56 #define LOGPATH WORKPATH"%s.log"
57
58 /***************** variables *************************************************/
59 //global used options
60 int LogToScreen = 0; // log to screen
61 int readflag = 0; // Read the weather station or use the cache file.
62 int vLevel = 0; // print more messages (0=only Errors, 3=all)
63 char vDst = 'c'; // print more messages ('c'=ToScreen, 'f'=ToFile, 'b'=ToBoth)
64 unsigned short old_pos = 0; // last index of previous read
65 char LogPath[255] = "";
66
67 /*****************************************************************************/
68 #include "fowsr.h"
69
70 /******* helper functions ****************************************************/
MsgPrintf(int Level,const char * fmt,...)71 void MsgPrintf(int Level, const char *fmt, ...)
72 {
73 char Buf[200];
74 va_list argptr;
75 FILE *f;
76
77 if(Level>vLevel)
78 return;
79 va_start(argptr, fmt);
80 vsprintf(Buf, fmt, argptr);
81 va_end(argptr);
82 if(vDst!='f') {
83 printf("%s", Buf);
84 }
85 if((vDst=='b')||(vDst=='f')) {
86 f = fopen(WORKPATH"fowsr.msg", "at");
87 if(f) {
88 fprintf(f, "%s", Buf);
89 fclose(f);
90 }
91 }
92 }
93
94 /*---------------------------------------------------------------------------*/
print_bytes(char * address,int length)95 void print_bytes(char *address, int length) {
96 int i = 0; //used to keep track of line lengths
97 char *line = (char*)address; //used to print char version of data
98 unsigned char ch; // also used to print char version of data
99 printf("%08X | ", (int)address); //Print the address we are pulling from
100 while (length-- > 0) {
101 printf("%02X ", (unsigned char)*address++); //Print each char
102 if (!(++i % 16) || (length == 0 && i % 16)) { //If we come to the end of a line...
103 //If this is the last line, print some fillers.
104 if (length == 0) { while (i++ % 16) { printf("__ "); } }
105 printf("| ");
106 while (line < address) { // Print the character version
107 ch = *line++;
108 printf("%c", (ch < 33 || ch == 255) ? 0x2E : ch);
109 }
110 // If we are not on the last line, prefix the next line with the address.
111 if (length > 0) { printf("\n%08X | ", (int)address); }
112 }
113 }
114 puts("");
115 }
116
117 /***************** libusb functions ******************************************/
118 /*void list_devices() {
119 struct usb_bus *bus;
120 for (bus = usb_get_busses(); bus; bus = bus->next) {
121 struct usb_device *dev;
122
123 for (dev = bus->devices; dev; dev = dev->next)
124 printf("0x%04x 0x%04x\n",
125 dev->descriptor.idVendor,
126 dev->descriptor.idProduct);
127 }
128 }*/
129
130 /*---------------------------------------------------------------------------*/
find_device(int vendor,int product)131 struct usb_device *find_device(int vendor, int product) {
132 struct usb_bus *bus;
133
134 for (bus = usb_get_busses(); bus; bus = bus->next) {
135 struct usb_device *dev;
136
137 for (dev = bus->devices; dev; dev = dev->next) {
138 if (dev->descriptor.idVendor == vendor
139 && dev->descriptor.idProduct == product)
140 return dev;
141 }
142 }
143 return NULL;
144 }
145
146 /***************** The CUSB class ********************************************/
CUSB_Open(int vendor,int product)147 int CUSB_Open(int vendor, int product)
148 { /* returns 0 if OK, <0 if error */
149 int ret;
150 char buf[1000];
151
152 usb_init();
153 if(vDst!='f')
154 usb_set_debug(vLevel+1);
155 usb_find_busses();
156 usb_find_devices();
157
158 dev = find_device(vendor, product);
159 if(!dev) {
160 MsgPrintf(0, "Weatherstation not found on USB (vendor,product)=(%04X,%04X)\n", vendor, product);
161 return -1;
162 }
163 devh = usb_open(dev);
164 if(!devh) {
165 MsgPrintf(0, "Open USB device failed (vendor,product)=(%04X,%04X)\n", vendor, product);
166 return -2;
167 }
168 signal(SIGTERM, CUSB_Close);
169 #ifdef LIBUSB_HAS_GET_DRIVER_NP
170 ret = usb_get_driver_np(devh, 0, buf, sizeof(buf));
171 MsgPrintf(3, "usb_get_driver_np returned %d\n", ret);
172 if (ret == 0) {
173 MsgPrintf(1, "interface 0 already claimed by driver \\'%s\\', attempting to detach it\n", buf);
174 #ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
175 ret = usb_detach_kernel_driver_np(devh, 0);
176 MsgPrintf(ret?0:1, "usb_detach_kernel_driver_np returned %d\n", ret);
177 #endif
178 }
179 #endif
180 ret = usb_claim_interface(devh, 0);
181 if(ret<0) {
182 MsgPrintf(0, "claim failed with error %d\n", ret);
183 return 0; // error, but device is already open and needs to be closed
184 }
185
186 ret = usb_set_altinterface(devh, 0);
187 if(ret<0) {
188 MsgPrintf(0, "set_altinterface failed with error %d\n", ret);
189 return 0; // error, but device is already open and needs to be closed
190 }
191 ret = usb_get_descriptor(devh, 1, 0, buf, 0x12);
192 ret = usb_get_descriptor(devh, 2, 0, buf, 0x09);
193 ret = usb_get_descriptor(devh, 2, 0, buf, 0x22);
194 ret = usb_release_interface(devh, 0);
195 if(ret) MsgPrintf(0, "failed to release interface before set_configuration: %d\n", ret);
196 ret = usb_set_configuration(devh, 1);
197 ret = usb_claim_interface(devh, 0);
198 if(ret) MsgPrintf(0, "claim after set_configuration failed with error %d\n", ret);
199 ret = usb_set_altinterface(devh, 0);
200 ret = usb_control_msg(devh, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 0xa, 0, 0, buf, 0, 1000);
201 ret = usb_get_descriptor(devh, 0x22, 0, buf, 0x74);
202
203 return 0;
204 }
205
206 /*---------------------------------------------------------------------------*/
CUSB_Close()207 void CUSB_Close()
208 {
209 int ret = usb_release_interface(devh, 0);
210 if(ret) MsgPrintf(0, "failed to release interface: %d\n", ret);
211 ret = usb_close(devh);
212 if(ret) MsgPrintf(0, "failed to close interface: %d\n", ret);
213 }
214
215 /*---------------------------------------------------------------------------*/
CUSB_read_block(unsigned short ptr,char * buf)216 short CUSB_read_block(unsigned short ptr, char* buf)
217 {
218 /*
219 Read 32 bytes data command
220
221 After sending the read command, the device will send back 32 bytes data wihtin 100ms.
222 If not, then it means the command has not been received correctly.
223 */
224 char buf_1 = (char)(ptr / 256);
225 char buf_2 = (char)(ptr & 0xFF);
226 char tbuf[8];
227 tbuf[0] = 0xA1; // READ COMMAND
228 tbuf[1] = buf_1; // READ ADDRESS HIGH
229 tbuf[2] = buf_2; // READ ADDRESS LOW
230 tbuf[3] = 0x20; // END MARK
231 tbuf[4] = 0xA1; // READ COMMAND
232 tbuf[5] = 0; // READ ADDRESS HIGH
233 tbuf[6] = 0; // READ ADDRESS LOW
234 tbuf[7] = 0x20; // END MARK
235
236 // Prepare read of 32-byte chunk from position ptr
237 int ret = usb_control_msg(devh, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x200, 0, tbuf, 8, 1000);
238 if(ret<0) MsgPrintf(0, "usb_control_msg failed (%d) whithin CUSB_read_block(%04X,...)\n", ret, ptr);
239 else {
240 // Read 32-byte chunk and place in buffer buf
241 ret = usb_interrupt_read(devh, 0x81, buf, 0x20, 1000);
242 if(ret<0) MsgPrintf(0, "usb_interrupt_read failed (%d) whithin CUSB_read_block(%04X,...)\n", ret, ptr);
243 }
244 return ret;
245 }
246
247 /*---------------------------------------------------------------------------*/
248 #ifdef NotUsed
CUSB_write_byte(unsigned short ptr,char * buf)249 unsigned short CUSB_write_byte(unsigned short ptr, char* buf)
250 {
251 /*
252 Write one byte data to ADDR command
253
254 If data has been received and written correctly,
255 the device will return 8 bytes 0xA5 indicating the command has been carried out sucessfully.
256 */
257 char buf_1 = (char)(ptr / 256);
258 char buf_2 = (char)(ptr & 0xFF);
259 char tbuf[8];
260 tbuf[0] = 0xA2; // WRITE COMMAND
261 tbuf[1] = buf_1; // WRITE ADDRESS HIGH
262 tbuf[2] = buf_2; // WRITE ADDRESS LOW
263 tbuf[3] = 0x20; // END MARK
264 tbuf[4] = 0xA2; // WRITE COMMAND
265 tbuf[5] = *buf; // DATA TO BE WRITTEN
266 tbuf[6] = 0; // DON'T CARE
267 tbuf[7] = 0x20; // END MARK
268
269 int ret;
270 // Prepare write of 32-byte chunk from position ptr
271 ret = usb_control_msg(devh, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x200, 0, tbuf, 8, 1000);
272 // Write 32-byte chunk from buffer buf
273 ret = usb_interrupt_write(devh, 0x81, buf, 0x20, 1000);
274 // Read 8-byte result and place in buffer tbuf
275 ret = usb_interrupt_read(devh, 0x81, tbuf, 0x08, 1000);
276
277 return ret;
278 }
279
280 /*---------------------------------------------------------------------------*/
CUSB_write_block(unsigned short ptr,char * buf)281 unsigned short CUSB_write_block(unsigned short ptr, char* buf)
282 {
283 /*
284 Write 32 bytes data command
285
286 After sending the write command, the 32 bytes data load must be sent to device within 500ms.
287 Once the 32 bytes data have been written to the EEPROM sucessfully,
288 then the device will return 8 bytes of 0xA5 telling that the data has been written correctly.
289 */
290 char buf_1 = (char)(ptr / 256);
291 char buf_2 = (char)(ptr & 0xFF);
292 char tbuf[8];
293 tbuf[0] = 0xA0; // WRITE COMMAND
294 tbuf[1] = buf_1; // WRITE ADDRESS HIGH
295 tbuf[2] = buf_2; // WRITE ADDRESS LOW
296 tbuf[3] = 0x20; // END MARK
297 tbuf[4] = 0xA0; // WRITE COMMAND
298 tbuf[5] = 0; // DON'T CARE
299 tbuf[6] = 0; // DON'T CARE
300 tbuf[7] = 0x20; // END MARK
301
302 int ret;
303 // Prepare write of 32-byte chunk from position ptr
304 ret = usb_control_msg(devh, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 9, 0x200, 0, tbuf, 8, 1000);
305 // Write 32-byte chunk from buffer buf
306 ret = usb_interrupt_write(devh, 0x81, buf, 0x20, 1000);
307 // Read 8-byte result and place in buffer tbuf
308 ret = usb_interrupt_read(devh, 0x81, tbuf, 0x08, 1000);
309
310 return ret;
311 }
312 #endif // NotUsed
313
314 /***************** The CWS class *********************************************/
CWS_Cache(char isStoring)315 void CWS_Cache(char isStoring)
316 {
317 int n;
318 char fname[] = WORKPATH"fowsr.dat"; // cache file
319 char Buf[40];
320 FILE* f = NULL;
321
322 if (isStoring == WS_CACHE_READ) {
323 if (f=fopen(fname,"rb")) {
324 printf("using cache file %s\n", fname);
325 n=fread(&m_previous_timestamp,sizeof(m_previous_timestamp),1,f);
326 n=fread(m_buf,sizeof(m_buf[0]),WS_BUFFER_SIZE,f);
327 }
328 }
329 else { // WS_CACHE_WRITE
330 if(m_timestamp<3600) {
331 strftime(Buf,sizeof(Buf),"%Y-%m-%d %H:%M:%S", localtime(&m_timestamp));
332 MsgPrintf(0, "wrong timestamp %s - cachefile not written\n", Buf);
333 }
334 else if (f=fopen(fname,"wb")) {
335 n=fwrite(&m_timestamp,sizeof(m_timestamp),1,f);
336 n=fwrite(m_buf,sizeof(m_buf[0]),WS_BUFFER_SIZE,f);
337 }
338 }
339 if (f) fclose(f);
340 }
341
342 /*---------------------------------------------------------------------------*/
CWS_print_decoded_data()343 void CWS_print_decoded_data()
344 {
345 char s2[100];
346
347 for(int i=WS_LOWER_FIXED_BLOCK_START; i<WS_LOWER_FIXED_BLOCK_END; i++) {
348 CWS_decode(&m_buf[ws_format[i].pos],
349 ws_format[i].ws_type,
350 ws_format[i].scale,
351 0.0,
352 s2);
353 printf("%s=%s\n", ws_format[i].name, s2);
354 }
355 }
356
357 /*---------------------------------------------------------------------------*/
CWS_Open()358 int CWS_Open()
359 { /* returns 0 if OK, <0 if error */
360 char Buf[40];
361 int ret = 0;
362
363 if(readflag)
364 ret = CUSB_Open(0x1941, 0x8021);
365
366 if(ret==0) {
367 CWS_Cache(WS_CACHE_READ); // Read cache file
368 strftime(Buf,sizeof(Buf),"%Y-%m-%d %H:%M:%S", localtime(&m_previous_timestamp));
369 MsgPrintf(2, "last cached record %s\n", Buf);
370 }
371 return ret;
372 }
373
374 /*---------------------------------------------------------------------------*/
CWS_Close(int NewDataFlg)375 int CWS_Close(int NewDataFlg)
376 {
377 char Buf[40];
378
379 if(NewDataFlg) CWS_Cache(WS_CACHE_WRITE); // Write cache file
380 strftime(Buf,sizeof(Buf),"%Y-%m-%d %H:%M:%S", localtime(&m_timestamp));
381 MsgPrintf(2, "last record read %s\n", Buf);
382 if(readflag)
383 CUSB_Close();
384 return 0;
385 }
386
387 /*---------------------------------------------------------------------------*/
CWS_dec_ptr(unsigned short ptr)388 unsigned short CWS_dec_ptr(unsigned short ptr)
389 {
390 // Step backwards through buffer.
391 ptr -= WS_BUFFER_RECORD;
392 if (ptr < WS_BUFFER_START)
393 // Start is reached, step to end of buffer.
394 ptr = WS_BUFFER_END;
395 return ptr;
396 }
397
398 /*---------------------------------------------------------------------------*/
CWS_inc_ptr(unsigned short ptr)399 unsigned short CWS_inc_ptr(unsigned short ptr)
400 {
401 // Step forward through buffer.
402 ptr += WS_BUFFER_RECORD;
403 if((ptr > WS_BUFFER_END)||(ptr < WS_BUFFER_START))
404 // End is reached, step to start of buffer.
405 ptr = WS_BUFFER_START;
406 return ptr;
407 }
408
409 /*---------------------------------------------------------------------------*/
CWS_DataHasChanged(unsigned char OldBuf[],unsigned char NewBuf[],size_t size)410 short CWS_DataHasChanged(unsigned char OldBuf[], unsigned char NewBuf[], size_t size)
411 { // copies size bytes from NewBuf to OldBuf, if changed
412 // returns 0 if nothing changed, otherwise 1
413 short NewDataFlg = 0;
414
415 for(short i=0; i<size; ++i) {
416 if(OldBuf[i]!=NewBuf[i]) {
417 NewDataFlg = 1;
418 MsgPrintf(3, "%04X(+%02X): %02X -> %02X\n",
419 (unsigned short)(&OldBuf[0]-m_buf), i,
420 OldBuf[i], NewBuf[i]);
421 OldBuf[i] = NewBuf[i];
422 }
423 }
424 return NewDataFlg;
425 }
426
427 /*---------------------------------------------------------------------------*/
CWS_read_fixed_block()428 short CWS_read_fixed_block()
429 { // Read fixed block in 32 byte chunks
430 unsigned short i;
431 unsigned char fb_buf[WS_FIXED_BLOCK_SIZE];
432 char NewDataFlg = 0;
433
434 for(i=WS_FIXED_BLOCK_START;i<WS_FIXED_BLOCK_SIZE;i+=WS_BUFFER_CHUNK)
435 if(CUSB_read_block(i, &fb_buf[i])<0)
436 return 0; //failure while reading data
437 // Check for new data
438 memcpy(&m_buf[WS_FIXED_BLOCK_START], fb_buf, 0x10); //disables change detection on the rain val positions
439 NewDataFlg = CWS_DataHasChanged(&m_buf[WS_FIXED_BLOCK_START], fb_buf, sizeof(fb_buf));
440 // Check for valid data
441 if(((m_buf[0]==0x55) && (m_buf[1]==0xAA))
442 || ((m_buf[0]==0xFF) && (m_buf[1]==0xFF)))
443 return NewDataFlg;
444
445 MsgPrintf(0, "Fixed block is not valid.\n");
446 exit(1);
447 }
448
449 /*---------------------------------------------------------------------------*/
CWS_calculate_rain_period(char done,unsigned short pos,unsigned short begin,unsigned short end)450 char CWS_calculate_rain_period(char done, unsigned short pos, unsigned short begin, unsigned short end)
451 {
452 if (done) // Already done?
453 return 1;
454
455 unsigned short result;
456 unsigned int begin_rain, end_rain;
457
458 begin_rain = CWS_unsigned_short(&m_buf[begin]);
459 end_rain = CWS_unsigned_short(&m_buf[end]);
460 if (begin_rain > end_rain) { // Test for wrap around in rain counter
461 end_rain += 0x10000; // Make sure that end rain counter always is the largest
462 }
463
464 result = (end_rain - begin_rain) % 0x10000; // Squeeze the result back to a short
465
466 m_buf[pos] = result % 0x100; // Lo byte
467 m_buf[pos+1] = (result / 256) % 0x100; // Hi byte
468
469 return 1;
470 }
471
472 /*---------------------------------------------------------------------------*/
CWS_calculate_rain(unsigned short current_pos,unsigned short data_count,unsigned short start)473 unsigned int CWS_calculate_rain(unsigned short current_pos, unsigned short data_count, unsigned short start)
474 {
475 /* ToDo (Josch) not tested; should be reprogrammed. There is no need to calculate sums and store them
476 in the dat file. The difference from point to point (e.g. change of hour, day and so on) gives the
477 correct value. */
478 // Initialize rain variables
479 m_buf[WS_RAIN_HOUR] = 0; m_buf[WS_RAIN_HOUR +1] = 0;
480 m_buf[WS_RAIN_DAY] = 0; m_buf[WS_RAIN_DAY +1] = 0;
481 m_buf[WS_RAIN_WEEK] = 0; m_buf[WS_RAIN_WEEK +1] = 0;
482 m_buf[WS_RAIN_MONTH] = 0; m_buf[WS_RAIN_MONTH+1] = 0;
483
484 // Flags set when calculation is done
485 char bhour = 0;
486 char bday = 0;
487 char bweek = 0;
488 char bmonth = 0;
489
490 // Set the different time periods
491 time_t hour = 60*60;
492 time_t day = 24*60*60;
493 time_t week = 7*24*60*60;
494 time_t month = 30*24*60*60;
495
496 unsigned short initial_pos = current_pos;
497 time_t timestamp = m_timestamp; // Set to current time
498
499 unsigned short i;
500 for (i=start;i<data_count;i++) { // Calculate backwards through buffer, not all values will be calculated if buffer is too short
501 if (difftime(m_timestamp,timestamp) > month) {
502 bmonth = CWS_calculate_rain_period(bmonth, WS_RAIN_MONTH, current_pos+WS_RAIN, initial_pos+WS_RAIN);
503
504 } else if (difftime(m_timestamp,timestamp) > week) {
505 bweek = CWS_calculate_rain_period(bweek, WS_RAIN_WEEK, current_pos+WS_RAIN, initial_pos+WS_RAIN);
506
507 } else if (difftime(m_timestamp,timestamp) > day) {
508 bday = CWS_calculate_rain_period(bday, WS_RAIN_DAY, current_pos+WS_RAIN, initial_pos+WS_RAIN);
509
510 } else if (difftime(m_timestamp,timestamp) > hour) {
511 bhour = CWS_calculate_rain_period(bhour, WS_RAIN_HOUR, current_pos+WS_RAIN, initial_pos+WS_RAIN);
512
513 }
514
515 timestamp -= m_buf[current_pos+WS_DELAY]*60; // Update timestamp
516
517 current_pos=CWS_dec_ptr(current_pos);
518 }
519
520 return (0);
521 }
522
523 /*---------------------------------------------------------------------------*/
CWS_dew_point(char * raw,float scale,float offset)524 float CWS_dew_point(char* raw, float scale, float offset)
525 {
526 float temp = CWS_signed_short(raw+WS_TEMPERATURE_OUT) * scale + offset;
527 float hum = raw[WS_HUMIDITY_OUT];
528
529 // Compute dew point, using formula from
530 // http://en.wikipedia.org/wiki/Dew_point.
531 float a = 17.27;
532 float b = 237.7;
533
534 float gamma = ((a * temp) / (b + temp)) + log(hum / 100);
535
536 return (b * gamma) / (a - gamma);
537 }
538
539 /*---------------------------------------------------------------------------*/
CWS_bcd_decode(unsigned char byte)540 unsigned char CWS_bcd_decode(unsigned char byte)
541 {
542 unsigned char lo = byte & 0x0F;
543 unsigned char hi = byte / 16;
544 return (lo + (hi * 10));
545 }
546
547 /*---------------------------------------------------------------------------*/
548 //Josch: keep this 2 functions, because they also work with big endian (not needed for intel,
549 // but -may be- for arm)
CWS_unsigned_short(unsigned char * raw)550 unsigned short CWS_unsigned_short(unsigned char* raw)
551 {
552 return raw[0] + (raw[1] * 256);
553 }
554
CWS_signed_short(unsigned char * raw)555 signed short CWS_signed_short(unsigned char* raw)
556 {
557 return raw[0] + (raw[1] * 256);
558 }
559
560 /*---------------------------------------------------------------------------*/
CWS_decode(unsigned char * raw,enum ws_types ws_type,float scale,float offset,char * result)561 int CWS_decode(unsigned char* raw, enum ws_types ws_type, float scale, float offset, char* result)
562 {
563 int n = 0;
564 float fresult;
565
566 if(!result) return 0;
567 else *result = '\0';
568 switch(ws_type) {
569 case ub:
570 fresult = raw[0] * scale + offset;
571 n=sprintf(result,"%.1f", fresult);
572 break;
573 case sb:
574 fresult = (signed char)raw[0] * scale + offset;
575 n=sprintf(result,"%.1f", fresult);
576 break;
577 case us:
578 fresult = CWS_unsigned_short(raw) * scale + offset;
579 n=sprintf(result,"%.1f", fresult);
580 break;
581 case ss:
582 fresult = CWS_signed_short(raw) * scale + offset;
583 n=sprintf(result,"%.1f", fresult);
584 break;
585 case dt:
586 {
587 unsigned char year, month, day, hour, minute;
588 year = CWS_bcd_decode(raw[0]);
589 month = CWS_bcd_decode(raw[1]);
590 day = CWS_bcd_decode(raw[2]);
591 hour = CWS_bcd_decode(raw[3]);
592 minute = CWS_bcd_decode(raw[4]);
593 n=sprintf(result,"%4d-%02d-%02d %02d:%02d", year + 2000, month, day, hour, minute);
594 }
595 break;
596 case tt:
597 n=sprintf(result,"%02d:%02d", CWS_bcd_decode(raw[0]), CWS_bcd_decode(raw[1]));
598 break;
599 case pb:
600 n = sprintf(result,"%02x", raw[0]);
601 break;
602 case wa:
603 // wind average - 12 bits split across a byte and a nibble
604 fresult = raw[0] + ((raw[2] & 0x0F) * 256);
605 fresult = fresult * scale + offset;
606 n=sprintf(result,"%.1f", fresult);
607 break;
608 case wg:
609 // wind gust - 12 bits split across a byte and a nibble
610 fresult = raw[0] + ((raw[1] & 0xF0) * 16);
611 fresult = fresult * scale + offset;
612 n=sprintf(result,"%.1f", fresult);
613 break;
614 case dp:
615 // Scale outside temperature and calculate dew point
616 fresult = CWS_dew_point(raw, scale, offset);
617 n=sprintf(result,"%.1f", fresult);
618 break;
619 default:
620 MsgPrintf(0, "CWS_decode: Unknown type %u\n", ws_type);
621 }
622 return n;
623 }
624
625 /*---------------------------------------------------------------------------*/
CWS_Read()626 int CWS_Read()
627 {
628 // Read fixed block
629 // - Get current_pos
630 // - Get data_count
631 // Read records backwards from current_pos untill previous current_pos reached
632 // Step 0x10 in the range 0x10000 to 0x100, wrap at 0x100
633 // USB is read in 0x20 byte chunks, so read at even positions only
634 // return 1 if new data, otherwise 0
635
636 m_timestamp = time(NULL); // Set to current time
637 old_pos = CWS_unsigned_short(&m_buf[WS_CURRENT_POS]);
638
639 int n, NewDataFlg = CWS_read_fixed_block();
640 unsigned char DataBuf[WS_BUFFER_CHUNK];
641
642 unsigned short data_count = CWS_unsigned_short(&m_buf[WS_DATA_COUNT]);
643 unsigned short current_pos= CWS_unsigned_short(&m_buf[WS_CURRENT_POS]);
644
645 if(current_pos%WS_BUFFER_RECORD) {
646 MsgPrintf(0, "CWS_Read: wrong current_pos=0x%04X\n", current_pos);
647 exit(1);
648 }
649 for (unsigned short i=0; i<data_count; ) {
650 if (!(current_pos&WS_BUFFER_RECORD)) {
651 // Read 2 records on even position
652 n = CUSB_read_block(current_pos, DataBuf);
653 if(n<32)
654 exit(1);
655 i += 2;
656 NewDataFlg |= CWS_DataHasChanged(&m_buf[current_pos], DataBuf, sizeof(DataBuf));
657 }
658 if(current_pos==(old_pos &(~WS_BUFFER_RECORD)))
659 break; //break only on even position
660 current_pos = CWS_dec_ptr(current_pos);
661 }
662 if((old_pos==0)||(old_pos==0xFFFF)) //cachefile empty or empty eeprom was read
663 old_pos = CWS_inc_ptr(current_pos);
664
665 return NewDataFlg;
666 }
667
668 /***************** The CWF class *********************************************/
CWF_Write(char arg,const char * fname,const char * ftype)669 int CWF_Write(char arg, const char* fname, const char* ftype)
670 {
671 // - Get current_pos
672 // - Get data_count
673 // Read data_count records forward from old_pos to current_pos.
674 // Calculate timestamp and break if already written
675 // Step 0x10 in the range 0x10000 to 0x100
676 // Store output file in requested format
677
678 time_t timestamp = m_timestamp - m_timestamp%60; // Set to current minute
679
680 unsigned short data_count = CWS_unsigned_short(&m_buf[WS_DATA_COUNT]);
681 unsigned short current_pos = CWS_unsigned_short(&m_buf[WS_CURRENT_POS]);
682 unsigned short end_pos = current_pos;
683 char s1[1000] = "", s2[1000] = "";
684 int n;
685 FILE* f = stdout;
686 int FileIsEmpty = 0;
687
688 if(arg!='c') { // open output file if neccessary and check if still empty
689 sprintf(s1, fname, ftype);
690 f = fopen(s1,"rt");
691 if(f) fclose(f); else FileIsEmpty = 1;
692 f = fopen(s1,"a+t");
693 if(!f)
694 return -1;
695 printf("writing to %s\n", s1);
696 }
697
698 if((old_pos==0)||(old_pos==0xFFFF)) //cachefile empty or empty eeprom was read
699 old_pos = current_pos;
700
701 // Header
702 switch (arg) {
703 case 'x':
704 fputs("<ws>\n",f);
705 break;
706 };
707
708 // Body
709 if(arg!='c') while(current_pos!=old_pos) { // get record & time to start output from
710 timestamp -= m_buf[current_pos+WS_DELAY]*60; // Update timestamp
711 current_pos = CWS_dec_ptr(current_pos);
712 }
713
714 for(unsigned short i=0; i<data_count; i++)
715 {
716 if((arg!='c')&&(arg!='f'))
717 CWS_calculate_rain(current_pos, data_count, i);
718
719 if((arg!='c')&&LogToScreen&&(current_pos==end_pos))
720 break; // current record is logged by FHEM itself if -c is set
721
722 switch (arg) {
723 case 'c':
724 // Output in FHEM ws3600 format
725 // n=strftime(s1,sizeof(s1),"DTime %d-%m-%Y %H:%M:%S\n", gmtime(×tamp));
726 n=strftime(s1,sizeof(s1),"DTime %d-%m-%Y %H:%M:%S\n", localtime(×tamp));
727 for (int j=0; ws3600_record[j].name[0]; j++) {
728 int pos = ws3600_record[j].pos;
729 if(pos<WS_BUFFER_RECORD) //record or fixed block?
730 pos += current_pos; //record
731 CWS_decode(&m_buf[pos],
732 ws3600_record[j].ws_type,
733 ws3600_record[j].scale,
734 0.,
735 s2);
736 sprintf(s1+strlen(s1), "%s %s\n", ws3600_record[j].name, s2);
737 };
738 break;
739 case 'f':
740 // Save in FHEM log format
741 if(FileIsEmpty) fputs("DateTime WS", f);
742 // n=strftime(s1,sizeof(s1),"%Y-%m-%d_%H:%M:%S", gmtime(×tamp));
743 n=strftime(s1,sizeof(s1),"%Y-%m-%d_%H:%M:%S WS", localtime(×tamp));
744 for (int j=0; ws3600_record[j].name[0]; j++) {
745 int pos = ws3600_record[j].pos;
746 if(pos<WS_BUFFER_RECORD) //record or fixed block?
747 pos += current_pos; //record
748 if(FileIsEmpty)
749 fprintf(f, " %s", ws3600_record[j].name);
750 CWS_decode(&m_buf[pos],
751 ws3600_record[j].ws_type,
752 ws3600_record[j].scale,
753 0.,
754 s2);
755 sprintf(s1+strlen(s1), " %s", s2);
756 };
757 if(FileIsEmpty) { fputs("\n", f); FileIsEmpty = 0; }
758 break;
759 case 'p':
760 // Save in pywws raw format
761 n=strftime(s1,100,"%Y-%m-%d %H:%M:%S", gmtime(×tamp));
762 for (int j=0;j<WS_PYWWS_RECORDS;j++) {
763 CWS_decode(&m_buf[current_pos+pywws_format[j].pos],
764 pywws_format[j].ws_type,
765 pywws_format[j].scale,
766 0.0,
767 s2);
768 sprintf(s1+strlen(s1), ",%s", s2);
769 };
770 break;
771 case 's':
772 // Save in PWS Weather format
773 // n=strftime(s1,100,"dateutc=%Y-%m-%d+%H\%%3A%M\%%3A%S", gmtime(×tamp));
774 n=strftime(s1,100,"dateutc=%Y-%m-%d+%H:%M:%S", gmtime(×tamp));
775 // Calculate relative pressure
776 pws_format[WS_PWS_PRESSURE].offset
777 += (
778 CWS_unsigned_short(m_buf+WS_CURR_REL_PRESSURE)
779 - CWS_unsigned_short(m_buf+WS_CURR_ABS_PRESSURE)
780 ) * WS_SCALE_HPA_TO_INHG;
781 for (int j=0;j<WS_PWS_RECORDS;j++) {
782 if (j==WS_PWS_HOURLY_RAIN || j==WS_PWS_DAILY_RAIN) {
783 CWS_decode(&m_buf[pws_format[j].pos],
784 pws_format[j].ws_type,
785 pws_format[j].scale,
786 pws_format[j].offset,
787 s2);
788 } else {
789 CWS_decode(&m_buf[current_pos+pws_format[j].pos],
790 pws_format[j].ws_type,
791 pws_format[j].scale,
792 pws_format[j].offset,
793 s2);
794 }
795 sprintf(s1+strlen(s1), "&%s=%s", pws_format[j].name, s2);
796 };
797 break;
798 case 'w':
799 // Save in Wunderground format
800 n=strftime(s1,100,"dateutc=%Y-%m-%d %H:%M:%S", gmtime(×tamp));
801 // Calculate relative pressure
802 wug_format[WS_WUG_PRESSURE].offset
803 += (
804 CWS_unsigned_short(m_buf+WS_CURR_REL_PRESSURE)
805 - CWS_unsigned_short(m_buf+WS_CURR_ABS_PRESSURE)
806 ) * WS_SCALE_HPA_TO_INHG;
807 for (int j=0;j<WS_WUG_RECORDS;j++) {
808 if (j==WS_WUG_HOURLY_RAIN || j==WS_WUG_DAILY_RAIN) {
809 CWS_decode(&m_buf[wug_format[j].pos],
810 wug_format[j].ws_type,
811 wug_format[j].scale,
812 wug_format[j].offset,
813 s2);
814 } else {
815 CWS_decode(&m_buf[wug_format[j].pos+current_pos],
816 wug_format[j].ws_type,
817 wug_format[j].scale,
818 wug_format[j].offset,
819 s2);
820 }
821 sprintf(s1+strlen(s1), "&%s=%s", wug_format[j].name, s2);
822 };
823 break;
824 case 'x':
825 // Save in XML format
826 n=strftime(s1,100," <wsd date=\"%Y-%m-%d %H:%M:%S\"", gmtime(×tamp));
827 for (int j=0;j<WS_RECORDS;j++) {
828 CWS_decode(&m_buf[current_pos+ws_format[j].pos],
829 ws_format[j].ws_type,
830 ws_format[j].scale,
831 0.0,
832 s2);
833 sprintf(s1+strlen(s1), " %s=\"%s\"", ws_format[j].name, s2);
834 };
835 strcat(s1,">");
836 break;
837 default:
838 MsgPrintf(0, "Unknown log file format.\n");
839 };
840
841 strcat(s1,"\n");
842 fputs(s1,f);
843
844 if(current_pos==end_pos)
845 break; // All new records written
846
847 timestamp += m_buf[current_pos+WS_DELAY]*60; // Update timestamp
848 current_pos = CWS_inc_ptr(current_pos);
849 };
850
851 // Footer
852 switch (arg) {
853 case 'x':
854 fputs("</ws>\n",f);
855 break;
856 };
857
858 if(arg!='c') fclose(f);
859 return(0);
860 }
861
862 /*****************************************************************************/
main(int argc,char ** argv)863 int main(int argc, char **argv)
864 {
865 int bflag = 0; // Display fixed block
866 int dflag = 0; // Dump decoded fixed block data
867 int rflag = 0; // Dump all weather station records
868
869 int fflag = 0; // Create fhemws.log
870 int pflag = 0; // Create pywws.log
871 int sflag = 0; // Create pwsweather.log
872 int wflag = 0; // Create wunderground.log
873 int xflag = 0; // Create fowsr.xml
874
875 int NewDataFlg = 0; // write to cache file or not
876 int c;
877 time_t tAkt = time(NULL);
878 char Buf[40], Buf2[200];
879
880 strcpy(LogPath, LOGPATH);
881
882 while ((c = getopt (argc, argv, "bcdf:n:rpswxv:")) != -1)
883 {
884 switch (c)
885 {
886 case 'b': // Display fixed block
887 bflag = 1;
888 break;
889 case 'd': // Dump decoded fixed block data
890 dflag = 1;
891 break;
892 case 'c':
893 readflag = 1;
894 LogToScreen = 1;
895 break;
896 case 'n': {
897 readflag = 1;
898 strftime(LogPath,sizeof(LogPath),optarg, localtime(&tAkt));
899 MsgPrintf(3, "option -n with value '%s'\n", LogPath);
900 break;
901 }
902 case 'r': // Dump all weather station records
903 rflag = 1;
904 break;
905 case 'f':
906 readflag = 1;
907 switch(optarg[0]) {
908 case 'f': fflag = 1; break;
909 case 'p': pflag = 1; break;
910 case 's': sflag = 1; break;
911 case 'w': wflag = 1; break;
912 case 'x': xflag = 1; break;
913 default:
914 MsgPrintf(0, "wrong option -f%s\n", optarg);
915 abort();
916 break;
917 }
918 break;
919 case 'v':
920 if(optarg[1]) switch(optarg[1]) {
921 case 'b': vDst = 'b'; break;
922 case 'f': vDst = 'f'; break;
923 default:
924 MsgPrintf(0, "Wrong option -v%s. Used -v%cc instead.\n", optarg, optarg[0]);
925 case 'c':
926 vDst = 'c'; break;
927 }
928 else {
929 MsgPrintf(0, "Wrong option -v%s. Used -v0c instead.\n", optarg);
930 vDst = 'c';
931 }
932 vLevel = atoi(optarg);
933 // MsgPrintf (3, "option v with value '%s' / Level=%d Dst=%c\n", optarg, vLevel, vDst);
934 break;
935 case '?':
936 printf("\n");
937 printf("Fine Offset Weather Station Reader "VERSION"\n\n");
938 printf("(c) 2013 Joerg Schulz (Josch at abwesend dot de)\n");
939 printf("(c) 2010 Arne-Jørgen Auberg (arne.jorgen.auberg@gmail.com)\n");
940 printf("Credits to Michael Pendec, Jim Easterbrook, Timo Juhani Lindfors\n\n");
941 printf("See http://fowsr.googlecode.com for more information\n\n");
942 printf("options\n");
943 printf(" -f[p|s|w|x|f] set Logformat for weather data\n");
944 printf(" -fp Logfile in pywws format\n");
945 printf(" -fs Logfile in PWS Weather format\n");
946 printf(" -fw Logfile in Wunderground format\n");
947 printf(" -fx Logfile in XML format\n");
948 printf(" -ff Logfile in FHEM log format\n");
949 printf(" -c Log to screen (in FHEM-WS3600 format)\n");
950 printf(" -n<filename> set full path and name for weather data, may contain\n");
951 printf(" %%-wildcards of the POSIX strftime function and %%%%s\n");
952 printf(" for a type specific name part\n");
953 printf(" default for pywws is: "WORKPATH"pywws.log\n");
954 printf(" -b Display fixed block\n");
955 printf(" -d Display decoded fixed block data\n");
956 printf(" -r Dump all weather station records\n");
957 printf(" -v<Level><Destination> output debug messages\n");
958 printf(" Level: 0-3 0-only errors, 3-all\n");
959 printf(" Destination: (c)onsole, (f)ile (same place as weather data), (b)oth\n\n");
960 exit (0);
961 default:
962 abort();
963 }
964 }
965
966 strftime(Buf, sizeof(Buf), "%Y-%m-%d %H:%M:%S", localtime(&tAkt));
967 Buf2[0] = '\0';
968 if(vLevel>=3) {
969 strcpy(Buf2, " Cmd:");
970 for(int i=0; i<argc; ++i) {
971 sprintf(Buf2+strlen(Buf2), " %s", argv[i]);
972 }
973 }
974 MsgPrintf(1, "%s FOWSR "VERSION" started%s\n", Buf, Buf2);
975
976 if(0==CWS_Open()) { // Read the cache file and open the weather station
977
978 if (readflag)
979 if(CWS_Read()) // Read the weather station
980 NewDataFlg = 1;
981
982 // Write the log files
983 if (LogToScreen)
984 CWF_Write('c', "", "");
985 if (fflag)
986 CWF_Write('f', LogPath, "fhem_ws");
987 if (pflag)
988 CWF_Write('p', LogPath, "pywws");
989 if (sflag)
990 CWF_Write('s', LogPath, "pwsweather");
991 if (wflag)
992 CWF_Write('w', LogPath, "wunderground");
993 if (xflag)
994 CWF_Write('x', LogPath, "xml");
995
996 if (bflag) // Display fixed block
997 print_bytes(m_buf, WS_FIXED_BLOCK_SIZE);
998 if (dflag) // Dump decoded fixed block data
999 CWS_print_decoded_data();
1000 if (rflag) // Dump all weather station records
1001 print_bytes(&m_buf[WS_BUFFER_START], WS_BUFFER_SIZE-WS_BUFFER_START);
1002
1003 CWS_Close(NewDataFlg); // Write the cache file and close the weather station
1004 }
1005 return 0;
1006 }
1007
1008 /******************************** EOF ****************************************/
1009