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(&timestamp));
726 				n=strftime(s1,sizeof(s1),"DTime %d-%m-%Y %H:%M:%S\n", localtime(&timestamp));
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(&timestamp));
743 				n=strftime(s1,sizeof(s1),"%Y-%m-%d_%H:%M:%S WS", localtime(&timestamp));
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(&timestamp));
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(&timestamp));
774 				n=strftime(s1,100,"dateutc=%Y-%m-%d+%H:%M:%S", gmtime(&timestamp));
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(&timestamp));
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(&timestamp));
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