finally got it working with using this driver and adding it to 
/usr/share/weewx/weewx/drivers i also added this below into the weewx 
config file 

[WMR300]
    # This section is for WMR300 weather stations.
    
    # The station model, e.g., WMR300A
    model = WMR300
    
    # The driver to use:
    driver = weewx.drivers.wmr300

   history_clear_pct = 5

    debug_decode=0
    debug_history=0
    debug_rain=1
# Set debug_backend to 0 for default lib
# 1 for libusb0
# 2 for libusb1
# 3 for openusb
# 4 for ?
    debug_backend=0


On Wednesday, 6 December 2017 19:53:35 UTC, Markus Benedikt Biewer wrote:
>
> I wrote you a private mail, I hope you have received it? I can give you a 
> SSH access to my instattion, which might be easier in terms of time 
> (although I won't learn sth)... Just let me know....
>
> Salut,
> Markus
>
> --
>
> Am Mittwoch, 6. Dezember 2017 15:23:09 UTC+1 schrieb mwall:
>>
>> scott and markus (and other wmr300 users),
>>
>> as of dec2017, there are two versions of the wmr300 driver:
>>
>> 1) wmr300 version 0.18 (in the weewx distribution)
>>
>> 2) wmr300x version 0.18nolegacy.05.01a
>>
>> of course we would like to bring these into a single driver.  the 
>> difficult part of that is being able to test all of the permutations.
>>
>> these are the outstanding issues:
>>
>> - how to get the initial startup to work with all libusb versions?  the 
>> timeouts you see are when the driver cannot get the station to start 
>> communication.
>>
>> - need to incorporate the rain counter changes from 0.18nolegacy.  that 
>> version has code that will automatically reset the rain counter - without 
>> that reset, one must manually reset the counter since the hardware does not 
>> wrap automatically.
>>
>> - figure out which libusb versions are truly broken.  for example, i 
>> experienced problems with libusb 1.0.11 that disappeared when i changed to 
>> libusb 1.0.20
>>
>> - the pyusb version should not matter; the libusb version seems to be the 
>> problem
>>
>> i developed the driver some years ago using a raspberry pi and the legacy 
>> libusb.  unfortunately the more recent libusb behave differently.
>>
>> i no longer have access to that computer or the weather station, so right 
>> now i cannot test any changes.
>>
>> what can you do right now?
>>
>> - for each configuration you try, be sure to record:
>>   - the wmr300 driver version
>>   - the weewx version
>>   - the libusb version
>>   - the pyusb version
>>
>> - try using libusb 0.1 instead of libusb 1.x
>>
>> - try using the 'nolegacy' driver with libusb 1.x
>>
>> if we can get a few people with wmr300 stations to do some testing (or 
>> give me access to their systems so i can do the testing), then we could 
>> consolidate the driver variants.
>>
>> m
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
#!/usr/bin/env python
# vim: sw=4 ts=4 expandtab
# Copyright 2015 Matthew Wall
# See the file LICENSE.txt for your rights.
#
# Credits:
# Thanks to Benji for the identification and decoding of 7 packet types
#
# Thanks to Eric G for posting USB captures and providing hardware for testing
#   https://groups.google.com/forum/#!topic/weewx-development/5R1ahy2NFsk
#
# Thanks to Zahlii
#   https://bsweather.myworkbook.de/category/weather-software/
#
# No thanks to oregon scientific - repeated requests for hardware and/or
# specifications resulted in no response at all.

# TODO: figure out battery level for each sensor
# TODO: figure out signal strength for each sensor
# TODO: figure out archive interval

# FIXME: figure out unknown bytes in history packet

# FIXME: decode the 0xdb packets

# FIXME: warn if altitude in pressure packet does not match weewx altitude

