Projet

Général

Profil

Révision 4308f247

ID4308f247afd2a32a670ead37446ba1fa36f81f50
Parent d3140645
Enfant 50234857

Ajouté par K.Cima il y a environ 4 ans

Add switchbotmeterbt plugin

Voir les différences:

plugins/sensors/switchbotmeterbt
1
#!/usr/bin/env python3
2

  
3
"""
4
=head1 NAME
5

  
6
  switchbotmeterbt - Munin plugin to monitor temperature/humidity with SwitchBot Meter BLE
7

  
8
=head1 CONFIGURATION
9

  
10
  Python 3 and bluepy are necessary.
11

  
12
  Example for Ubuntu:
13
    $ sudo apt install python3-pip
14
    $ sudo pip3 install bluepy
15

  
16
=head1 ENVIRONMENT VARIABLES
17

  
18
  user            : root privilege is necessary for BLE scan
19
  env.macaddr     : Mac Address(es) of SwitchBot Meter(s) separated by white-space (Required)
20
  env.hcidev      : HCI device index. (Optional, default is 0)
21
  env.tempunit    : Temperature unit. (Optional, default is C)
22
  env.scantimeout : Timeout for BLE scan. (Optional, default is 5.0 seconds)
23

  
24
  Example:
25
    [switchbotmeterbt]
26
      user  root
27
      env.macaddr  aa:aa:aa:aa:aa:aa bb:bb:bb:bb:bb:bb cc:cc:cc:cc:cc:cc
28

  
29
=head1 NOTES
30

  
31
  For more details about SwitchBot Meter, see https://www.switch-bot.com/products/switchbot-meter
32

  
33
=head1 AUTHOR
34

  
35
  K.Cima https://github.com/shakemid
36

  
37
=head1 LICENSE
38

  
39
  GPLv2
40
  SPDX-License-Identifier: GPL-2.0-only
41

  
42
=head1 Magic markers
43

  
44
  #%# family=contrib
45
  #%# capabilities=
46

  
47
=cut
48
"""
49

  
50
import sys
51
import os
52
import subprocess
53
from bluepy.btle import Scanner, DefaultDelegate
54

  
55

  
56
class SwitchbotScanDelegate(DefaultDelegate):
57
    def __init__(self, macaddrs):
58
        super().__init__()
59
        self.sensorValues = {}
60
        self.macaddrs = macaddrs
61

  
62
    # Called when advertising data is received from an LE device
63
    def handleDiscovery(self, dev, isNewDev, isNewData):
64
        if dev.addr in self.macaddrs:
65
            for (adtype, desc, value) in dev.getScanData():
66
                if desc == '16b Service Data':
67
                    munin_debug(value)
68
                    self._decodeSensorData(dev, value)
69

  
70
    def _decodeSensorData(self, dev, value):
71
        # Extract lower 6 octets
72
        valueBinary = bytes.fromhex(value[4:16])
73

  
74
        # Refer to Meter BLE open API
75
        # https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API
76
        deviceType = chr(valueBinary[0] & 0b01111111)
77
        battery = valueBinary[2] & 0b01111111
78
        tint = valueBinary[4] & 0b01111111
79
        tdec = valueBinary[3] & 0b00001111
80
        temperature = tint + tdec / 10
81
        isTemperatureAboveFreezing = valueBinary[4] & 0b10000000
82
        if not isTemperatureAboveFreezing:
83
            temperature = -temperature
84
        humidity = valueBinary[5] & 0b01111111
85

  
86
        self.sensorValues[dev.addr] = {
87
            'DeviceType':  deviceType,
88
            'Temperature': temperature,
89
            'Humidity':    humidity,
90
            'Battery':     battery,
91
            'RSSI':        dev.rssi
92
        }
93
        munin_debug(str(self.sensorValues))
94

  
95

  
96
class SwitchbotMeterPlugin():
97
    def __init__(self):
98
        self.params = {}
99
        self.params['pluginname'] = os.path.basename(__file__)
100
        self.params['macaddrs'] = [str.lower(macaddr)
101
                                   for macaddr in os.getenv('macaddr').split()]
