diff --git a/README.rst b/README.rst index 648b609..cc08863 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,7 @@ Due to PyBluez limitations, this library will currently only work on Linux and W Dependencies ____________ - - `Python 2.7.x`_ + - `Python 2.7.x or Python 3.3+`_ - PyBluez_ Installation @@ -47,11 +47,16 @@ ____________ First, install PyBluez using the appropriate link or command for your OS: **Windows** - Download and install `PyBluez for Python 2.7`_ + Download and install `PyBluez for Python 2.7`_, or `PyBlues on Windows with Python 3.3+`_ **Ubuntu/Debian**:: - sudo apt-get install python-bluez +For Python 2, there's a system package: + sudo apt install python-bluez + +For Python 3, install dependencies, and use pip: + sudo apt install libbluetooth-dev + sudo pip install pybluez **Fedora**:: @@ -77,6 +82,7 @@ When I try to pip install plugable-btaps I get a compilation error: .. _Plugable PS-BTAPS1 Bluetooth Home Automation Switch: http://plugable.com/products/ps-btaps1/ .. _PyBluez: https://code.google.com/p/pybluez/ -.. _Python 2.7.x: https://www.python.org/ +.. _Python 2.7.x or Python 3.3+: https://www.python.org/ .. _PyBluez for Python 2.7: https://code.google.com/p/pybluez/downloads/detail?name=PyBluez-0.20.win32-py2.7.exe -.. _GitHub Wiki: https://github.com/bernieplug/plugable-btaps/wiki/libbtaps-Documentation-and-Examples \ No newline at end of file +.. _PyBluez on Windows for Python 3.3+: https://stackoverflow.com/questions/48821917/downloading-and-installing-pybluez-for-a-64-bit-windows-10-pc +.. _GitHub Wiki: https://github.com/bernieplug/plugable-btaps/wiki/libbtaps-Documentation-and-Examples diff --git a/btaps/btaps.py b/btaps/btaps.py index 13fcfc9..6c07583 100644 --- a/btaps/btaps.py +++ b/btaps/btaps.py @@ -1,7 +1,23 @@ +from __future__ import absolute_import, division, unicode_literals, print_function, nested_scopes + import sys -import libbtaps +from . import libbtaps import time +try: + # Py2.7 + # noinspection PyShadowingBuiltins,PyUnresolvedReferences + input = raw_input + # noinspection PyShadowingBuiltins,PyUnresolvedReferences + range = xrange + # noinspection PyShadowingBuiltins,PyUnresolvedReferences + str = unicode + # noinspection PyUnresolvedReferences,PyCompatibility + from future_builtins import * +except (ImportError, NameError): + # Py3.3+ + pass + def get_line(): line = raw_input('> ') @@ -14,27 +30,23 @@ def get_line(): def print_dic_sorted(dic): order = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] for key in sorted(dic, key=order.index): - print key, ":", dic[key], "", + print(key, ":", dic[key], "", end='') - print "" + print("") -# Given a list of BTApsTimer objects, print them in a legible format +# Given a list of BTApsTimer objects, print(them in a legible format) def print_timers(timer_list): - print "Timers:" + print("Timers:") for timer in timer_list: - print "\tName: ", timer.name - print "\tID: ", timer.timer_id - print "\tOn: ", - if timer.on == 1: - print "On" - else: - print "Off" - print "\tDays: ", + print("\tName: ", timer.name) + print("\tID: ", timer.timer_id) + print("\tOn: ", "On" if timer.on == 1 else 'Off') + print("\tDays: ", end='') print_dic_sorted(timer.repeat_days) - print "\tStart Time: ", timer.start_time - print "\tEnd Time: ", timer.end_time - print "" + print("\tStart Time: ", timer.start_time) + print("\tEnd Time: ", timer.end_time) + print("") # Turn switch on/off @@ -50,40 +62,40 @@ def print_status(btaps): name = btaps.get_dev_name() status = btaps.get_switch_state() - print "Name: " + name - print "Switch: ", + print("Name: " + name) + print("Switch: ", end='') if status[0] == 1: - print "On" + print("On") else: - print "Off" + print("Off") return status # Simple interactive command line prompts for creating new timer def create_timer(btaps, timer_list): - print "Creating New Timer:" - print "Name: " + print("Creating New Timer:") + print("Name: ") name = get_line() new_timer = libbtaps.BTapsTimer(len(timer_list) + 1, name) - print "Enter Start and End Time in 24-hour format (ex: 23:54)" - print "Start Time: " + print("Enter Start and End Time in 24-hour format (ex: 23:54)") + print("Start Time: ") start = get_line() start = time.strptime(start, "%H:%M") new_timer.set_start_time(start[3], start[4]) - print "End Time: " + print("End Time: ") end = get_line() end = time.strptime(end, "%H:%M") new_timer.set_end_time(end[3], end[4]) - print "Repeat Timer?" + print("Repeat Timer?") repeat = get_line().lower() if repeat == "y": day_list = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] for i, day in enumerate(day_list): - print day, "?" + print(day, "?") repeat = get_line().lower() if repeat == 'y': day_list[i] = True @@ -93,7 +105,7 @@ def create_timer(btaps, timer_list): new_timer.set_repeat_days(day_list[0], day_list[1], day_list[2], day_list[3], day_list[4], day_list[5], day_list[6]) - print "Enable New Timer? Y/N" + print("Enable New Timer? Y/N") enable = get_line().lower() if enable == 'y': new_timer.toggle_on() @@ -103,36 +115,36 @@ def create_timer(btaps, timer_list): # Simple interactive command line prompts for modifying a timer def modify_timer(btaps, timer_list): - print "Enter Timer ID for the timer you wish to modify:" + print("Enter Timer ID for the timer you wish to modify:") id = get_line() mod_timer = timer_list[int(id)-1] - print "Enter values you wish to change, leave blank to keep original value" - print "Name: ", mod_timer.name + print("Enter values you wish to change, leave blank to keep original value") + print("Name: ", mod_timer.name) name = get_line() if name != '': mod_timer.set_name(name) - print "Enter Start and End Time in 24-hour format (ex: 23:54)" - print "Start Time: ", + print("Enter Start and End Time in 24-hour format (ex: 23:54)") + print("Start Time: ", end='') print_dic_sorted(mod_timer.start_time) start = get_line() if start != '': start = time.strptime(start, "%H:%M") mod_timer.set_start_time(start[3], start[4]) - print "End Time: ", mod_timer.end_time + print("End Time: ", mod_timer.end_time) end = get_line() if end != '': end = time.strptime(end, "%H:%M") mod_timer.set_end_time(end[3], end[4]) - print "Repeat Timer?", mod_timer.repeat_days + print("Repeat Timer?", mod_timer.repeat_days) repeat = get_line().lower() if repeat == "y": day_list = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] for i, day in enumerate(day_list): - print day, "?" + print(day, "?") repeat = get_line().lower() if repeat == 'y': day_list[i] = True @@ -142,7 +154,7 @@ def modify_timer(btaps, timer_list): mod_timer.set_repeat_days(day_list[0], day_list[1], day_list[2], day_list[3], day_list[4], day_list[5], day_list[6]) - print "Enable Timer? Y/N" + print("Enable Timer? Y/N") enable = get_line().lower() if (enable == 'y') and (mod_timer.on != 1): mod_timer.toggle_on() @@ -153,10 +165,10 @@ def modify_timer(btaps, timer_list): def main(argv): - print " === Plugable PS-BTAPS CLI v0.8 ===" + print(" === Plugable PS-BTAPS CLI v0.8 ===") if len(argv) != 2: - print "USAGE: python", sys.argv[0], "[Bluetooth address]" - print "EXAMPLE: python", sys.argv[0], "00:00:FF:FF:00:00" + print("USAGE: python", sys.argv[0], "[Bluetooth address]") + print("EXAMPLE: python", sys.argv[0], "00:00:FF:FF:00:00") sys.exit(0) # Establish connection to BTAPS @@ -169,14 +181,14 @@ def main(argv): print_timers(status[1]) while True: - print "Select a function..." - print "1. (T)oggle Switch" - print "2. (C)reate Timer" - print "3. (M)odify Timer" - print "4. (D)elete Timer" - print "5. (S)et Device Name" - print "6. (G)et Switch Status (Name, On/Off, Timers)" - print "7. E(x)it" + print("Select a function...") + print("1. (T)oggle Switch") + print("2. (C)reate Timer") + print("3. (M)odify Timer") + print("4. (D)elete Timer") + print("5. (S)et Device Name") + print("6. (G)et Switch Status (Name, On/Off, Timers)") + print("7. E(x)it") try: function = get_line().lower() @@ -189,11 +201,11 @@ def main(argv): modify_timer(btaps, status[1]) elif function in ['4', 'd']: print_timers(status[1]) - print "Enter Timer ID to delete:" + print("Enter Timer ID to delete:") timer_id = get_line() btaps.delete_timer(timer_id) elif function in ['5', 's']: - print "New Device Name:" + print("New Device Name:") name = get_line() btaps.set_dev_name(name) elif function in ['6', 'g']: @@ -210,4 +222,4 @@ def main(argv): btaps.disconnect() if __name__ == '__main__': - main(sys.argv) \ No newline at end of file + main(sys.argv) diff --git a/btaps/libbtaps.py b/btaps/libbtaps.py index 7fdf6de..0d1c8cd 100644 --- a/btaps/libbtaps.py +++ b/btaps/libbtaps.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, division, unicode_literals, print_function, nested_scopes + from array import array import sys import datetime @@ -8,6 +10,20 @@ else: import bluetooth +try: + # Py2.7 + # noinspection PyShadowingBuiltins, PyUnresolvedReferences,PyCompatibility + input = raw_input + # noinspection PyShadowingBuiltins, PyUnresolvedReferences,PyCompatibility + range = xrange + # noinspection PyShadowingBuiltins,PyUnresolvedReferences + str = unicode + from future_builtins import * + +except (ImportError, NameError): + # Py3.3+ + pass + # Converts badly formatted hex into decimal values. # Example: 0x20 becomes 20 @@ -36,7 +52,7 @@ def __recv_data(self, length=128): data = self.socket.recv(length) return data except IOError as e: - print e + print(e) pass # Find and connect to btaddr provided when instantiating class @@ -69,7 +85,7 @@ def disconnect(self): def set_datetime_now(self): now = datetime.datetime.now() # Set Date/Time Packet: 0xCCAA090901(year)(month)(day)(hour)(minute)(second)(weekday number) - payload = buffer(array('B', [0xcc, 0xaa, 0x09, 0x09, 0x01, + payload = bytes(bytearray([0xcc, 0xaa, 0x09, 0x09, 0x01, int(now.strftime('%y'), 16), int(now.strftime('%m'), 16), int(now.strftime('%d'), 16), int(now.strftime('%H'), 16), int(now.strftime('%M'), 16), int(now.strftime('%S'), 16), @@ -83,7 +99,7 @@ def set_datetime_now(self): # Get name given to the device def get_dev_name(self): # Request name packet: 0xCCAA03180119 - payload = buffer(array('B', [0xCC, 0xAA, 0x03, 0x18, 0x01, 0x19])) + payload = bytes(bytearray([0xCC, 0xAA, 0x03, 0x18, 0x01, 0x19])) self.socket.send(payload) response = self.__recv_data() @@ -95,8 +111,8 @@ def set_dev_name(self, name): return False # Changed name packet: 0xCCAA121701(New name, max 16 chars) - name_array = array('B', name) - packet = array('B', [0xCC, 0xAA, 0x12, 0x17, 0x01]) + name_array = bytearray(name) + packet = bytearray([0xCC, 0xAA, 0x12, 0x17, 0x01]) packet += name_array if len(packet) > 21: @@ -105,7 +121,7 @@ def set_dev_name(self, name): while len(packet) < 21: packet.append(0) - payload = buffer(packet) + payload = bytes(packet) self.socket.send(payload) response = self.__recv_data() return response @@ -115,15 +131,15 @@ def set_dev_name(self, name): def set_switch(self, on): if on: # ON Payload = 0xCCAA03010101 - payload = buffer(array('B', [0xCC, 0xAA, 0x03, 0x01, 0x01, 0x01])) + payload = bytes(bytearray([0xCC, 0xAA, 0x03, 0x01, 0x01, 0x01])) else: # OFF Payload = 0xCCAA03010100 - payload = buffer(array('B', [0xCC, 0xAA, 0x03, 0x01, 0x01, 0x00])) + payload = bytes(bytearray([0xCC, 0xAA, 0x03, 0x01, 0x01, 0x00])) self.socket.send(payload) response = self.__recv_data() - response_bytes = memoryview(response).tolist() + response_bytes = list(bytes(response)) # Failed response packet: 0xCC55020101 if response_bytes == [0xCC, 0x55, 0x02, 0x01, 0x01]: @@ -136,7 +152,7 @@ def set_switch(self, on): return True def __set_timer(self, btapstimer, create=True): - name_array = array('B', btapstimer.name) + name_array = bytearray(btapstimer.name) start_time = btapstimer.get_start_time_bad_hex() end_time = btapstimer.get_end_time_bad_hex() @@ -147,7 +163,7 @@ def __set_timer(self, btapstimer, create=True): else: create_byte = 0x03 - packet = array('B', [0xCC, 0xAA, 0x1A, create_byte, 0x01, btapstimer.timer_id, repeat_day, + packet = bytearray([0xCC, 0xAA, 0x1A, create_byte, 0x01, btapstimer.timer_id, repeat_day, start_time[0], start_time[1], end_time[0], end_time[1], btapstimer.on]) packet += name_array @@ -157,7 +173,7 @@ def __set_timer(self, btapstimer, create=True): while len(packet) < 29: packet.append(0) - payload = buffer(packet) + payload = bytes(packet) self.socket.send(payload) response = self.__recv_data() return response @@ -177,23 +193,23 @@ def modify_timer(self, btapstimer): # timer_list is a list of BTApsTimer objects def get_switch_state(self): # Request device state packet: 0xCCAA03120113 - payload = buffer(array('B', [0xCC, 0xAA, 0x03, 0x12, 0x01, 0x13])) + payload = bytes(bytearray([0xCC, 0xAA, 0x03, 0x12, 0x01, 0x13])) self.socket.send(payload) response = self.__recv_data() - on = memoryview(response).tolist()[7] + on = list(bytearray(response))[7] response_list = [] while True: response = self.__recv_data(23) - if memoryview(response).tolist() == [0x00]: + if list(bytearray(response)) == [0x00]: break else: response_list.append(response) timer_list = [] for timer_response in response_list: - timer_bytes = memoryview(timer_response).tolist() + timer_bytes = list(bytearray(timer_response)) timer = BTapsTimer(timer_bytes[0], timer_response[7:]) timer.set_repeat_days_byte(timer_bytes[1]) timer.set_start_time(bad_hex_to_dec(timer_bytes[2]), bad_hex_to_dec(timer_bytes[3])) @@ -213,7 +229,7 @@ def delete_timer(self, timer): timer_id = timer # Delete timer packet: 0xCCAA04190101(timer id) - payload = buffer(array('B', [0xCC, 0xAA, 0x04, 0x19, 0x01, 0x01, timer_id])) + payload = bytes(bytearray([0xCC, 0xAA, 0x04, 0x19, 0x01, 0x01, timer_id])) self.socket.send(payload) response = self.__recv_data() diff --git a/setup.py b/setup.py index e9d3d18..b4a8ff8 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='plugable-btaps', - version='0.8.3', + version='0.8.4', description='Open Source Library for Controlling the Plugable PS-BTAPS1 Bluetooth AC Outlet Switch', long_description=long_description, @@ -26,8 +26,9 @@ 'Intended Audience :: Developers', 'Topic :: Home Automation', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 2 :: Only', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', 'Environment :: Console', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', @@ -43,4 +44,4 @@ 'btaps=btaps:main', ], }, -) \ No newline at end of file +)