"""Driver for Oregon Scientific WMR300 weather stations.

Sensor data transmission frequencies:
  wind: 2.5 to 3 seconds
    TH: 10 to 12 seconds
  rain: 20 to 24 seconds

The station supports 1 wind, 1 rain, 1 UV, and up to 8 temperature/humidity
sensors.

Sniffing USB traffic shows all communication is interrupt.  The endpoint
descriptors for the device show this as well.  Response timing is 1.

The station ships with "Weather OS PRO" software for windows.  This was used
for the USB sniffing.

Internal observation names use the convention name_with_specifier.  These are
mapped to the wview or other schema as needed with a configuration setting.
For example, for the wview schema, wind_speed maps to windSpeed, temperature_0
maps to inTemp, and humidity_1 maps to outHumidity.

Maximum value for rain counter is 400 in (10160 mm) (40000 = 0x9c 0x40).  The
counter does not wrap; it must be reset when it hits maximum value otherwise
rain data will not be recorded.


Message types -----------------------------------------------------------------

packet types from station:
57 - station type/model; history count + other status
41 - ACK
D2 - history; 128 bytes
D3 - temperature/humidity/dewpoint/heatindex; 61 bytes
D4 - wind/windchill; 54 bytes
D5 - rain; 40 bytes
D6 - pressure; 46 bytes
DB - forecast; 32 bytes
DC - temperature/humidity ranges; 62 bytes

packet types from host:
A6 - heartbeat - response is 57 (usually)
41 - ACK
65 - do not delete history when it is reported. each of these is ack-ed by the station
b3 - delete history after you give it to me.
cd - start history request. last two bytes are one after most recent read
35 - finish history request. last two bytes are latest record index that was read.
73 - some sort of initialisation packet
72 - ? on rare occasions will be used in place of 73, in both observed cases
        the console was already free-running transmitting data

WOP sends A6 message every 20 seconds
WOP requests history at startup, then again every 120 minutes
each A6 is followed by a 57 from the station (except the one initiating history)
each data packet D* from the station is followed by an ack packet 41 from host
D2 (history) records are recorded every minute
D6 (pressure) packets seem to come every 15 minutes (900 seconds)
4,5 of 7x match 12,13 of 57

---- cameron's extra notes:

Station will free-run transmitting data for about 100s without seeing an ACK.
a6 will always be followed by 91 ca 45 42 but final byte may be 0, 20, 32, 67, 8b, d6, df, or...
    0 - when first packet after connection or program startup.
    It looks like the final byte is just the last character that was previously written to a static output buffer.
    and hence it is meaningless.
41 - ack in - 2 types
41 - ack out - numerous types. - combinations of packet type, channel, last byte.
    As for a6, it looks like the last byte is just uncleared residue in the buffer.
b3 59 0a 17 01 <eb>   - when you give me history, delete afterwards
                      - final 2 bytes are probably ignored
    response: ACK b3 59 0a 17
65 19 e5 04 52 <b6>   - when you give me history, do not delete afterwards
                      - final 2 bytes are probably ignored
    response: ACK 65 19 e5 04
cd 18 30 62 nn mm  - start history, starting at record 0xnnmm
    response - if preceeded by 65, the ACK is the same string:  ACK 65 19 e5 04
             - if preceeded by b3 then there is NO ACK.

Initialisation:
out:    a6 91 ca 45 52          - note: null 6th byte.
in:     57:  WMR300,A004,<b13><b14>\0\0,<history index>,<b21>,<b23>,        where numbered bytes are unknown content
then either...
out:    73 e5 0a 26 <b5> <b6>       - b5 is set to b13 of pkt 57, b6 <= b14
in:     41 43 4b 73 e5 0a 26 <b8> <b9> - this is the full packet 73 prefixed by "ACK"
or...
out:    72 a9 c1 60 52              - occurs when console is already free-running (but how does WOsP know?)
NO ACK

Message field decodings -------------------------------------------------------

Values are stored in 1 to 3 bytes in big endian order.  Negative numbers are
stored as Two's Complement (if the first byte starts with F it is a negative
number). Count values are unsigned.

no data:
 7f ff

values for channel number:
0 - console sensor
1 - sensor 1
2 - sensor 2
...
8 - sensor 8

values for trend:
0 - steady
1 - rising
2 - falling
3 - no sensor data

bitwise transformation for compass direction:
1000 0000 0000 0000 = NNW
0100 0000 0000 0000 = NW
0010 0000 0000 0000 = WNW
0001 0000 0000 0000 = W
0000 1000 0000 0000 = WSW
0000 0100 0000 0000 = SW
0000 0010 0000 0000 = SSW
0000 0001 0000 0000 = S
0000 0000 1000 0000 = SSE
0000 0000 0100 0000 = SE
0000 0000 0010 0000 = ESE
0000 0000 0001 0000 = E
0000 0000 0000 1000 = ENE
0000 0000 0000 0100 = NE
0000 0000 0000 0010 = NNE
0000 0000 0000 0001 = N 

values for forecast:
0x08 - cloudy
0x0c - rainy
0x1e - partly cloudy
0x0e - partly cloudy at night
0x70 - sunny
0x00 - clear night


Message decodings -------------------------------------------------------------

message: ACK
byte hex dec description                 decoded value
 0   41  A   acknowledgement             ACK
 1   43  C
 2   4b  K
 3   73                                 command sent from PC
 4   e5
 5   0a
 6   26
 7   0e
 8   c1

examples:
 41 43 4b 73 e5 0a 26 0e c1             last 2 bytes differ
 41 43 4b 65 19 e5 04                   always same


message: station info
byte hex dec description                 decoded value
 0   57  W   station type                WMR300
 1   4d  M
 2   52  R
 3   33  3
 4   30  0
 5   30  0
 6   2c  ,
 7   41  A   station model               A002
 8   30  0
 9   30  0
10   32  2                      - or 0x34
11   2c  ,
12   0e                         - (3777 dec) or mine always 88 8b  (34955)
13   c1
14   00                         - always?
15   00
16   2c  ,
17   67      next history record      26391 (0x67*256 0x17)  (0x7fe0 (32736) is full)
                        The value at this index has not been used yet.
18   17
19   2c  ,
20   4b             - usually 'K' (0x4b). occasionally was 0x43  -
  or 43               when history is set to delete after downloading.
                      This is a 1-bit change (1<<3)
                      NB: Does not return to 4b when latest history record is reset to 0x20 after
                      history is deleted. 
21   2c  ,
22   52             - 0x52 (82, 'R'), occasionally 0x49(73, 'G')
                      0b 0101 0010   (0x52) vs
                      0b 0100 1001   (0x49)  lots of bits flipped!
  or 49               this maybe has some link with one or other battery, but does not make sense
23   2c  ,

examples:
 57 4d 52 33 30 30 2c 41 30 30 32 2c 0e c1 00 00 2c 67 17 2c 4b 2c 52 2c
 57 4d 52 33 30 30 2c 41 30 30 32 2c 88 8b 00 00 2c 2f b5 2c 4b 2c 52 2c
 57 4d 52 33 30 30 2c 41 30 30 34 2c 0e c1 00 00 2c 7f e0 2c 4b 2c 49 2c
 57 4d 52 33 30 30 2c 41 30 30 34 2c 88 8b 00 00 2c 7f e0 2c 4b 2c 49 2c

message: history
byte hex dec description                 decoded value
 0   d2      packet type
 1   80  128 packet length
 2   31      count                       12694  - index number of this packet
 3   96       "
 4   0f   15 year                        ee if not set
 5   08    8 month                       ee if not set
 6   0a   10 day                         ee if not set
 7   06    6 hour
 8   02    2 minute
 9   00      temperature 0               21.7 C
10   d9
11   00      temperature 1               25.4 C
12   fe
13   7f      temperature 2
14   ff
15   7f      temperature 3
16   ff
17   7f      temperature 4
18   ff
19   7f      temperature 5
20   ff
21   7f      temperature 6
22   ff
23   7f      temperature 7
24   ff
25   7f      temperature 8
26   ff        (a*256 + b)/10
27   26      humidity 0                  38 %
28   49      humidity 1                  73 %
29   7f      humidity 2
30   7f      humidity 3
31   7f      humidity 4
32   7f      humidity 5
33   7f      humidity 6
34   7f      humidity 7
35   7f      humidity 8
36   00      dewpoint 1                  20.0 C
37   c8        (a*256 + b)/10
38   7f      dewpoint 2
39   ff
40   7f      dewpoint 3
41   ff
42   7f      dewpoint 4
43   ff
44   7f      dewpoint 5
45   ff
46   7f      dewpoint 6
47   ff
48   7f      dewpoint 7
49   ff
50   7f      dewpoint 8
51   ff
52   7f      heat index 1               C
53   fd        (a*256 + b)/10
54   7f      heat index 2
55   ff
56   7f      heat index 3
57   ff
58   7f      heat index 4
59   ff
60   7f      heat index 5
61   ff
62   7f      heat index 6
63   ff
64   7f      heat index 7
65   ff
66   7f      heat index 8
67   ff
68   7f      wind chill                C
69   fd        (a*256 + b)/10
70   7f      ?
71   ff      ?
72   00      wind gust speed           0.0 m/s
73   00        (a*256 + b)/10
74   00      wind average speed        0.0 m/s
75   00        (a*256 + b)/10
76   01      wind gust direction       283 degrees
77   1b        (a*256 + b)
78   01      wind average direction    283 degrees
78   1b        (a*256 + b)
80   30      forecast
81   00      ?
82   00      ?
83   00      hourly rain              hundredths_of_inch
84   00        (a*256 + b)
85   00      ?
86   00      accumulated rain         hundredths_of_inch
87   03        (a*256 + b)
88   0f      accumulated rain start year
89   07      accumulated rain start month
90   09      accumulated rain start day
91   13      accumulated rain start hour
92   09      accumulated rain start minute
93   00      rain rate                hundredths_of_inch/hour
94   00        (a*256 + b)
95   26      pressure                 mbar
96   ab        (a*256 + b)/10
97   01      pressure trend
98   7f      ?
99   ff      ?
100  7f      ?
101  ff      ?
102  7f      ?
103  ff      ?
104  7f      ?
105  ff      ?
106  7f      ?
107  7f      ?
108  7f      ?
109  7f      ?
110  7f      ?
111  7f      ?
112  7f      ?
113  7f      ?
114  ff      ?
115  7f      ?
116  ff      ?
117  7f      ?
118  ff      ?
119  00      ?
120  00      ?
121  00      ?
122  00      ?
123  00      ?
124  00      ?
125  00      ?
126  f8      checksum
127  3b


message: temperature/humidity/dewpoint
byte hex dec description                 decoded value
 0   D3      packet type
 1   3D   61 packet length
 2   0E   14 year
 3   05    5 month
 4   09    9 day
 5   12   12 hour
 6   14   20 minute
 7   01    1 channel number
 8   00      temperature                 19.5 C
 9   C3 
10   2D      humidity                    45 %
11   00      dewpoint                    7.0 C
12   46
13   7F      heat index                  N/A
14   FD 
15   00      temperature trend
16   00      humidity trend     (not sure - never saw a falling value)
17   0E   14 max_dewpoint_last_day year
18   05    5 month
19   09    9 day
20   0A   10 hour
21   24   36 minute
22   00      max_dewpoint_last_day       13.0 C
23   82 
24   0E   14 min_dewpoint_last_day year
25   05    5 month
26   09    9 day
27   10   16 hour
28   1F   31 minute
29   00      min_dewpoint_last_day       6.0 C
30   3C 
31   0E   14 max_dewpoint_last_month year
32   05    5 month
33   01    1 day
34   0F   15 hour
35   1B   27 minute
36   00      max_dewpoint_last_month     13.0 C
37   82 
38   0E   14 min_dewpoint_last_month year
39   05    5 month
40   04    4 day
41   0B   11 hour
42   08    8 minute
43   FF      min_dewpoint_last_month     -1.0 C
44   F6 
45   0E   14 max_heat_index year
46   05    5 month
47   09    9 day
48   00    0 hour
49   00    0 minute
50   7F      max_heat_index              N/A
51   FF 
52   0E   14 min_heat_index year
53   05    5 month
54   01    1 day
55   00    0 hour
56   00    0 minute
57   7F      min_heat_index              N/A
58   FF 
59   0B      checksum
60   63     

 0   41      ACK
 1   43 
 2   4B 
 3   D3      packet type
 4   01      channel number
 5   8B                                  sometimes DF and others

examples:
 41 43 4b d3 00 20      - for last byte: 32, 67, 8b, d6
 41 43 4b d3 01 20      - for last byte: same + 20, df
 for unused temps, last byte always 8b (or is it byte 14 of pkt 57?)


message: wind
byte hex dec description                 decoded value
 0   D4      packet type
 1   36   54 packet length
 2   0E   14 year
 3   05    5 month
 4   09    9 day
 5   12   18 hour
 6   14   20 minute
 7   01    1 channel number
 8   00      gust speed                  1.4 m/s
 9   0E 
10   00      gust direction              168 degrees
11   A8 
12   00      average speed               2.9 m/s
13   1D 
14   00      average direction           13 degrees
15   0D 
16   00      compass direction           3 N/NNE
17   03 
18   7F      windchill                   32765 N/A
19   FD 
20   0E   14 gust today year
21   05    5 month
22   09    9 day
23   10   16 hour
24   3B   59 minute
25   00      gust today                  10 m/s
26   64 
27   00      gust direction today        39 degree
28   27 
29   0E   14 gust this month year
30   05    5 month
31   09    9 day
32   10   16 hour
33   3B   59 minute
34   00      gust this month             10 m/s
35   64 
36   00      gust direction this month   39 degree
37   27 
38   0E   14 wind chill today year
39   05    5 month
40   09    9 day
41   00    0 hour
42   00    0 minute
43   7F      windchill  today            N/A
44   FF 
45   0E   14 windchill this month year
46   05    5 month
47   03    3 day
48   09    9 hour
49   04    4 minute
50   00      windchill this month        2.9 C
51   1D 
52   07      checksum
53   6A 

 0   41      ACK
 1   43 
 2   4B 
 3   D4      packet type
 4   01      channel number
 5   8B     variable

examples:
 41 43 4b d4 01 20      - last byte: 20, 32, 67, 8b, d6, df
 41 43 4b d4 01 16


message: rain
byte hex dec description                 decoded value
 0   D5      packet type
 1   28  40  packet length
 2   0E  14  year
 3   05   5  month
 4   09   9  day
 5   12  18  hour
 6   15  21  minute
 7   01   1  channel number
 8   00
 9   00      rainfall this hour          0 inch
10   00
11   00 
12   00      rainfall last 24 hours      0.12 inch
13   0C  12
14   00 
15   00      rainfall accumulated        1.61 inch
16   A1 161
17   00      rainfall rate               0 inch/hr
18   00
19   0E  14  accumulated start year
20   04   4  month
21   1D  29  day
22   12  18  hour
23   00   0  minute
24   0E  14  max rate last 24 hours year
25   05   5  month
26   09   9  day
27   01   1  hour
28   0C  12  minute
29   00   0  max rate last 24 hours      0.11 inch/hr ((0x00<<8)+0x0b)/100.0
30   0B  11
31   0E  14  max rate last month year
32   05   5  month
33   02   2  day
34   04   4  hour
35   0C  12  minute
36   00   0  max rate last month         1.46 inch/hr ((0x00<<8)+0x92)/100.0
37   92 146
38   03      checksum                    794 = (0x03<<8) + 0x1a
39   1A 

 0   41      ACK
 1   43 
 2   4B 
 3   D5      packet type
 4   01      channel number
 5   8B

examples:
 41 43 4b d5 01 20     - last byte: 20, 32, 67, 8b, d6, df
 41 43 4b d5 01 16


message: pressure
byte hex dec description                 decoded value
 0   D6      packet type
 1   2E   46 packet length
 2   0E   14 year
 3   05    5 month
 4   0D   13 day
 5   0E   14 hour
 6   30   48 minute
 7   00    1 channel number
 8   26      station pressure            981.7 mbar  ((0x26<<8)+0x59)/10.0
 9   59
10   27      sea level pressure          1015.3 mbar ((0x27<<8)+0xa9)/10.0
11   A9 
12   01      altitude meter              300 m       (0x01<<8)+0x2c
13   2C 
14   03      barometric trend (have seen 0,1,2, and 3! )
15   00      only ever observed 0 or 2.  is this battery?
16   0E   14 max pressure today year
17   05    5 max pressure today month
18   0D   13 max pressure today day
19   0C   12 max pressure today hour
20   33   51 max pressure today minute
21   27      max pressure today          1015.7 mbar
22   AD 
23   0E   14 min pressure today year
24   05    5 min pressure today month
25   0D   13 min pressure today day
26   00    0 min pressure today hour
27   06    6 min pressure today minute
28   27      min pressure today          1014.1 mbar
29   9D 
30   0E   14 max pressure month year
31   05    5 max pressure month month
32   04    4 max pressure month day
33   01    1 max pressure month hour
34   15   21 max pressure month minute
35   27      max pressure month          1022.5 mbar
36   F1 
37   0E   14 min pressure month year
38   05    5 min pressure month month
39   0B   11 min pressure month day
40   00    0 min pressure month hour
41   06    6 min pressure month minute
42   27      min pressure month          1007.8 mbar
43   5E 
44   06      checksum
45   EC 

 0   41      ACK
 1   43 
 2   4B 
 3   D6      packet type
 4   00      channel number
 5   8B 

examples:
 41 43 4b d6 00 20     - last byte: 32, 67, 8b


message: forecast
byte hex dec description                 decoded value
 0   DB
 1   20         pkt length
 2   0F  15  year
 3   07   7  month
 4   09   9  day
 5   12  18  hour
 6   23  35  minute
 7   00         below are alternate observations - little overlap
 8   FA         0a
 9   79         02, 22, 82, a2 - related to console battery!
10   FC         05  - never saw changed
11   40         f9  - never saw changed
12   01         fe  - never saw changed
13   4A         fc  - never saw changed
14   06         variable
15   17         variable
16   14         variable
17   23         variable
18   06         00 to 07 (no 01)
19   01 
20   00         00 or 01
21   00    remainder same     
22   01
23   01
24   01
25   00
26   00
27   00
28   FE
29   00
30   05      checksum
31   A5         "

 0   41      ACK
 1   43 
 2   4B 
 3   D6      packet type
 4   00      channel number
 5   20

examples:
 41 43 4b db 00 20     - last byte: 32, 67, 8b, d6


message: temperature/humidity ranges
byte hex dec description                 decoded value
 0   DC      packet type
 1   3E   62 packet length
 2   0E   14 year
 3   05    5 month
 4   0D   13 day
 5   0E   14 hour
 6   30   48 minute
 7   00    0 channel number
 8   0E   14 max temp today year
 9   05    5 month
10   0D   13 day
11   00    0 hour
12   00    0 minute
13   00      max temp today              20.8 C
14   D0 
15   0E   14 min temp today year
16   05    5 month
17   0D   13 day
18   0B   11 hour
19   34   52 minute
20   00      min temp today              19.0 C
21   BE 
22   0E   14 max temp month year
23   05    5 month
24   0A   10 day
25   0D   13 hour
26   19   25 minute
27   00      max temp month              21.4 C
28   D6 
29   0E   14 min temp month year
30   05    5 month
31   04    4 day
32   03    3 hour
33   2A   42 minute
34   00      min temp month              18.1 C
35   B5 
36   0E   14 max humidity today year
37   05    5 month
38   0D   13 day
39   05    5 hour
40   04    4 minute
41   45      max humidity today          69 %
42   0E   14 min numidity today year
43   05    5 month
44   0D   13 day
45   0B   11 hour
46   32   50 minute
47   41      min humidity today          65 %
48   0E   14 max humidity month year
49   05    5 month
50   0C   12 day
51   13   19 hour
52   32   50 minute
53   46      max humidity month          70 %
54   0E   14 min humidity month year
55   05    5 month
56   04    4 day
57   14   20 hour
58   0E   14 minute
59   39      min humidity month          57 %
60   07      checksum
61   BF 

 0   41      ACK
 1   43 
 2   4B 
 3   DC      packet type
 4   00    0 channel number
 5   8B 

examples:
 41 43 4b dc 00 20     - last byte: 32, 67, 8b, d6
 41 43 4b dc 01 20     - last byte: 20, 32, 67, 8b, d6, df
 41 43 4b dc 01 16
 41 43 4b dc 00 16

"""