102
        self.params['hcidev'] = int(os.getenv('hcidev', 0))
103
        self.params['tempunit'] = os.getenv('tempunit', 'C')
104
        self.params['scantimeout'] = float(os.getenv('scantimeout', 5.0))
105

  
106
        self.graphs = {
107
            'temp': {
108
                'name': 'Temperature',
109
                'attrs': [
110
                    'graph_title SwitchBot Meter Temperature',
111
                    'graph_category sensors',
112
                    'graph_vlabel Temp ' + self.params['tempunit'],
113
                    'graph_scale no',
114
                    'graph_args --base 1000'
115
                ]
116
            },
117
            'humid': {
118
                'name': 'Humidity',
119
                'attrs': [
120
                    'graph_title SwitchBot Meter Humidity',
121
                    'graph_category sensors',
122
                    'graph_vlabel Humid %',
123
                    'graph_scale no',
124
                    'graph_args --base 1000'
125
                ]
126
            },
127
            'batt': {
128
                'name': 'Battery',
129
                'attrs': [
130
                    'graph_title SwitchBot Meter Battery',
131
                    'graph_category sensors',
132
                    'graph_vlabel %',
133
                    'graph_scale no',
134
                    'graph_args --base 1000 --lower-limit 0 --upper-limit 100'
135
                ]
136
            },
137
            'rssi': {
138
                'name': 'RSSI',
139
                'attrs': [
140
                    'graph_title SwitchBot Meter RSSI',
141
                    'graph_category sensors',
142
                    'graph_vlabel dB',
143
                    'graph_scale no',
144
                    'graph_args --base 1000'
145
                ]
146
            }
147
        }
148

  
149
    def config(self):
150
        # print config
151
        for k in self.graphs.keys():
152
            print('multigraph ' + self.params['pluginname'] + '_' + k)
153
            for line in self.graphs[k]['attrs']:
154
                print(line)
155
            for macaddr in self.params['macaddrs']:
156
                field = macaddr.replace(':', '')
157
                print(field + '.label ' + macaddr)
158

  
159
    def fetch(self):
160
        # scan to fetch data
161
        scanner = Scanner(self.params['hcidev']).withDelegate(SwitchbotScanDelegate(self.params['macaddrs']))
162

  
163
        try:
164
            # sometimes it might fail
165
            scanner.scan(self.params['scantimeout'])
166
            for macaddr in self.params['macaddrs']:
167
                check = scanner.delegate.sensorValues[macaddr]
168
        except KeyError as e:
169
            munin_error('retry scan for exception: ' + str(type(e)))
170
            scanner.scan(self.params['scantimeout'])
171
        except Exception as e:
172
            munin_error('reset hci and retry scan for exception: ' + str(type(e)))
173
            subprocess.call(f'hciconfig hci{self.params["hcidev"]} down && hciconfig hci{self.params["hcidev"]} up', shell=True)
174
            scanner.scan(self.params['scantimeout'])
175

  
176
        # print value
177
        for k in self.graphs.keys():
178
            print('multigraph ' + self.params['pluginname'] + '_' + k)
179
            for macaddr in self.params['macaddrs']:
180
                field = macaddr.replace(':', '')
181
                try:
182
                    print(field + '.value ' +
183
                          str(scanner.delegate.sensorValues[macaddr][self.graphs[k]['name']]))
184
                except KeyError:
185
                    pass
186

  
187

  
188
def munin_error(message):
189
    print(message, file=sys.stderr)
190

  
191

  
192
def munin_debug(message):
193
    if os.getenv('MUNIN_DEBUG') == '1':
194
        print('# ' + message)
195

  
196

  
197
def main():
198
    plugin = SwitchbotMeterPlugin()
199

  
200
    if len(sys.argv) > 1 and sys.argv[1] == 'config':
201
        plugin.config()
202
        if os.getenv('MUNIN_CAP_DIRTYCONFIG') == '1':
203
            plugin.fetch()
204
    else:
205
        plugin.fetch()
206

  
207

  
208
if __name__ == '__main__':
209
    main()

Formats disponibles : Unified diff