diff --git a/BytePack.py b/BytePack.py deleted file mode 100644 index 7db14a8..0000000 --- a/BytePack.py +++ /dev/null @@ -1,60 +0,0 @@ -import struct - -class UnderflowException(Exception): - pass - -class BytePack: - """ - Helper class to pack and unpack integers and floats from a buffer - """ - def __init__(self,bytebuf=[]): - self.i = 0 - self.bytes = bytebuf[:] - def putByte(self,v): - self.bytes.append(v) - def put(self,v,b=1): - if type(v) == float: - v = struct.unpack("BBBB",struct.pack("f",v)) - for e in v: - self.putByte(e) - elif type(v) == int: - while b: - self.putByte(v&0xFF) - v >>= 8 - b -= 1 - else: - # Is it iterable? Try it, assuming it's a list of bytes - for byte in v: - self.putByte(byte) - def get(self,b=1,t=int,signed=False): - if t == int: - if b > self.getBytesRemaining(): - raise UnderflowException() - r = 0 - s = 0 - top_b = 0 - while b: - top_b = self.bytes[self.i] - r += top_b << s - s += 8 - self.i += 1 - b -= 1 - # Sign extend - if signed and top_b & 0x80: - r -= 1 << s - return r - elif t==float: - if 4 > self.getBytesRemaining(): - raise UnderflowException() - r = struct.unpack("f",struct.pack("BBBB",*self.bytes[self.i:self.i+4])) - self.i += 4 - return r[0] - def getBytes(self, max_bytes=0): - if max_bytes == 0: - rval = self.bytes[self.i:] - else: - rval = self.bytes[self.i:self.i+max_bytes] - self.i += len(rval) - return rval - def getBytesRemaining(self): - return len(self.bytes) - self.i \ No newline at end of file diff --git a/CArrayWriter.py b/CArrayWriter.py deleted file mode 100644 index 42afb3c..0000000 --- a/CArrayWriter.py +++ /dev/null @@ -1,23 +0,0 @@ -def writeHeader(file,array_name,payload): - decl_line = "const unsigned char %s[%d]"%( array_name,len(payload)) - - #Write header file - file.write('#define %s_LEN %d\n'%(array_name.upper(),len(payload))) - file.write('extern ' + decl_line + ';\n') - -def writeAsCArray(file, array_name, payload): - decl_line = "const unsigned char %s[%d]"%( array_name,len(payload)) - # Open main output file - # Write the opening lines - file.write(decl_line + '={\n') - - c_on_row = 0 - - for c in payload: - file.write("0x%02X,\t"%ord(c)) - c_on_row+=1 - if(c_on_row == 8): - c_on_row = 0 - file.write("\n") - - file.write("\n};\n") diff --git a/Example.py b/Example.py index c837c4c..4679dd8 100644 --- a/Example.py +++ b/Example.py @@ -1,4 +1,5 @@ -from Mooshimeter import * +from mooshimeter.meter import Mooshimeter +from mooshimeter import bg_wrapper import threading import time @@ -14,7 +15,7 @@ def run(self): """ Example.py -This script is meant to demonstrate use of the Mooshimeter and BGWrapper classes. +This script is meant to demonstrate use of the Mooshimeter and bg_wrapper classes. The script does the following: - Scan for BLE devices - Filter for Mooshimeters @@ -51,7 +52,7 @@ def writeCh2(self, meter, val): if __name__=="__main__": # Set up the lower level to talk to a BLED112 in port COM4 # REPLACE THIS WITH THE BLED112 PORT ON YOUR SYSTEM - BGWrapper.initialize("COM4") + bg_wrapper.initialize("COM4") inputthread = InputThread() inputthread.start() cmd_queue = [] @@ -59,7 +60,7 @@ def addToQueue(s): cmd_queue.append(s) inputthread.cb = addToQueue # Scan for 3 seconds - scan_results = BGWrapper.scan(5) + scan_results = bg_wrapper.scan(5) # Filter for devices advertising the Mooshimeter service results_wrapped = filter(lambda(p):Mooshimeter.mUUID.METER_SERVICE in p.ad_services, scan_results) if len(results_wrapped) == 0: @@ -75,7 +76,7 @@ def addToQueue(s): m.loadTree() # Wait for us to load the command tree while m.tree.getNodeAtLongname('SAMPLING:TRIGGER')==None: - BGWrapper.idle() + bg_wrapper.idle() # Unlock the meter by writing the correct CRC32 value # The CRC32 node's value is written when the tree is received m.sendCommand('admin:crc32 '+str(m.tree.getNodeAtLongname('admin:crc32').value)) @@ -103,7 +104,7 @@ def addToQueue(s): while True: # This call checks the serial port and processes new data - BGWrapper.idle() + bg_wrapper.idle() if time.time()-last_heartbeat_time > 10: last_heartbeat_time = time.time() for m in meters: diff --git a/License.txt b/LICENSE similarity index 100% rename from License.txt rename to LICENSE diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..689e50f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include README.md \ No newline at end of file diff --git a/README.md b/README.md index 917bddd..1f40468 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ # Mooshimeter-PythonAPI A python API relying on the BLED112 to talk to the Mooshimeter +[![Build status](https://ci.appveyor.com/api/projects/status/5ajjxonexbaqsu6c/branch/master?svg=true)](https://ci.appveyor.com/project/spyoungtech/mooshimeter-pythonapi/branch/master) + + +# Install + +``` +pip install mooshimeter +``` + +**NOTE:** This package is maintained unofficially. +It has no official connection to the [official Mooshim repository](https://github.com/mooshim/Mooshimeter-PythonAPI). + + Example.py: This script is meant to demonstrate use of the Mooshimeter and BGWrapper classes. The script does the following: diff --git a/UUID.py b/UUID.py deleted file mode 100644 index c75ef2c..0000000 --- a/UUID.py +++ /dev/null @@ -1,36 +0,0 @@ -# Utility class -class UUID: - def __init__(self, initializer): - if type(initializer) == type(""): - #String input - self.bytes = self.__stringToBytes(initializer) - elif type(initializer) == type(1): - # Integer initialized, assume a 2 byte UUID - self.bytes = ((initializer>>8)&0xFF, (initializer>>0)&0xFF) - else: - #Byte array input - self.bytes = tuple(initializer) - def __stringToBytes(self, arg): - arg = arg.upper() - arg = arg.replace("-","") - l = [int(arg[i:i+2],16) for i in range(0,len(arg),2)] - return tuple(l) - def __bytesToString(self, bytes): - l = ["%02X"%bytes[i] for i in range(len(bytes))] - if len(bytes) == 16: - l = ["%02X"%bytes[i] for i in range(16)] - l.insert( 4,'-') - l.insert( 7,'-') - l.insert(10,'-') - l.insert(13,'-') - return ''.join(l) - def asString(self): - return self.__bytesToString(self.bytes) - def __eq__(self, other): - return self.bytes==other.bytes - def __hash__(self): - return self.asString().__hash__() - def __str__(self): - return self.asString() - def __repr__(self): - return self.asString() \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..6024409 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,31 @@ +install: + - ps: | + py -2.7 -m virtualenv venv + .\venv\Scripts\activate.ps1 + python -m pip install --upgrade pip + python -m pip install --upgrade . + + +build_script: + - ps: | + .\venv\Scripts\activate.ps1 + python setup.py sdist bdist_wheel + +artifacts: + - name: dist + path: dist\* + +test: off + +deploy_script: + - ps: | + if ($env:APPVEYOR_REPO_TAG -eq "true") { + py -2.7 -m virtualenv deploy_venv + .\deploy_venv\Scripts\activate.ps1 + python -m pip install --upgrade pip + pip install --upgrade wheel + pip install --upgrade twine + twine upload dist\* + } else { + echo "Skipping Deploy Because this is not a tagged commit" + } diff --git a/mooshimeter/__init__.py b/mooshimeter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/BGWrapper.py b/mooshimeter/bg_wrapper.py similarity index 98% rename from BGWrapper.py rename to mooshimeter/bg_wrapper.py index 68d8715..46efc3b 100644 --- a/BGWrapper.py +++ b/mooshimeter/bg_wrapper.py @@ -1,7 +1,7 @@ -import bglib +from mooshimeter.vendor import bglib import serial import time -from UUID import * +from mooshimeter.utils import UUID # This module is designed to be used like a singleton class # It wraps the functions of bglib in an easier to use way @@ -126,7 +126,7 @@ def findHandleForUUID(self,uuid): if c.uuid == uuid: rval.append(c.handle) if len(rval) != 1: - raise + raise Exception('Expected rval length to be exactly 1. Was %d length' % len(rval)) return rval[0] def readByHandle(self,char_handle): return read(self.conn_handle,char_handle) diff --git a/ConfigNode.py b/mooshimeter/config_node.py similarity index 98% rename from ConfigNode.py rename to mooshimeter/config_node.py index 2163b8c..2c37582 100644 --- a/ConfigNode.py +++ b/mooshimeter/config_node.py @@ -1,5 +1,5 @@ import zlib -import CArrayWriter +from mooshimeter import utils class NTYPE: PLAIN =0 # May be an informational node, or a choice in a chooser @@ -198,13 +198,13 @@ def writeCHeader(self,f): f.write('} cmd_code_t;\n') packed = self.pack() - CArrayWriter.writeHeader(f,'config_tree_packed',packed) + utils.writeHeader(f, 'config_tree_packed', packed) f.write('extern const unsigned long config_tree_crc32;\n') def writeCDec(self,f): f.write('#pragma once\n') packed = self.pack() - CArrayWriter.writeAsCArray(f,'config_tree_packed',packed) + utils.writeAsCArray(f, 'config_tree_packed', packed) crc32 = zlib.crc32(packed) # adapt for wart in zlib.crc32... signed ints diff --git a/Mooshimeter.py b/mooshimeter/meter.py similarity index 94% rename from Mooshimeter.py rename to mooshimeter/meter.py index 926eb7d..55863ee 100644 --- a/Mooshimeter.py +++ b/mooshimeter/meter.py @@ -1,14 +1,13 @@ # coding=UTF-8 -import BGWrapper -from UUID import * -from ConfigNode import * -from BytePack import * +from mooshimeter import bg_wrapper +from mooshimeter.config_node import NTYPE, ConfigNode, ConfigTree +from mooshimeter.utils import BytePack, UnderflowException, UUID import binascii -class MeterSerOut(BGWrapper.Characteristic): +class MeterSerOut(bg_wrapper.Characteristic): def __init__(self, meter, parent, handle, uuid): """ - :param other: a BGWrapper.Characteristic + :param other: a bg_wrapper.Characteristic :return: """ super(MeterSerOut,self).__init__(parent, handle, uuid) @@ -76,10 +75,10 @@ def unpack(self): self.aggregate += b.bytes[1:] # Attempt to decode a message, if we succeed pop the message off the byte queue self.interpretAggregate() -class MeterSerIn(BGWrapper.Characteristic): +class MeterSerIn(bg_wrapper.Characteristic): def __init__(self, meter, parent, handle, uuid): """ - :param other: a BGWrapper.Characteristic + :param other: a bg_wrapper.Characteristic :return: """ super(MeterSerIn,self).__init__(parent, handle, uuid) @@ -132,7 +131,7 @@ def sendToMeter(self, payload): def __init__(self, peripheral): """ Initialized instance variables - :param peripheral: a BGWrapper.Peripheral instance + :param peripheral: a bg_wrapper.Peripheral instance :return: """ self.p = peripheral @@ -221,7 +220,7 @@ def tmp_cb(): wrap[0]+=1 self.meter_serout.enableNotify(True,tmp_cb) def disconnect(self): - BGWrapper.disconnect(self.p.conn_handle) + bg_wrapper.disconnect(self.p.conn_handle) def getUUIDString(self): return self.p.getUUIDString() def attachCallback(self,node_path,notify_cb): diff --git a/mooshimeter/utils.py b/mooshimeter/utils.py new file mode 100644 index 0000000..e145757 --- /dev/null +++ b/mooshimeter/utils.py @@ -0,0 +1,123 @@ +import struct + +class UnderflowException(Exception): + pass + +class BytePack: + """ + Helper class to pack and unpack integers and floats from a buffer + """ + def __init__(self,bytebuf=[]): + self.i = 0 + self.bytes = bytebuf[:] + def putByte(self,v): + self.bytes.append(v) + def put(self,v,b=1): + if type(v) == float: + v = struct.unpack("BBBB",struct.pack("f",v)) + for e in v: + self.putByte(e) + elif type(v) == int: + while b: + self.putByte(v&0xFF) + v >>= 8 + b -= 1 + else: + # Is it iterable? Try it, assuming it's a list of bytes + for byte in v: + self.putByte(byte) + def get(self,b=1,t=int,signed=False): + if t == int: + if b > self.getBytesRemaining(): + raise UnderflowException() + r = 0 + s = 0 + top_b = 0 + while b: + top_b = self.bytes[self.i] + r += top_b << s + s += 8 + self.i += 1 + b -= 1 + # Sign extend + if signed and top_b & 0x80: + r -= 1 << s + return r + elif t==float: + if 4 > self.getBytesRemaining(): + raise UnderflowException() + r = struct.unpack("f",struct.pack("BBBB",*self.bytes[self.i:self.i+4])) + self.i += 4 + return r[0] + def getBytes(self, max_bytes=0): + if max_bytes == 0: + rval = self.bytes[self.i:] + else: + rval = self.bytes[self.i:self.i+max_bytes] + self.i += len(rval) + return rval + def getBytesRemaining(self): + return len(self.bytes) - self.i + + +def writeHeader(file, array_name, payload): + decl_line = "const unsigned char %s[%d]" % (array_name, len(payload)) + + # Write header file + file.write('#define %s_LEN %d\n' % (array_name.upper(), len(payload))) + file.write('extern ' + decl_line + ';\n') + + +def writeAsCArray(file, array_name, payload): + decl_line = "const unsigned char %s[%d]" % (array_name, len(payload)) + # Open main output file + # Write the opening lines + file.write(decl_line + '={\n') + + c_on_row = 0 + + for c in payload: + file.write("0x%02X,\t" % ord(c)) + c_on_row += 1 + if (c_on_row == 8): + c_on_row = 0 + file.write("\n") + + file.write("\n};\n") + +# Utility class +class UUID: + def __init__(self, initializer): + if type(initializer) == type(""): + #String input + self.bytes = self.__stringToBytes(initializer) + elif type(initializer) == type(1): + # Integer initialized, assume a 2 byte UUID + self.bytes = ((initializer>>8)&0xFF, (initializer>>0)&0xFF) + else: + #Byte array input + self.bytes = tuple(initializer) + def __stringToBytes(self, arg): + arg = arg.upper() + arg = arg.replace("-","") + l = [int(arg[i:i+2],16) for i in range(0,len(arg),2)] + return tuple(l) + def __bytesToString(self, bytes): + l = ["%02X"%bytes[i] for i in range(len(bytes))] + if len(bytes) == 16: + l = ["%02X"%bytes[i] for i in range(16)] + l.insert( 4,'-') + l.insert( 7,'-') + l.insert(10,'-') + l.insert(13,'-') + return ''.join(l) + def asString(self): + return self.__bytesToString(self.bytes) + def __eq__(self, other): + return self.bytes==other.bytes + def __hash__(self): + return self.asString().__hash__() + def __str__(self): + return self.asString() + def __repr__(self): + return self.asString() \ No newline at end of file diff --git a/mooshimeter/vendor/__init__.py b/mooshimeter/vendor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bglib.py b/mooshimeter/vendor/bglib.py similarity index 100% rename from bglib.py rename to mooshimeter/vendor/bglib.py diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d4d1f9b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyserial \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..29b21fb --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +license_file = LICENSE \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..947597c --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup + +setup( + name='mooshimeter', + version='0.1.0', + packages=['mooshimeter', 'mooshimeter.vendor'], + url='https://github.com/spyoungtech/Mooshimeter-PythonAPI', + license='GPLv3', + author='James Whong', + author_email='hello@moosh.im', + maintainer='Spencer Young', + maintainer_email='spencer.young@spyoung.com', + description='UNOFFICIAL! This package is a fork from the original mooshimeter python API', + install_requires=['pyserial'] +)