from __future__ import with_statement
import syslog
import time
import usb.core
import usb.util

import weewx.drivers
import weewx.wxformulas
from weeutil.weeutil import timestamp_to_string

# x for experimental - not a model designation.
DRIVER_NAME = 'WMR300'
DRIVER_VERSION = '0.18nolegacy.05.17d'

DEBUG_COMM = 0
DEBUG_PACKET = 0
DEBUG_COUNTS = 0
DEBUG_DECODE = 0
DEBUG_HISTORY = 1
DEBUG_RAIN = 0

DEBUG_BACKEND = 0


def loader(config_dict, _):
    return WMR300Driver(**config_dict[DRIVER_NAME])

def confeditor_loader():
    return WMR300ConfEditor()


def logmsg(level, msg):
    syslog.syslog(level, 'wmr300x: %s' % msg)

def logdbg(msg):
    logmsg(syslog.LOG_DEBUG, msg)

def loginf(msg):
    logmsg(syslog.LOG_INFO, msg)

def logerr(msg):
    logmsg(syslog.LOG_ERR, msg)

def logcrt(msg):
    logmsg(syslog.LOG_CRIT, msg)

def logexception( msg, e):
    if e.errno is None:
        errnostr="Undefined"
    else:
        errnostr = str( e.errno )

    logerr( "%s exception: %s (#%s), backend: %s" % (msg, e.strerror, errnostr, str(e.backend_error_code)) )

def _fmt_bytes(data):
    return ' '.join(['%02x' % x for x in data])

def _lo(x):
    return x - 256 * (x >> 8)

def _hi(x):
    return x >> 8


