WiiFit Waage
Übersicht
Dieses Python Skript baut eine Bluetooth Verbindung mit einem Wii Fit Board auf. Ist dies erledigt, schickt es das ermittelte Gewicht (in einer Dauerschleife) übers Netzwerk an FHEM. Forumsthread
Ohne eine Versionsnummerierung zu haben, nenne ich es mal 0.1 => erste öffentliche Version
Voraussetzungen
- Getestet mit Debian Wheezy.
- Bluetooth Stick
- Relais (wenn fhem das Pairing automatisch machen soll)
Konfiguration der Software
Das Programm an sich benötigt eine Config-Datei.
Beispieldatei, könnte Fass_1.json" genannt werden
{ "BT-Adress": "00:25:A0:40:5C:28", "fhem_host_port": "http://127.0.0.1:8083", "fhem_request": "/fhem?cmd=", "fhem_command": "setreading%20Fass_1%20Gewicht" }
Was ist hier einzustellen?
- "BTAdress": Bluetoothadresse des Wii Fit Boards
- "fhem_host_port": Adresse des fhem Servers, ebenso wie der Port. (Muss man also anpassen)
- "fhem_request": (weiß nicht, wie es heißt), sollte aber so passen
- "fhem_command": das, was fhem dann wirklich tun soll; hier: setreading Fass_1 Gewicht
- das Gewicht.py hängt da dann das Gewicht in dran und sendet es an fhem
Aufruf
$ ./Gewicht.py --conf "Fass_1.json"
Benötigte Pakete installieren
sudo apt-get install python-bluetooth bluez python-gobject
Um die Adresse in Erfahrung zu bringen:
$ hcitool dev
Hardware-Modding
Leider kann man kein automatisches Pairing (wie bei einer Freisprechanlage und dem Handy im Auto) einrichten. Also ich kann es jedenfalls nicht. Daher muss bei jedem Skriptaufruf (das Skript läuft dann in einer Dauerschleife) den kleinen roten Pairing Knopf drücken. Der ist intelligenterweise unter der Batterieabdeckung.
Lösung:
Auf die Rückseite der Platine werden bei dem Taster 2 Drähte angelötet. Nun kann man z.B. mit einem Relais einfach kurz den Kontakt schließen lassen, so dass das Pairing am Board eingeleitet wird. Vom Timing her empfiehlt es sich (wenn man es von Hand mal ausprobieren will ohne Löterei), zuerst das Wii Fit Board in den Pairing Modus zu bringen, dann das Skript zu starten.
Programmcode
z,B. mit
$ nano Gewicht.py
Direkt im Wunschverzeichnis ablegen.
#!/usr/bin/env python import collections import time import bluetooth import sys import subprocess import json import urllib import argparse # --------- User Settings --------- WEIGHT_SAMPLES = 500 # --------------------------------- # Wiiboard Parameters CONTINUOUS_REPORTING = "04" # Easier as string with leading zero COMMAND_LIGHT = 11 COMMAND_REPORTING = 12 COMMAND_REQUEST_STATUS = 15 COMMAND_REGISTER = 16 COMMAND_READ_REGISTER = 17 INPUT_STATUS = 20 INPUT_READ_DATA = 21 EXTENSION_8BYTES = 32 BUTTON_DOWN_MASK = 8 TOP_RIGHT = 0 BOTTOM_RIGHT = 1 TOP_LEFT = 2 BOTTOM_LEFT = 3 BLUETOOTH_NAME = "Nintendo RVL-WBC-01" # Read Configuration from .json file ap = argparse.ArgumentParser() ap.add_argument("-c", "--conf", required=True, help="path to the JSON configuration file; eg try: >wiiboard_kneipe.py --conf waage1.json") args = vars(ap.parse_args()) #ConfigFile laden conf = json.load(open(args["conf"])) client = None # create Network command url = (conf["fhem_host_port"]) request = (conf["fhem_request"]) command = (conf["fhem_command"]) #value = "%20" + str(white) fhem = url + request + command +"%20" #print fhem #urllib.urlopen(fhem) class EventProcessor: def __init__(self): self._measured = False self.done = False self._measureCnt = 0 self._events = range(WEIGHT_SAMPLES) def mass(self, event): if (event.totalWeight > 2): self._events[self._measureCnt] = event.totalWeight self._measureCnt += 1 if self._measureCnt == WEIGHT_SAMPLES: self._sum = 0 for x in range(0, WEIGHT_SAMPLES-1): self._sum += event.totalWeight self._weight = self._sum/WEIGHT_SAMPLES self._measureCnt = 0 print str(self._weight) + " kg" fhemComplete = fhem + str(self._weight) #print fhemComplete urllib.urlopen(fhemComplete) if not self._measured: self._measured = True @property def weight(self): if not self._events: return 0 histogram = collections.Counter(round(num, 1) for num in self._events) return histogram.most_common(1)[0][0] class BoardEvent: def __init__(self, topLeft, topRight, bottomLeft, bottomRight, buttonPressed, buttonReleased): self.topLeft = topLeft self.topRight = topRight self.bottomLeft = bottomLeft self.bottomRight = bottomRight self.buttonPressed = buttonPressed self.buttonReleased = buttonReleased #convenience value self.totalWeight = topLeft + topRight + bottomLeft + bottomRight class Wiiboard: def __init__(self, processor): # Sockets and status self.receivesocket = None self.controlsocket = None self.processor = processor self.calibration = [] self.calibrationRequested = False self.LED = False self.address = None self.buttonDown = False for i in xrange(3): self.calibration.append([]) for j in xrange(4): self.calibration[i].append(10000) # high dummy value so events with it don't register self.status = "Disconnected" self.lastEvent = BoardEvent(0, 0, 0, 0, False, False) try: self.receivesocket = bluetooth.BluetoothSocket(bluetooth.L2CAP) self.controlsocket = bluetooth.BluetoothSocket(bluetooth.L2CAP) except ValueError: raise Exception("Error: Bluetooth not found") def isConnected(self): return self.status == "Connected" # Connect to the Wiiboard at bluetooth address <address> def connect(self, address): if address is None: print "Non existant address" return self.receivesocket.connect((address, 0x13)) self.controlsocket.connect((address, 0x11)) if self.receivesocket and self.controlsocket: print "Connected to Wiiboard at address " + address self.status = "Connected" self.address = address self.calibrate() useExt = ["00", COMMAND_REGISTER, "04", "A4", "00", "40", "00"] self.send(useExt) self.setReportingType() print "Wiiboard connected" else: print "Could not connect to Wiiboard at address " + address def receive(self): while self.status == "Connected" and not self.processor.done: data = self.receivesocket.recv(25) intype = int(data.encode("hex")[2:4]) if intype == INPUT_STATUS: # TODO: Status input received. It just tells us battery life really self.setReportingType() elif intype == INPUT_READ_DATA: if self.calibrationRequested: packetLength = (int(str(data[4]).encode("hex"), 16) / 16 + 1) self.parseCalibrationResponse(data[7:(7 + packetLength)]) if packetLength < 16: self.calibrationRequested = False elif intype == EXTENSION_8BYTES: self.processor.mass(self.createBoardEvent(data[2:12])) else: print "ACK to data write received" def disconnect(self): if self.status == "Connected": self.status = "Disconnecting" while self.status == "Disconnecting": self.wait(100) try: self.receivesocket.close() except: pass try: self.controlsocket.close() except: pass print "WiiBoard disconnected" # Try to discover a Wiiboard def discover(self): print "Press the red sync button on the board now" address = None bluetoothdevices = bluetooth.discover_devices(duration=6, lookup_names=True) for bluetoothdevice in bluetoothdevices: if bluetoothdevice[1] == BLUETOOTH_NAME: address = bluetoothdevice[0] print "Found Wiiboard at address " + address if address is None: print "No Wiiboards discovered." return address def createBoardEvent(self, bytes): buttonBytes = bytes[0:2] bytes = bytes[2:12] buttonPressed = False buttonReleased = False state = (int(buttonBytes[0].encode("hex"), 16) << 8) | int(buttonBytes[1].encode("hex"), 16) if state == BUTTON_DOWN_MASK: buttonPressed = True if not self.buttonDown: print "Button pressed" self.buttonDown = True if not buttonPressed: if self.lastEvent.buttonPressed: buttonReleased = True self.buttonDown = False print "Button released" rawTR = (int(bytes[0].encode("hex"), 16) << 8) + int(bytes[1].encode("hex"), 16) rawBR = (int(bytes[2].encode("hex"), 16) << 8) + int(bytes[3].encode("hex"), 16) rawTL = (int(bytes[4].encode("hex"), 16) << 8) + int(bytes[5].encode("hex"), 16) rawBL = (int(bytes[6].encode("hex"), 16) << 8) + int(bytes[7].encode("hex"), 16) topLeft = self.calcMass(rawTL, TOP_LEFT) topRight = self.calcMass(rawTR, TOP_RIGHT) bottomLeft = self.calcMass(rawBL, BOTTOM_LEFT) bottomRight = self.calcMass(rawBR, BOTTOM_RIGHT) boardEvent = BoardEvent(topLeft, topRight, bottomLeft, bottomRight, buttonPressed, buttonReleased) return boardEvent def calcMass(self, raw, pos): val = 0.0 #calibration[0] is calibration values for 0kg #calibration[1] is calibration values for 17kg #calibration[2] is calibration values for 34kg if raw < self.calibration[0][pos]: return val elif raw < self.calibration[1][pos]: val = 17 * ((raw - self.calibration[0][pos]) / float((self.calibration[1][pos] - self.calibration[0][pos]))) elif raw > self.calibration[1][pos]: val = 17 + 17 * ((raw - self.calibration[1][pos]) / float((self.calibration[2][pos] - self.calibration[1][pos]))) return val def getEvent(self): return self.lastEvent def getLED(self): return self.LED def parseCalibrationResponse(self, bytes): index = 0 if len(bytes) == 16: for i in xrange(2): for j in xrange(4): self.calibration[i][j] = (int(bytes[index].encode("hex"), 16) << 8) + int(bytes[index + 1].encode("hex"), 16) index += 2 elif len(bytes) < 16: for i in xrange(4): self.calibration[2][i] = (int(bytes[index].encode("hex"), 16) << 8) + int(bytes[index + 1].encode("hex"), 16) index += 2 # Send <data> to the Wiiboard # <data> should be an array of strings, each string representing a single hex byte def send(self, data): if self.status != "Connected": return data[0] = "52" senddata = "" for byte in data: byte = str(byte) senddata += byte.decode("hex") self.controlsocket.send(senddata) #Turns the power button LED on if light is True, off if False #The board must be connected in order to set the light def setLight(self, light): if light: val = "10" else: val = "00" message = ["00", COMMAND_LIGHT, val] self.send(message) self.LED = light def calibrate(self): message = ["00", COMMAND_READ_REGISTER, "04", "A4", "00", "24", "00", "18"] self.send(message) self.calibrationRequested = True def setReportingType(self): bytearr = ["00", COMMAND_REPORTING, CONTINUOUS_REPORTING, EXTENSION_8BYTES] self.send(bytearr) def wait(self, millis): time.sleep(millis / 1000.0) def main(): processor = EventProcessor() board = Wiiboard(processor) address = (conf["BT-Adress"]) try: # Disconnect already-connected devices. # This is basically Linux black magic just to get the thing to work. subprocess.check_output(["bluez-test-input", "disconnect", address], stderr=subprocess.STDOUT) subprocess.check_output(["bluez-test-input", "disconnect", address], stderr=subprocess.STDOUT) except: pass print "Trying to connect..." board.connect(address) # The wii board must be in sync mode at this time board.wait(200) # Flash the LED so we know we can step on. board.setLight(False) board.wait(500) board.setLight(True) board.receive() if __name__ == "__main__": main()
Mit
$ chmod 755 Gewicht.py
noch die Flags für Ausführbarkeit setzen