class WMR300Driver(weewx.drivers.AbstractDevice):
    """weewx driver that communicates with a WMR300 weather station."""

    # the default map is for the wview schema
    # index (left) is standardised name used by weewx
    # value (right) is name used within this code. Assigned by xx_decode functions
    DEFAULT_MAP = {
        'pressure': 'pressure',
        'barometer': 'barometer',
        'windSpeed': 'wind_avg',
        'windDir': 'wind_dir',
        'windGust': 'wind_gust',
        'windGustDir': 'wind_gust_dir',
        'inTemp': 'temperature_0',
        'outTemp': 'temperature_1',
        'extraTemp1': 'temperature_2',
        'extraTemp2': 'temperature_3',
        'extraTemp3': 'temperature_4',
        'extraTemp4': 'temperature_5',
        'extraTemp5': 'temperature_6',
        'extraTemp6': 'temperature_7',
        'extraTemp7': 'temperature_8',
        'inHumidity': 'humidity_0',
        'outHumidity': 'humidity_1',
        'extraHumid1': 'humidity_2',
        'extraHumid2': 'humidity_3',
        'extraHumid3': 'humidity_4',
        'extraHumid4': 'humidity_5',
        'extraHumid5': 'humidity_6',
        'extraHumid6': 'humidity_7',
        'extraHumid7': 'humidity_8',
        'dewpoint': 'dewpoint_1',
        'extraDewpoint1': 'dewpoint_2',
        'extraDewpoint2': 'dewpoint_3',
        'extraDewpoint3': 'dewpoint_4',
        'extraDewpoint4': 'dewpoint_5',
        'extraDewpoint5': 'dewpoint_6',
        'extraDewpoint6': 'dewpoint_7',
        'extraDewpoint7': 'dewpoint_8',
        'heatindex': 'heatindex_1',
        'extraHeatindex1': 'heatindex_2',
        'extraHeatindex2': 'heatindex_3',
        'extraHeatindex3': 'heatindex_4',
        'extraHeatindex4': 'heatindex_5',
        'extraHeatindex5': 'heatindex_6',
        'extraHeatindex6': 'heatindex_7',
        'extraHeatindex7': 'heatindex_8',
        'windchill': 'windchill',
        'rainRate': 'rain_rate'}

    def __init__(self, **stn_dict):
        loginf('driver version is %s' % DRIVER_VERSION)
        self.model = stn_dict.get('model', 'WMR300x')
        self.sensor_map = dict(self.DEFAULT_MAP)
        if 'sensor_map' in stn_dict:
            self.sensor_map.update(stn_dict['sensor_map'])
        # loginf('sensor map is %s' % self.sensor_map)
        self.heartbeat = 20 # how often to send a6 messages, in seconds
        self.history_retry = 60 # how often to retry history, in seconds

        self.set_history_limit( stn_dict.get('history_clear_pct', Station.HIST_CLEAR_PERCENT )) 

        global DEBUG_COMM
        DEBUG_COMM = int(stn_dict.get('debug_comm', DEBUG_COMM))
        global DEBUG_PACKET
        DEBUG_PACKET = int(stn_dict.get('debug_packet', DEBUG_PACKET))
        global DEBUG_COUNTS
        DEBUG_COUNTS = int(stn_dict.get('debug_counts', DEBUG_COUNTS))
        global DEBUG_DECODE
        DEBUG_DECODE = int(stn_dict.get('debug_decode', DEBUG_DECODE))
        global DEBUG_HISTORY
        DEBUG_HISTORY = int(stn_dict.get('debug_history', DEBUG_HISTORY))
        global DEBUG_RAIN
        DEBUG_RAIN = int(stn_dict.get('debug_rain', DEBUG_RAIN))
        global DEBUG_BACKEND
        DEBUG_BACKEND = int(stn_dict.get('debug_backend', DEBUG_BACKEND))

        self.last_rain = None
        self.last_a6 = 0
        self.last_65 = 0
        #self.last_7x = 0       - we don't need this for anything I can see.
        self.last_record_rcvd = Station.HISTORY_START_REC -1
        self.final_history_index = 0
        self.history_pct = 0.0
        self.last_history_pct_logged = -200
        # FIXME: make the cache values age
        # FIXME: do this generically so it can be used in other drivers
        self.pressure_cache = dict()
        self.station = Station()
        self.station.open()
        self.initiateComms()

    def set_history_limit( self, limit=50 ):
        """ set the limit at which the console's history buffer will be cleared

        limit: integer value defining percentage full
        """

        self.history_clear_pct = int(limit)

        if self.history_clear_pct > 95:
            self.history_clear_pct = 95
        # it does not clear history for very low values,
        # 4% does not work
        # 5% does work
        if self.history_clear_pct < 5:
            self.history_clear_pct = 5
        loginf( "Set to clear history at %d%%" % self.history_clear_pct )
        return

    def set_history_pct(self, index_value ):
        """ assign the current history buffer percent 
        index_value = next available entry index as returned in 57 status packet
        """
        self.history_pct = 100.0 * float(index_value - Station.HISTORY_START_REC) / Station.HISTORY_N_RECORDS 
        #self.history_pct_int = int( self.history_pct )   # don't use any more

    def set_final_history_index( self, buf):
        """ read the current history index from console status packet and adjust 
        relevant values.

        Updates instance variables final_history_index and history_pct

        Returns the index value
        """
        if buf[0] != 0x57:
            return None
        # extract 16-bit number
        idx = (buf[17] << 8) + buf[18]
        if idx < Station.HISTORY_START_REC:
            raise WMR300Error("History index: %d below limit of %d" % (idx, Station.HISTORY_START_REC) )
        elif idx > Station.HISTORY_MAX_REC:
            raise WMR300Error("History index: %d above limit of %d" % (idx, Station.HISTORY_MAX_REC) )
        self.final_history_index = idx             
        self.set_history_pct( idx )
        return idx


    def closePort(self):
        self.station.close()
        self.station = None

    @property
    def hardware_name(self):
        return self.model

    def initiateComms( self ):
        r""" initiate communications with wmr300 console.

        1. send a special a6 packet
        2. read the packet 57
        3. send the type-73 packet
        4. read the ACK
        done
        """

        retrycount = 0
        while retrycount < 3:
            retrycount += 1
            try:
                n_written=0
                n_read=0
                n_read = self.station.flush_read_buffer( "initComms" )

                loginf("send initial heartbeat, try %i" % retrycount )
                cmd = [0xa6, 0x91, 0xca, 0x45, 0x52 ]
                state = "req status a6 null"
                n_written=0
                n_written = self.station.write_noTO(cmd, state)
                self.last_a6 = time.time()

                # now read the packet 57
                state = "reading packet 57"
                count=0
                while count < 10 :
                    count += 1
                    buf = self.station.read()
                    if buf is None:
                        raise InitiationError( "initComm: failed to read packet57" )        # try loop again - not sure if this is any use.
                    if buf[0] == 0x57:
                        break

                    logerr( "initComm: got 0x%02x instead of 0x57" % buf[0] )        # try loop again - not sure if this is any use.

                if buf is None or buf[0] != 0x57:
                    raise InitiationError( "failed to get initialization packet57" )        # try loop again - not sure if this is any use.
                else:
                    pkt = Station._decode_57( buf )
                    idx = self.set_final_history_index( buf )
                    self.magic0 = pkt['magic0']
                    self.magic1 = pkt['magic1']
                    # keep these in case we ever work out what they mean.
                    self.mystery0 = pkt['mystery0']
                    self.mystery1 = pkt['mystery1']

                # now write the packet 73
                cmd = [0x73, 0xe5, 0x0a, 0x26, self.magic0, self.magic1 ]
                state = "write 73"
                n_written=0
                n_written = self.station.write_noTO(cmd, "initComm write 73" )

                # and read the ACK
                state = "reading ACK to 73"
                count=0
                while count < 10 :
                    count += 1
                    buf = self.station.read()
                    if buf is None:
                        raise InitiationError( "initComm: failed to read ACK to packet73" )        # try loop again - not sure if this is any use.
                    if buf[0] == 0x41:
                        break
                    logerr( "initComm: got 0x%02x instead of ACK to packet73" % buf[0] )
                    # I don't see anything useful in the ACK, it should have same contents as the packet 73,
                    # and have no idea what to do if they are not
                
                if ( retrycount == 1 ):
                    if DEBUG_HISTORY:
                        loginf("Initiation completed"  )
                else:
                    loginf("Initiation completed in %i tries" % retrycount )
                return


            except usb.core.USBError as e:
                errmsg = repr(e)
                if e.backend_error_code == self.station.backend_timeout_code :
                    logexception( "initiateComms (from "+state+" with "+str(n_written)+" written)", e)
                elif not ('No error' in errmsg):
                    logexception( "USB failure in initiateComms (from "+state+")", e)
                    raise weewx.WeeWxIOError(e)
            except (WrongLength, BadChecksum, InitiationError), e:
                logerr( "initComms: " + repr(e))
            time.sleep(0.10)
        raise WMR300Error( "Initiation failed, excessive retries" )

    def initiateHistory( self, keep_hist=True ):
        r""" initiate history record stream from wmr300 console.
        keep_hist == True means read all unread history and retain it
                     False means (attempt to) delete the history

        1. For a read and keep history:
            1a. send a special a6 packet (which looks like all other a6 packets except that
              we get no status reply (0x57) here)
            1b. send a 65 packet 
        or for delete:
            1a. send a 0xb3 packet to read with delete, 
        2. read the ACK
        3. send a cd packet
        4. do not read an ACK - it might not come.
        - return, to start reading history packets.
        """

        retrycount = 0
        while retrycount < 5:
            retrycount += 1
            try:
                if DEBUG_HISTORY:
                    loginf("send history startup, try %i" % retrycount )
                n_read = self.station.flush_read_buffer( "initHist" )

                if keep_hist:
                    cmd = [0xa6, 0x91, 0xca, 0x45, 0x52, 0x8b ]
                    state = "req status a6 null"
                    n_written=0
                    n_written = self.station.write_noTO(cmd, "initHist a6")
                    self.last_a6 = time.time()

                    # now write  packet 65
                    cmd = [0x65, 0x19, 0xe5, 0x04, 0x52, 0x8b ]
                    cmd_type = 0x65
                    initiate_with = "0x65"
                    state = "write '65'"
                    nxtrec = Station.get_start_index( self.last_record_rcvd )
                else:
                    # ask for delete after sending
                    cmd = [0xb3, 0x59, 0x0a, 0x17, 0x01, 0xeb ]
                    cmd_type = 0xb3
                    initiate_with = "0xb3"
                    state = "write 'b3'"
                    # a partial read seems to be sufficient. No need to read stuff we are not going to save
                    nxtrec = Station.get_start_index( self.final_history_index - 1200 )
                    #nxtrec = Station.get_start_index( Station.HISTORY_START_REC )        # force full read for the moment
                n_written=0
                n_written = self.station.write_noTO(cmd, "initHist timeout on write: " + initiate_with)

                # read the ACK
                # there may be regular data packets interspersed here, so
                # potentially we need to read a few...
                state = "reading ACK"
                count=0
                while count < 10 :
                    count += 1
                    buf = self.station.read()

                    if buf is None:
                        # try big loop again - not sure if this is any use.
                        raise InitiationError( "initHist: failed to read ACK to packet " + initiate_with )
                    if buf[0] == 0x41 and buf[3] == cmd_type:
                        break

                # now send the history request start
                if DEBUG_HISTORY :
                    loginf("Initing history req with cmd %s, starting with record %d" % (initiate_with , nxtrec) )
                state = "requesting next rec " + str(nxtrec)
                cmd = [0xcd, 0x18, 0x30, 0x62, _hi(nxtrec), _lo(nxtrec)]
                n_written = self.station.write_noTO(cmd, "initHist timeout on 0xcd write")

                # DO NOT expect any ACK
                # The history loop is just a pile of writes from the console, and any ACKs or 0x57 packets are often out of sequence
                # so we just drop into the read loop and process whatever comes.
                
                if ( retrycount == 1 ):
                    if DEBUG_HISTORY:
                        loginf("History read initiated"  )
                else:
                    loginf("History read initiated in %i tries" % retrycount )
                return


            except usb.core.USBError as e:
                errmsg = repr(e)
                if e.backend_error_code == self.station.backend_timeout_code :
                    logexception( "initiateHist (from "+state+" with "+str(n_written)+" written)", e)
                elif not ('No error' in errmsg):
                    logexception( "USB failure in initiateHist (from "+state+")", e)
                    raise weewx.WeeWxIOError(e)
            except (WrongLength, BadChecksum, InitiationError), e:
                logerr( "HistRead: " + repr(e) )
            time.sleep(0.10)
        raise WMR300Error( "History Initiation failed, excessive retries" )

    def finaliseHistory( self ):
        r""" conclude history record stream from wmr300 console.

        1. final a6 has already been sent, and 57 seen
        2. send a 35 packet
        4. there is no ACK , but sometimes another 57!? ignore whatever.
        done
        """

        retrycount = 0
        while retrycount < 3:
            retrycount += 1
            try:
                if DEBUG_HISTORY:
                    loginf("history finish, try %i" % retrycount )

                # the flush is just in case it has started sending a current condition update...
                n_read = self.station.flush_read_buffer( "finHist" )

                # now write  packet 35

                cmd = [0x35, 0x0b, 0x1a, 0x87,  _hi(self.last_record_rcvd), _lo( self.last_record_rcvd )]
                state = "write 35"
                n_written=0
                n_written = self.station.write_noTO(cmd, "finaliseHist timeout on 0x35 write: ")

                
                if ( retrycount == 1 ):
                    if DEBUG_HISTORY:
                        loginf("History read completed"  )
                else:
                    loginf("History read completed in %i tries" % retrycount )
                return


            except usb.core.USBError as e:
                errmsg = repr(e)
                if e.backend_error_code == self.station.backend_timeout_code :
                    logexception( "finaliseHist (from "+state+" with "+str(n_written)+" written)", e)
                elif not ('No error' in errmsg):
                    logexception( "USB failure in finaliseHist (from "+state+")", e)
                    raise weewx.WeeWxIOError(e)
            except (WrongLength, BadChecksum, InitiationError), e:
                logerr( "Histfinalise: " + repr(e) )
            time.sleep(0.10)
        raise WMR300Error( "finalise Hist failed, excessive retries" )


    def genLoopPackets(self):
        state = "unset"        # debug state for identifying timeouts
        n_written=0
        data_since_heartbeat = 0
        while True:
            try:
                state = "reading"
                buf = self.station.read()
                if buf:
                    pkt = Station.decode(buf)
                    if buf[0] in [0xd3, 0xd4, 0xd5, 0xd6, 0xdb, 0xdc]:
                        # compose ack for most data packets
                        cmd = [0x41, 0x43, 0x4b, buf[0], buf[7] ]
                        state = "ack write"
                        n_written=0
                        # don't bother sending the ACK - the console does not care and it can lead to lockup.
                        #n_written = self.station.write_noTO(cmd, "genLoopPackets timeout on ACK write")

                        # we only care about packets with loop data
                        if pkt['packet_type'] in [0xd3, 0xd4, 0xd5, 0xd6]:
                            data_since_heartbeat += 1
                            packet = self.convert_loop(pkt)
                            yield packet
                    elif buf[0] == 0x57:
                        next_pkt_index = self.set_final_history_index( buf )
                        if next_pkt_index == Station.HISTORY_MAX_REC  and  self.last_history_pct_logged <= 100.01 :
                            logerr( "History now at FULL CAPACITY" )
                            self.last_history_pct_logged = 101      # ensure only logged once.
                        elif (self.history_pct > self.last_history_pct_logged + Station.HISTORY_LOG_INTERVAL) or (self.history_pct < self.last_history_pct_logged) :
                            loginf( "History now at %.1f%% capacity" % self.history_pct )
                            self.last_history_pct_logged = self.history_pct
                if time.time() - self.last_a6 > self.heartbeat:
                    if DEBUG_HISTORY and data_since_heartbeat < 10 :
                        loginf( "Packets in hearbeat interval = %d" % data_since_heartbeat )
                    if data_since_heartbeat <= 0 :
                        logerr( "No data in hearbeat interval, trying to restart" )
                        n_read = self.station.flush_read_buffer( "genLoopPackets" )
                        # I think the 0x73 starts the data transmission, but not sure if the 
                        # a6 / 73 order is important.
                        cmd = [0x73, 0xe5, 0x0a, 0x26, self.magic0, self.magic1 ]
                        state = "write 73"
                        n_written=0
                        n_written = self.station.write_noTO(cmd, "genLoopPackets write 73" )

                    data_since_heartbeat = 0 
                    cmd = [0xa6, 0x91, 0xca, 0x45, 0x52  ]
                    state = "req status a6"
                    n_written=0
                    n_written = self.station.write_noTO(cmd, "genloop heartbeat" )
                    self.last_a6 = time.time()
                if self.history_pct >= self.history_clear_pct :
                    # it is time to clear console's history buffer...
                    loginf( "History at %d%% capacity exceeds limit of %d%%; clearing..." %
                                    (self.history_pct, self.history_clear_pct) )
                    reread_start_time = time.time() 
                    self.reread_history_records( reread_start_time )
                    reread_duration = time.time() - reread_start_time 
                    loginf( "History clear completed in %.1f sec" % reread_duration )
                    pass

            except usb.core.USBError as e:
                errmsg = repr(e)
                if e.backend_error_code == self.station.backend_timeout_code :
                    logexception( "genLoopPackets (from "+state+" with "+str(n_written)+" written)", e)
                elif not ('No error' in errmsg):
                    logexception( "USB failure in GenLoopPackets (from "+state+")", e)
                    raise weewx.WeeWxIOError(e)
            except (WrongLength, BadChecksum), e:
                loginf(e)
            time.sleep(0.001)

    def genStartupRecords(self, since_ts):
        hbuf = None
        last_ts = None
        cnt = 0
        if DEBUG_HISTORY:
            loginf("read Hist since %s: from %d to %d" % (timestamp_to_string(since_ts), self.last_record_rcvd, self.final_history_index ) )
        state = "unset hist"        # debug state for identifying timeouts
        if self.final_history_index <= 0 :
            # this should never happen - it means
            #     either we have never seen a 57 status packet since starting
            #     or else it was bad.
            # probably needs a message and exception
            return
        self.initiateHistory( )

        partpkt = 0         # partially completed packet count

        state = "reading history"
        while True:
            try:
                buf = self.station.read()
                if buf is not None:
                    if partpkt == 64 :
                        # need better indicator of second half history?
                        # There is no indicator - we HAVE to assume packets are consecutive.
                        # I presume console firmware "guarantees" they will be.
                        buf = hbuf + buf
                        hbuf = None
                        partpkt = 128
                    elif partpkt == 0 and buf[0] == 0xd2:
                        pktlength = buf[1]
                        if pktlength != 128:
                            raise WMR300Error("History record unexpected length: assumed 128, found %d" % pktlength )
                        hbuf = buf
                        buf = None
                        partpkt = 64
                        # just read the next half immediately - we don't want possible a6 packets interspersed
                        # of course, "that can't happen"
                        continue

                    if partpkt == 128:
                        partpkt = 0
                        new_record = Station.get_record_index(buf)
                        if new_record != self.last_record_rcvd +1 :
                            loginf("historical record skipped from : %d  to %d" % 
                                    (self.last_record_rcvd, new_record ) )
                        self.last_record_rcvd = new_record 
                        ts = Station._extract_ts(buf[4:9])
                        if ts is not None and ts > since_ts:
                            keep = True if last_ts is not None else False
                            pkt = Station.decode(buf)
                            packet = self.convert_historical(pkt, ts, last_ts)
                            last_ts = ts
                            if keep:
                                if DEBUG_HISTORY:
                                    if ( pkt['indexnum'] != self.last_record_rcvd ):
                                        loginf("historical record saved: %d, %d" % 
                                                (pkt['indexnum'], self.last_record_rcvd ))
                                cnt += 1
                                yield packet
                    elif buf[0] == 0x57:
                        idx = self.set_final_history_index(buf)
                        msg = "count=%s kept last_index rcvd=%s final_index=%s; state = %s" % (
                            cnt, self.last_record_rcvd, idx, state)
                        if state == "wait57":
                            loginf("catchup completed: %s" % msg)
                            break
                        else:
                            loginf("catchup in progress: %s" % msg)

                    elif buf[0] in [0xd3, 0xd4, 0xd5, 0xd6, 0xdb, 0xdc]:
                        # don't send ack for most data packets - the PC s/w does but they get
                        # ignored anyway! so we avoid problems trying to write ack when
                        # device is trying to write data to us. 
                        # I REALLY don't understand how this happens since we don't seem to have trouble
                        # sending the a6 packets alone.
                        """ ignore this debug - too much data
                        if DEBUG_HISTORY:
                            loginf( "catchup ignoring pkt 0x%2x at %d" % (buf[0], self.last_record_rcvd ) )
                            """
                        # cmd = [0x41, 0x43, 0x4b, buf[0], buf[7]  ]
                        # n_written=0
                        # n_written = self.station.write_noTO(cmd, "rereadHist timeout on ACK write")

                if self.last_record_rcvd >= (self.final_history_index -1) :
                    if state == "reading history":
                        if DEBUG_HISTORY:
                            msg = "count=%s kept, last_received=%s final=%s" % (
                                cnt, self.last_record_rcvd, self.final_history_index )
                            loginf("catchup nearly complete: %s; state=%s" % (msg, state) )
                        state = "finishing"

                if (state == "finishing") or (time.time() - self.last_a6 > self.heartbeat):
                    if DEBUG_HISTORY:
                        loginf("request station status at index: %s; state: %s" %
                                   (self.last_record_rcvd, state ) )
                    cmd = [0xa6, 0x91, 0xca, 0x45, 0x52 ]
                    if state == "finishing" :
                        # it is possible that another history packet has been created between the
                        # most recent 0x57 status and now. So, we have to stay in the loop
                        # to read the possible next packet.
                        # evidence suggests that such history packet will arrive before the
                        # 0x57 reply to this request, so presumably it was already in some output queue.
                        state = "wait57"
                    self.last_a6 = time.time()
                    self.station.write_noTO(cmd, "genHist heartbeat" )

            except usb.core.USBError as e:
                errmsg = repr(e)
                if e.backend_error_code == self.station.backend_timeout_code :
                    logexception( "genStartupRecords (from "+state+")", e)
                elif not ('No error' in errmsg):
                    logexception( "USB failure in genStartupRecord (from "+state+")", e)
                    raise weewx.WeeWxIOError(e)
                else:
                    logexception( "genStartupRecords no error?", e)
            except (WrongLength, BadChecksum), e:
                loginf(e)
            time.sleep(0.001)        

        self.finaliseHistory()

    def reread_history_records(self, since_ts):
        """ This rereads a few history records in the hope of triggering the console to delete them.
        Let's assume we are not needing to keep any, nor
        do we need to store any other loop records that might be interposed.
        """
        hbuf = None
        last_ts = None
        if DEBUG_HISTORY:
            loginf("Reread Hist since %s: from %d to %d" % (timestamp_to_string(since_ts), self.last_record_rcvd, self.final_history_index ) )
        cnt = 0
        state = "unset hist"        # debug state for identifying timeouts
        self.initiateHistory( keep_hist=False )

        partpkt = 0         # partially completed packet count

        state = "reading history"
        """
        A lot of this code is duplicated from genStartupRecords() with bits removed.
        I decided to split them  rather than run a dual-purpose function because 
        genStartupRecords() is a generator and this is not.
        """
        while True:
            try:
                buf = self.station.read()
                if buf is not None:
                    if partpkt == 64 :
                        buf = hbuf + buf
                        hbuf = None
                        partpkt = 128
                    elif partpkt == 0 and buf[0] == 0xd2:
                        pktlength = buf[1]
                        if pktlength != 128:
                            raise WMR300Error("History record unexpected length: assumed 128, found %d" % pktlength )
                        hbuf = buf
                        buf = None
                        partpkt = 64
                        continue

                    if partpkt == 128:
                        partpkt = 0
                        new_record = Station.get_record_index(buf)
                        self.last_record_rcvd = new_record 
                        ts = Station._extract_ts(buf[4:9])
                        if ts is not None and ts > since_ts:
                            keep = False        # just ignore it...
                            pkt = Station.decode(buf)
                            packet = self.convert_historical(pkt, ts, last_ts)
                            last_ts = ts
                            if keep:
                                if DEBUG_HISTORY:
                                    if ( pkt['indexnum'] != self.last_record_rcvd ):
                                        loginf("historical record saved: %d, %d" % 
                                                (pkt['indexnum'], self.last_record_rcvd ))
                                cnt += 1

                    elif buf[0] == 0x57:
                        idx = self.set_final_history_index(buf)
                        msg = "kept count=%s; last_index rcvd=%s; final_index=%s; state = %s" % (
                            cnt, self.last_record_rcvd, idx, state)
                        if state == "wait57":
                            loginf("History reread completed: %s" % msg)
                            break
                        else:
                            loginf("History reread in progress: %s" % msg)

                    elif buf[0] in [0xd3, 0xd4, 0xd5, 0xd6, 0xdb, 0xdc]:
                        # don't send ack for most data packets 
                        if DEBUG_HISTORY:
                            loginf( "History reread ignoring pkt 0x%2x at %d" % (buf[0], self.last_record_rcvd ) )

                if self.last_record_rcvd >= (self.final_history_index -1) :
                    if state == "reading history":
                        if DEBUG_HISTORY:
                            msg = "count=%s kept, last_received=%s final=%s" % (
                                cnt, self.last_record_rcvd, self.final_history_index )
                            loginf("History reread nearly complete: %s; state=%s" % (msg, state) )
                        state = "finishing"

                if (state == "finishing") or (time.time() - self.last_a6 > self.heartbeat):
                    if DEBUG_HISTORY:
                        loginf("request station status at index: %s; state: %s" %
                               (self.last_record_rcvd, state ) )
                    cmd = [0xa6, 0x91, 0xca, 0x45, 0x52 ]
                    if state == "finishing" :
                        state = "wait57"
                    self.last_a6 = time.time()
                    self.station.write_noTO(cmd, "reread_hist heartbeat" )

            except usb.core.USBError as e:
                errmsg = repr(e)
                if e.backend_error_code == self.station.backend_timeout_code :
                    logexception( "reread_history (from "+state+")", e)
                elif not ('No error' in errmsg):
                    logexception( "USB failure in reread_history (from "+state+")", e)
                    raise weewx.WeeWxIOError(e)
                else:
                    logexception( "reread_history no error?", e)
            except (WrongLength, BadChecksum), e:
                loginf(e)
            time.sleep(0.001)        

        self.finaliseHistory()

    def convert(self, pkt, ts):
        # if debugging packets, log everything we got
        if DEBUG_PACKET:
            logdbg("raw packet: %s" % pkt)
        # timestamp and unit system are the same no matter what
        p = {'dateTime': ts, 'usUnits': weewx.METRICWX}
        # map hardware names to the requested database schema names
        for label in self.sensor_map:
            if self.sensor_map[label] in pkt:
                p[label] = pkt[self.sensor_map[label]]
        # single variable to track last_rain assumes that any historical reads
        # will happen before any loop reads, and no historical reads will
        # happen after any loop reads.  otherwise double-counting of rain
        # events could happen.
        if 'rain_total' in pkt:
            p['rain'] = self.calculate_rain(pkt['rain_total'], self.last_rain)
            if DEBUG_RAIN and pkt['rain_total'] != self.last_rain:
                loginf("rain=%s rain_total=%s last_rain=%s" %
                       (p['rain'], pkt['rain_total'], self.last_rain))
            self.last_rain = pkt['rain_total']
            if pkt['rain_total'] == Station.MAX_RAIN_MM:
                loginf("rain counter maximum reached, counter reset required")
        if DEBUG_PACKET:
            loginf("converted packet: %s" % p)
        return p

    def convert_historical(self, pkt, ts, last_ts):
        p = self.convert(pkt, ts)
        if last_ts is not None:
            # the interval is in units of minutes in the DB
            p['interval'] = (ts - last_ts) / 60
        return p

    def convert_loop(self, pkt):
        p = self.convert(pkt, int(time.time() + 0.5))
        p['rxCheckPercent'] = self.history_pct;     # fake value as easiest way to return it.
        if 'pressure' in p:
            # cache any pressure-related values
            for x in ['pressure', 'barometer']:
                self.pressure_cache[x] = p[x]
        else:
            # apply any cached pressure-related values
            p.update(self.pressure_cache)
        return p

    @staticmethod
    def calculate_rain(newtotal, oldtotal):
        """Calculate the rain difference given two cumulative measurements."""
        if newtotal is not None and oldtotal is not None:
            if newtotal >= oldtotal:
                delta = newtotal - oldtotal
            else:
                loginf("rain counter decrement detected: new=%s old=%s" %
                       (newtotal, oldtotal))
                delta = None
        else:
            loginf("possible missed rain event: new=%s old=%s" %
                   (newtotal, oldtotal))
            delta = None
        return delta


class WMR300Error(weewx.WeeWxIOError):
    """map station errors to weewx io errors"""

class WrongLength(WMR300Error):
    """bad packet length"""

class BadChecksum(WMR300Error):
    """bogus checksum"""

class InitiationError(WMR300Error):
    """something went wrong in the initiation process"""


class Station(object):
    # these identify the weather station on the USB
    VENDOR_ID = 0x0FDE
    PRODUCT_ID = 0xCA08
    MESSAGE_LENGTH = 64
    EP_IN = 0x81
    EP_OUT = 0x01

    # default value: clear console history buffer when it reaches this percentage full...
    HIST_CLEAR_PERCENT = 5
    # only needed for diagnostics
    HISTORY_LOG_INTERVAL = 1        # report every time history size changes by more than this percentage

    HISTORY_START_REC = 0x20        # index to first history record
    HISTORY_MAX_REC = 0x7fe0        # index to history record when full
    HISTORY_N_RECORDS = 32704       # maximum number of records returned (equals MAX_REC - START_REC)
    MAX_RAIN_MM = 10160 # maximum value of rain counter, in mm

    BACKEND_LIBUSB0 = 0
    BACKEND_LIBUSB1 = 1
    BACKEND_OPENUSB = 2
    BACKEND_OTHER = 3


    def __init__(self, vend_id=VENDOR_ID, prod_id=PRODUCT_ID):
        self.vendor_id = vend_id
        self.product_id = prod_id
        #self.handle = None
        self.timeout = 500
        self.interface = 0          # device has only the one interface
        self.dev = None
        self.recv_counts = dict()
        self.send_counts = dict()
        self.backend = Station.BACKEND_OTHER
        self.backend_name = "do not know"
        self.backend_timeout_code = -1111


    def __enter__(self):
        self.open()
        return self

    def __exit__(self, _, value, traceback):  # @UnusedVariable
        self.close()

    def open(self):
        if DEBUG_BACKEND == 0:
            dev = usb.core.find( idVendor=self.vendor_id, idProduct=self.product_id)
        else:
            # adjust this for test purposes...
            if DEBUG_BACKEND == 1:
                import usb.backend.libusb0 as libusb
            elif DEBUG_BACKEND == 2:
                import usb.backend.libusb1 as libusb
            elif DEBUG_BACKEND == 3:
                import usb.backend.openusb as libusb
            else:
                raise WMR300Error("invalid backend option" )

            dev = usb.core.find( backend=libusb.get_backend(), idVendor=self.vendor_id, idProduct=self.product_id)

        if not dev:
            raise WMR300Error("Unable to find station on USB: "
                              "cannot find device with "
                              "VendorID=0x%04x ProductID=0x%04x" %
                              (self.vendor_id, self.product_id))

        self.dev = dev
        backend = str( dev.backend )
        if "libusb0" in backend:
            self.backend = Station.BACKEND_LIBUSB0
            self.backend_name = "libusb0"
            self.backend_timeout_code = -110
        elif "libusb1" in backend:
            self.backend = Station.BACKEND_LIBUSB1
            self.backend_name = "libusb1"
            self.backend_timeout_code = -7
        elif "openusb" in backend:
            self.backend = Station.BACKEND_OPENUSB
            self.backend_name = "openusb"
            self.backend_timeout_code = -110 # FIXME - I dont know this
        else:
            self.backend = Station.BACKEND_OTHER
            self.backend_name = "some other"
            self.backend_timeout_code = -110 # FIXME - I dont know this

        loginf( "using PyUSB backend: %s" % self.backend_name )


        # for HID devices on linux, be sure kernel does not claim the interface
        if self.backend == Station.BACKEND_LIBUSB0:
            loginf( "libusb0 - detaching interface %d" % self.interface )
            try:
                dev.detach_kernel_driver( self.interface )
            except (usb.core.USBError):
                pass

        else:
            kernelactive = dev.is_kernel_driver_active( self.interface )
            
            if kernelactive :
                loginf( "Kernel driver is active, detaching interface %d" % self.interface )
                dev.detach_kernel_driver( self.interface )

        dev.set_configuration()

        dev.reset()


    def close(self):
        if self.dev is not None:
            try:
                usb.util.release_interface(self.dev, self.interface)
            except (ValueError, usb.core.USBError), e:
                loginf("Release interface failed: %s" % e)
            self.dev = None

    def reset(self):
        self.dev.reset()

    def read(self, count=True):
        buf = None
        try:
            buf = self.dev.read(
                Station.EP_IN, self.MESSAGE_LENGTH, self.timeout)
            if DEBUG_COMM:
                logdbg("read: %s" % _fmt_bytes(buf))
            if DEBUG_COUNTS and count:
                self.update_count(buf, self.recv_counts)
        except usb.core.USBError as e:
            errmsg = repr(e)
            if e.backend_error_code == self.backend_timeout_code :
                pass
            elif not ('No error' in errmsg):
                raise
            else:
                logexception( "dev.read", e)
        return buf

    def write(self, buf):
        if DEBUG_COMM:
            logdbg("write: %s" % _fmt_bytes(buf))
        # pad with zeros up to the standard message length
        while len(buf) < self.MESSAGE_LENGTH:
            buf.append(0x00)
        sent = self.dev.write(Station.EP_OUT, buf, self.timeout)
        if DEBUG_COUNTS:
            self.update_count(buf, self.send_counts)
        return sent

    def write_noTO( self, buf, message ):
        """ write_noTO - attempt to write a packet, but do not treat timeout as an error.

            A timeout probably means that the console is not interested in listening to us at the moment.
        """
        n_written = 0
        try:
            # their bizarre code seems to occasionally time out on writing ACK
            # - ignore any for the moment...
            n_written = self.write(buf)
        except usb.core.USBError as e:
            if e.backend_error_code == self.backend_timeout_code :
                logexception( message, e )
            else:
                raise

        return n_written    # possibly wrong - we cannot tell.

    def flush_read_buffer( self, state ):
        """ just read whatever the device has ready to send us and discard
        """
        lasttimeout = self.timeout
        self.timeout = 100
        pkts = 0
        try: 
            while( self.read( False ) is not None ) :
                    pkts+=1

        finally:
            self.timeout = lasttimeout 

        if ( pkts > 0 ):
            if DEBUG_HISTORY:
                loginf( "%s: discarded %d packets" % (state, pkts ) )
        return pkts


    # keep track of the message types for debugging purposes
    @staticmethod
    def update_count(buf, count_dict):
        label = 'empty'
        if buf and len(buf) > 0:
            if buf[0] in [0xd3, 0xd4, 0xd5, 0xd6, 0xdb, 0xdc]:
                # message type and channel for data packets
                label = '%02x:%d' % (buf[0], buf[7])
            elif (buf[0] in [0x41] and
                  buf[3] in [0xd3, 0xd4, 0xd5, 0xd6, 0xdb, 0xdc]):
                # message type and channel for data ack packets
                label = '%02x:%02x:%d' % (buf[0], buf[3], buf[4])
            else:
                # otherwise just track the message type
                label = '%02x' % buf[0]
        if label in count_dict:
            count_dict[label] += 1
        else:
            count_dict[label] = 1
        cstr = []
        for k in sorted(count_dict):
            cstr.append('%s: %s' % (k, count_dict[k]))
        logdbg('counts: %s' % ''.join(cstr))

    @staticmethod
    def _verify_length(label, length, buf):
        if buf[1] != length:
            raise WrongLength("%s: wrong length: expected %02x, got %02x" %
                              (label, length, buf[1]))

    @staticmethod
    def _verify_checksum(label, buf, msb_first=True):
        """Calculate and compare checksum"""
        try:
            cs1 = Station._calc_checksum(buf)
            cs2 = Station._extract_checksum(buf, msb_first)
            if cs1 != cs2:
                raise BadChecksum("%s: bad checksum: %04x != %04x" %
                                  (label, cs1, cs2))
        except IndexError, e:
            raise BadChecksum("%s: not enough bytes for checksum: %s" %
                              (label, e))

    @staticmethod
    def _calc_checksum(buf):
        cs = 0
        for x in buf[:-2]:
            cs += x
        return cs

    @staticmethod
    def _extract_checksum(buf, msb_first):
        if msb_first:
            return (buf[-2] << 8) | buf[-1]
        return (buf[-1] << 8) | buf[-2]

    @staticmethod
    def _extract_ts(buf):
        if buf[0] == 0xee and buf[1] == 0xee and buf[2] == 0xee:
            # year, month, and day are 0xee when timestamp is unset
            return None
        try:
            year = int(buf[0]) + 2000
            month = int(buf[1])
            day = int(buf[2])
            hour = int(buf[3])
            minute = int(buf[4])
            return time.mktime((year, month, day, hour, minute, 0, -1, -1, -1))
        except IndexError:
            raise WMR300Error("buffer too short for timestamp")
        except (OverflowError, ValueError), e:
            raise WMR300Error(
                "cannot create timestamp from y:%s m:%s d:%s H:%s M:%s: %s" %
                (buf[0], buf[1], buf[2], buf[3], buf[4], e))

    @staticmethod
    def _extract_signed(hi, lo, m):
        if hi == 0x7f:
            return None
        s = 0
        if hi & 0xf0 == 0xf0:
            s = 0x10000
        return ((hi << 8) + lo - s) * m

    @staticmethod
    def _extract_value(buf, m):
        if buf[0] == 0x7f:
            return None
        if len(buf) == 2:
            return ((buf[0] << 8) + buf[1]) * m
        return buf[0] * m

    @staticmethod
    def get_start_index(n):
        # return the starting history index to read
        # the HISTORY_MAX_REC value is what it returned in packet 0x57 when the
        # buffer is full. You cannot ask for it, only the one before.
        if n < Station.HISTORY_START_REC:
            return Station.HISTORY_START_REC
        if n >= Station.HISTORY_MAX_REC-1:
            return Station.HISTORY_MAX_REC-1        # wraparound never happens
        return n

    @staticmethod
    def get_record_index(buf):
        # extract the index from the history record
        if buf[0] != 0xd2:
            return None
        return (buf[2] << 8) + buf[3]

    @staticmethod
    def decode(buf):
        try:
            pkt = getattr(Station, '_decode_%02x' % buf[0])(buf)
            if DEBUG_DECODE:
                logdbg('decode: %s %s' % (_fmt_bytes(buf), pkt))
            return pkt
        except IndexError, e:
            raise WMR300Error("cannot decode buffer: %s" % e)
        except AttributeError:
            raise WMR300Error("unknown packet type %02x: %s" %
                              (buf[0], _fmt_bytes(buf)))

    @staticmethod
    def _decode_57(buf):
        """57 packet contains station information"""
        pkt = dict()
        pkt['packet_type'] = 0x57
        pkt['station_type'] = ''.join("%s" % chr(x) for x in buf[0:6])
        pkt['station_model'] = ''.join("%s" % chr(x) for x in buf[7:11])
        pkt['magic0'] = buf[12]
        pkt['magic1'] = buf[13]
        pkt['history_cleared'] = (buf[20] == 0x43 )
        pkt['mystery0'] = buf[22]
        pkt['mystery1'] = buf[23]
        pkt['final_idx'] = (buf[17] << 8) + buf[18]
        #if DEBUG_HISTORY:
            #loginf("pkt 57 in status records: %s" % pkt['final_idx'] )
        return pkt

    @staticmethod
    def _decode_41(_):
        """41 43 4b is ACK"""
        pkt = dict()
        pkt['packet_type'] = 0x41
        return pkt

    @staticmethod
    def _decode_d2(buf):
        """D2 packet contains history data"""
        Station._verify_length("D2", 0x80, buf)
        Station._verify_checksum("D2", buf[:0x80], msb_first=False)
        pkt = dict()
        pkt['packet_type'] = 0xd2
        pkt['indexnum'] = Station.get_record_index(buf)
        pkt['ts'] = Station._extract_ts(buf[4:9])
        for i in range(0, 9):
            pkt['temperature_%d' % i] = Station._extract_signed(
                buf[9 + 2 * i], buf[10 + 2 * i], 0.1) # C
            pkt['humidity_%d' % i] = Station._extract_value(
                buf[27 + i:28 + i], 1.0) # %
        for i in range(1, 9):
            pkt['dewpoint_%d' % i] = Station._extract_signed(
                buf[36 + 2 * i], buf[37 + 2 * i], 0.1) # C
            pkt['heatindex_%d' % i] = Station._extract_signed(
                buf[52 + 2 * i], buf[53 + 2 * i], 0.1) # C
        pkt['windchill'] = Station._extract_signed(buf[68], buf[69], 0.1) # C
        pkt['wind_gust'] = Station._extract_value(buf[72:74], 0.1) # m/s
        pkt['wind_avg'] = Station._extract_value(buf[74:76], 0.1) # m/s
        pkt['wind_gust_dir'] = Station._extract_value(buf[76:78], 1.0) # degree
        pkt['wind_dir'] = Station._extract_value(buf[78:80], 1.0) # degree
        pkt['forecast'] = Station._extract_value(buf[80:81], 1.0)
        pkt['rain_hour'] = Station._extract_value(buf[83:85], 0.254) # mm
        pkt['rain_total'] = Station._extract_value(buf[86:88], 0.254) # mm
        pkt['rain_start_dateTime'] = Station._extract_ts(buf[88:93])
        pkt['rain_rate'] = Station._extract_value(buf[93:95], 0.254) # mm/hour
        pkt['barometer'] = Station._extract_value(buf[95:97], 0.1) # mbar
        pkt['pressure_trend'] = Station._extract_value(buf[97:98], 1.0)
        return pkt

    @staticmethod
    def _decode_d3(buf):
        """D3 packet contains temperature/humidity data"""
        Station._verify_length("D3", 0x3d, buf)
        Station._verify_checksum("D3", buf[:0x3d])
        pkt = dict()
        pkt['packet_type'] = 0xd3
        pkt['ts'] = Station._extract_ts(buf[2:7])
        pkt['channel'] = buf[7]
        pkt['temperature_%d' % pkt['channel']] = Station._extract_signed(
            buf[8], buf[9], 0.1) # C
        pkt['humidity_%d' % pkt['channel']] = Station._extract_value(
            buf[10:11], 1.0) # %
        pkt['dewpoint_%d' % pkt['channel']] = Station._extract_signed(
            buf[11], buf[12], 0.1) # C
        pkt['heatindex_%d' % pkt['channel']] = Station._extract_signed(
            buf[13], buf[14], 0.1) # C
        return pkt

    @staticmethod
    def _decode_d4(buf):
        """D4 packet contains wind data"""
        Station._verify_length("D4", 0x36, buf)
        Station._verify_checksum("D4", buf[:0x36])
        pkt = dict()
        pkt['packet_type'] = 0xd4
        pkt['ts'] = Station._extract_ts(buf[2:7])
        pkt['channel'] = buf[7]
        pkt['wind_gust'] = Station._extract_value(buf[8:10], 0.1) # m/s
        pkt['wind_gust_dir'] = Station._extract_value(buf[10:12], 1.0) # degree
        pkt['wind_avg'] = Station._extract_value(buf[12:14], 0.1) # m/s
        pkt['wind_dir'] = Station._extract_value(buf[14:16], 1.0) # degree
        pkt['windchill'] = Station._extract_signed(buf[18], buf[19], 0.1) # C
        return pkt

    @staticmethod
    def _decode_d5(buf):
        """D5 packet contains rain data"""
        Station._verify_length("D5", 0x28, buf)
        Station._verify_checksum("D5", buf[:0x28])
        pkt = dict()
        pkt['packet_type'] = 0xd5
        pkt['ts'] = Station._extract_ts(buf[2:7])
        pkt['channel'] = buf[7]
        pkt['rain_hour'] = Station._extract_value(buf[9:11], 0.254) # mm
        pkt['rain_24_hour'] = Station._extract_value(buf[12:14], 0.254) # mm
        pkt['rain_total'] = Station._extract_value(buf[15:17], 0.254) # mm
        pkt['rain_rate'] = Station._extract_value(buf[17:19], 0.254) # mm/hour
        pkt['rain_start_dateTime'] = Station._extract_ts(buf[19:24])
        return pkt

    @staticmethod
    def _decode_d6(buf):
        """D6 packet contains pressure data"""
        Station._verify_length("D6", 0x2e, buf)
        Station._verify_checksum("D6", buf[:0x2e])
        pkt = dict()
        pkt['packet_type'] = 0xd6
        pkt['ts'] = Station._extract_ts(buf[2:7])
        pkt['channel'] = buf[7]
        pkt['pressure'] = Station._extract_value(buf[8:10], 0.1) # mbar
        pkt['barometer'] = Station._extract_value(buf[10:12], 0.1) # mbar
        pkt['altitude'] = Station._extract_value(buf[12:14], 1.0) # meter
        return pkt

    @staticmethod
    def _decode_dc(buf):
        """DC packet contains temperature/humidity range data"""
        Station._verify_length("DC", 0x3e, buf)
        Station._verify_checksum("DC", buf[:0x3e])
        pkt = dict()
        pkt['packet_type'] = 0xdc
        pkt['ts'] = Station._extract_ts(buf[2:7])
        return pkt

    @staticmethod
    def _decode_db(buf):
        """DB packet is forecast"""
        Station._verify_length("DB", 0x20, buf)
        Station._verify_checksum("DB", buf[:0x20])
        pkt = dict()
        pkt['packet_type'] = 0xdb
        return pkt


class WMR300ConfEditor(weewx.drivers.AbstractConfEditor):
    @property
    def default_stanza(self):
        return """
[WMR300x]
    # This section is for WMR300 weather stations.

    # The station model, e.g., WMR300A
    model = WMR300x

    # The driver to use:
    driver = user.wmr300x

    # the console history buffer will be emptied each
    #    time it gets to this percent full
    # allowed range 5 to 95.
    history_clear_pct = 10
"""

    def modify_config(self, config_dict):
        print """
Setting rainRate, windchill, heatindex calculations to hardware. 
Dewpoint from hardware is truncated to integer so use software"""
        config_dict.setdefault('StdWXCalculate', {})
        config_dict['StdWXCalculate'].setdefault('Calculations', {})
        config_dict['StdWXCalculate']['Calculations']['rainRate'] = 'hardware'
        config_dict['StdWXCalculate']['Calculations']['windchill'] = 'hardware'
        config_dict['StdWXCalculate']['Calculations']['heatindex'] = 'hardware'
        config_dict['StdWXCalculate']['Calculations']['dewpoint'] = 'software'


# define a main entry point for basic testing of the station without weewx
# engine and service overhead.  invoke this as follows from the weewx root dir:
#
# PYTHONPATH=. python user/wmr300x.py

if __name__ == '__main__':
    import optparse

    usage = """%prog [options] [--help]"""

    syslog.openlog('wmr300x', syslog.LOG_PID | syslog.LOG_CONS)
    syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
    parser = optparse.OptionParser(usage=usage)
    parser.add_option('--version', dest='version', action='store_true',
                      help='display driver version')
    (options, args) = parser.parse_args()

    if options.version:
        print "wmr300 driver version %s" % DRIVER_VERSION
        exit(0)

    stn_dict = {
        'hist_clear_pct' : 20,
        'debug_comm': 1,
        'debug_loop': 0,
        'debug_counts': 0,
        'debug_decode': 0,
        'debug_history': 0,
        'debug_rain': 1,
        'debug_backend': 0
        }
    stn = WMR300Driver(**stn_dict)

    logdbg( "Station is %s, prod: %s, by: %s" % (stn.hardware_name, stn.station.dev.product, stn.station.dev.manufacturer) )

    test_history= True
    test_history= not test_history

    if test_history:
        last_week=time.time() - 3600*24*7
        pktnum =  stn.last_record_rcvd 
        lastidx = stn.final_history_index

        for packet in stn.genStartupRecords( last_week ):
            if ( (pktnum %100 == 0) or pktnum > 9700 ):
                print "record %d (%d to go) index:%d" % (pktnum, lastidx-pktnum, stn.last_record_rcvd )
                print packet
            pktnum += 1

    for packet in stn.genLoopPackets():
        print packet

Reply via email to