diff --git a/NEWS b/NEWS index aa69d7bb6..d3a03f704 100644 --- a/NEWS +++ b/NEWS @@ -6,10 +6,11 @@ Copyright (C) 2000-2016 Stephane Fillod, and others Please send Hamlib bug reports to hamlib-developer@lists.sourceforge.net Version 3.2 - 2017-06-30 + 2017-08-31 * New models, IC-7850/IC-7851 in IC-785x. Mike, W9MDB * Fix ft991_get_mode, Mike, W9MDB * New model, FT-891. Mike, W9MDB + * Build instructions and test script for Python3 Version 3.1 2016-12-31 diff --git a/bindings/Makefile.am b/bindings/Makefile.am index 59a253b30..e2067bf13 100644 --- a/bindings/Makefile.am +++ b/bindings/Makefile.am @@ -14,9 +14,10 @@ SWGDEP=$(top_srcdir)/include/hamlib/rig.h $(top_srcdir)/include/hamlib/riglist.h $(SWGFILES) EXTRA_DIST = $(SWGFILES) \ - Makefile.PL perltest.pl tcltest.tcl pytest.py luatest.lua + Makefile.PL perltest.pl tcltest.tcl pytest.py py3test.py luatest.lua \ + README.python -noinst_SCRIPTS = perltest.pl tcltest.tcl pytest.py hamlibvb.bas luatest.lua +noinst_SCRIPTS = perltest.pl tcltest.tcl pytest.py py3test.py hamlibvb.bas luatest.lua BUILT_SOURCES= MOSTLYCLEANFILES= diff --git a/bindings/README.python b/bindings/README.python new file mode 100644 index 000000000..7621e3185 --- /dev/null +++ b/bindings/README.python @@ -0,0 +1,169 @@ +For several years the Python world has been in a state of transition from +version 2.x to 3.x. Much code still exists for Python2 in the form of +applications, modules, and packages and will likely be supported for some +time to come. Python3 is gaining acceptance, especially with new +development and many modules and packages are now available for Python3. + +The Python developers have taken care to ensure that Python2 and Python3 +and their respective modules and packages can be installed concurrently. +The steps below take advantage of this feature to install Hamlib.py modules +for both Python versions. Complete installation of each Python version +including their respective development files are required for successful +generation of the bindings by SWIG. + +At this time the GNU Autotools do not offer a clean method of building SWIG +bindings for multiple versions of Python. Some hacks can be found on the +Web but until a clean native solution is offered the steps in this document +should prove adequate. With the wealth of Python2 programs, modules, and +packages still in use, it isn't likely to disappear from distributions +anytime soon. Python3 is becoming more popular for new development with a +complete standard library and many modules and packages being ported over +and available. It's time that we offer a means to generate bindings for +either version. This document will provide the steps for doing so. + +NOTE: Developers and testers building from a Git clone/checkout will need +to bootstrap the build system by running the autogen.sh script. Source +releases and source daily snapshots already have this step completed. + +NOTE: The commands assume an out of tree build in a sibling directory to +the main source directory. Adjust your paths accordingly. Adjust your +--prefix option as needed (installation to the user's home directory is +shown to avoid root privileges). + +Asssuming that Python2 is the default installed Python interpreter, build +its bindings first: + + ../hamlib/configure --with-python-binding --prefix=$HOME/local + make + make install + +At this point the Hamlib binaries, development files, and Python2 bindings +will be installed to their customary locations under $HOME/local. + +Examination of the configure script's output will confirm that Python2 is +found and used as this extract shows: + +checking whether to build python binding and demo... yes +checking for a Python interpreter with version >= 2.1... python +checking for python... /usr/bin/python +checking for python version... 2.7 +checking for python platform... linux2 +checking for python script directory... ${prefix}/lib64/python2.7/site-packages +checking for python extension module directory... ${exec_prefix}/lib64/python2.7/site-packages +checking for python2.7... (cached) /usr/bin/python +checking for a version of Python >= '2.1.0'... yes +checking for the distutils Python package... yes +checking for Python include path... -I/usr/include/python2.7 +checking for Python library path... -L/usr/lib64 -lpython2.7 +checking for Python site-packages path... /usr/lib64/python2.7/site-packages +checking python extra libraries... -lpthread -ldl -lutil +checking python extra linking flags... -Xlinker -export-dynamic +checking consistency of all components of python development environment... yes + +At this point the file pytest.py in the source bindings directory may be +run as a test. If an error is given that the Hamlib module cannot be +found, see below. + +The next step is to configure and build for Python3: + + ../hamlib/configure --with-python-binding PYTHON=`which python3` --prefix=$HOME/local + make + +Here the PYTHON environment variable is set to the first python3 executable +found in the path (python3 may be a symbolic link, which is fine). This +may be unwanted behavior if multiple versions of Python are installed. + +Python3 was found as shown in this configure output extract: + +checking whether to build python binding and demo... yes +checking whether /usr/bin/python3 version is >= 2.1... yes +checking for /usr/bin/python3 version... 3.6 +checking for /usr/bin/python3 platform... linux +checking for /usr/bin/python3 script directory... ${prefix}/lib64/python3.6/site-packages +checking for /usr/bin/python3 extension module directory... ${exec_prefix}/lib64/python3.6/site-packages +checking for python3.6... /usr/bin/python3 +checking for a version of Python >= '2.1.0'... yes +checking for the distutils Python package... yes +checking for Python include path... -I/usr/include/python3.6m +checking for Python library path... -L/usr/lib64 -lpython3.6m +checking for Python site-packages path... /usr/lib64/python3.6/site-packages +checking python extra libraries... -lpthread -ldl -lutil +checking python extra linking flags... -Xlinker -export-dynamic +checking consistency of all components of python development environment... yes + +Since all the Makefiles were regenerated by the second run of configure, +hamlib will be compiled again. + +Next install the Python3 bindings: + + cd bindings + make install + +In this case, only the generated files in 'bindings' will be installed +which will be the new Python3 bindings. + +Test that the Hamlib Python3 bindings are found by running the +bindings/py3test.py script. + +At this point working bindings are installed and have been tested. + +Running 'make uninstall' will only remove the version of the bindings that +was last configured. To uninstall the other version the respective options +will need to be passed to 'configure' and 'make uninstall' run again. + +What to do if Python complains the module cannot be found. + +There are various ways that a specific path can be provided to Python. +Perhaps the easiest is to provide an environment variable to your script. + +Since Python will not have $HOME/local/... in its search path, here is an +example: + + $ py3test.py + Traceback (most recent call last): + File "./py3test.py", line 9, in + import Hamlib + ModuleNotFoundError: No module named 'Hamlib' + +This isn't good! Let's set an environment variable for the running script: + + PYTHONPATH=$HOME/local/lib64/python3.6/site-packages:$PYTHONPATH ./py3test.py + +Success! + +Like the standard PATH environment variable PYTHONPATH can contain multiple +paths separated by colons. In this case, if PYTHONPATH was already set the +new path is prepended to its prior setting. + +While setting the environment variable is good for a one-off run, a more +permanent solution can be acheived by placing a file that ends in .pth in a +directory that Python will process when starting. The special place is: + + ~/.local/lib64/python3.6/site-packages + +A .pth file must be set for each major.minor version of Python. Here is an +example for Python 2.7: + + $HOME/.local/lib64/python2.7/site-packages/home.pth + +Its content is simple: + + /home/username/local/lib64/python2.7/site-packages + +(These examples are from a Slackware box which installs Python modules into +the 'lib64' directory. Other distributions may simply use 'lib' or another +name.) + +To verify the path, start the Python interpreter in interactive mode, +import the sys module, and check the value of sys.path. It will show a +number of paths in the list including the ones above and the directory the +interpreter was started from and various system directories. + +Far more information than this is available in the relevant Python +documentation, but this should get your scripts working. + +As always, feedback is welcome: + + Hamlib Developers + +73, Nate, N0NB diff --git a/bindings/py3test.py b/bindings/py3test.py new file mode 100755 index 000000000..3334b755e --- /dev/null +++ b/bindings/py3test.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import sys + +## Uncomment to run this script from an in-tree build (or adjust to the +## build directory) without installing the bindings. +#sys.path.append ('.') +#sys.path.append ('.libs') + +import Hamlib + +def StartUp(): + """Simple script to test the Hamlib.py module with Python3.""" + + print("%s: Python %s; %s\n" \ + % (sys.argv[0], sys.version.split()[0], Hamlib.cvar.hamlib_version)) + + Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE) + + # Init RIG_MODEL_DUMMY + my_rig = Hamlib.Rig(Hamlib.RIG_MODEL_DUMMY) + my_rig.set_conf("rig_pathname", "/dev/Rig") + my_rig.set_conf("retry", "5") + + my_rig.open () + + # 1073741944 is token value for "itu_region" + # but using get_conf is much more convenient + region = my_rig.get_conf(1073741944) + rpath = my_rig.get_conf("rig_pathname") + retry = my_rig.get_conf("retry") + + print("status(str):\t\t%s" % Hamlib.rigerror(my_rig.error_status)) + print("get_conf:\t\tpath = %s, retry = %s, ITU region = %s" \ + % (rpath, retry, region)) + + my_rig.set_freq(Hamlib.RIG_VFO_B, 5700000000) + my_rig.set_vfo(Hamlib.RIG_VFO_B) + + print("freq:\t\t\t%s" % my_rig.get_freq()) + + my_rig.set_freq(Hamlib.RIG_VFO_A, 145550000) + (mode, width) = my_rig.get_mode() + + print("mode:\t\t\t%s\nbandwidth:\t\t%s" % (Hamlib.rig_strrmode(mode), width)) + + my_rig.set_mode(Hamlib.RIG_MODE_CW) + (mode, width) = my_rig.get_mode() + + print("mode:\t\t\t%s\nbandwidth:\t\t%s" % (Hamlib.rig_strrmode(mode), width)) + + print("ITU_region:\t\t%s" % my_rig.state.itu_region) + print("Backend copyright:\t%s" % my_rig.caps.copyright) + print("Model:\t\t\t%s" % my_rig.caps.model_name) + print("Manufacturer:\t\t%s" % my_rig.caps.mfg_name) + print("Backend version:\t%s" % my_rig.caps.version) + print("Backend license:\t%s" % my_rig.caps.copyright) + print("Rig info:\t\t%s" % my_rig.get_info()) + + my_rig.set_level("VOX", 1) + + print("VOX level:\t\t%s" % my_rig.get_level_i("VOX")) + + my_rig.set_level(Hamlib.RIG_LEVEL_VOX, 5) + + print("VOX level:\t\t%s" % my_rig.get_level_i(Hamlib.RIG_LEVEL_VOX)) + + af = 12.34 + + print("Setting AF to %0.2f...." % (af)) + + my_rig.set_level("AF", af) + + print("status:\t\t\t%s - %s" % (my_rig.error_status, + Hamlib.rigerror(my_rig.error_status))) + + print("AF level:\t\t%s" % my_rig.get_level_f(Hamlib.RIG_LEVEL_AF)) + print("strength:\t\t%s" % my_rig.get_level_i(Hamlib.RIG_LEVEL_STRENGTH)) + print("status:\t\t\t%s" % my_rig.error_status) + print("status(str):\t\t%s" % Hamlib.rigerror(my_rig.error_status)) + + chan = Hamlib.channel(Hamlib.RIG_VFO_B) + my_rig.get_channel(chan) + + print("get_channel status:\t%s" % my_rig.error_status) + print("VFO:\t\t\t%s, %s" % (Hamlib.rig_strvfo(chan.vfo), chan.freq)) + print("Attenuators:\t\t%s" % my_rig.caps.attenuator) + print("\nSending Morse, '73'") + + my_rig.send_morse(Hamlib.RIG_VFO_A, "73") + my_rig.close() + + print("\nSome static functions:") + + err, lon1, lat1 = Hamlib.locator2longlat("IN98XC") + err, lon2, lat2 = Hamlib.locator2longlat("DM33DX") + err, loc1 = Hamlib.longlat2locator(lon1, lat1, 3) + err, loc2 = Hamlib.longlat2locator(lon2, lat2, 3) + + print("Loc1:\t\tIN98XC -> %9.4f, %9.4f -> %s" % (lon1, lat1, loc1)) + print("Loc2:\t\tDM33DX -> %9.4f, %9.4f -> %s" % (lon2, lat2, loc2)) + + err, dist, az = Hamlib.qrb(lon1, lat1, lon2, lat2) + longpath = Hamlib.distance_long_path(dist) + + print("Distance:\t%.3f km, azimuth %.2f, long path:\t%.3f km" \ + % (dist, az, longpath)) + + # dec2dms expects values from 180 to -180 + # sw is 1 when deg is negative (west or south) as 0 cannot be signed + err, deg1, mins1, sec1, sw1 = Hamlib.dec2dms(lon1) + err, deg2, mins2, sec2, sw2 = Hamlib.dec2dms(lat1) + + lon3 = Hamlib.dms2dec(deg1, mins1, sec1, sw1) + lat3 = Hamlib.dms2dec(deg2, mins2, sec2, sw2) + + print('Longitude:\t%4.4f, %4d° %2d\' %2d" %1s\trecoded: %9.4f' \ + % (lon1, deg1, mins1, sec1, ('W' if sw1 else 'E'), lon3)) + + print('Latitude:\t%4.4f, %4d° %2d\' %2d" %1s\trecoded: %9.4f' \ + % (lat1, deg2, mins2, sec2, ('S' if sw2 else 'N'), lat3)) + + +if __name__ == '__main__': + StartUp() diff --git a/bindings/pytest.py b/bindings/pytest.py index 3419aafef..9716b2251 100755 --- a/bindings/pytest.py +++ b/bindings/pytest.py @@ -1,82 +1,94 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # -*- coding: utf-8 -*- import sys -sys.path.append ('.') -sys.path.append ('.libs') +## Uncomment to run this script from an in-tree build (or adjust to the +## build directory) without installing the bindings. +#sys.path.append ('.') +#sys.path.append ('.libs') import Hamlib -def StartUp (): - print "Python", sys.version[:5], "test,", Hamlib.cvar.hamlib_version, "\n" +def StartUp(): + """Simple script to test the Hamlib.py module with Python2.""" - #Hamlib.rig_set_debug (Hamlib.RIG_DEBUG_TRACE) - Hamlib.rig_set_debug (Hamlib.RIG_DEBUG_NONE) + print "%s: Python %s; %s\n" \ + % (sys.argv[0], sys.version.split()[0], Hamlib.cvar.hamlib_version) + + Hamlib.rig_set_debug(Hamlib.RIG_DEBUG_NONE) # Init RIG_MODEL_DUMMY my_rig = Hamlib.Rig (Hamlib.RIG_MODEL_DUMMY) - my_rig.set_conf ("rig_pathname","/dev/Rig") - my_rig.set_conf ("retry","5") + my_rig.set_conf("rig_pathname", "/dev/Rig") + my_rig.set_conf("retry", "5") - my_rig.open () + my_rig.open() # 1073741944 is token value for "itu_region" # but using get_conf is much more convenient region = my_rig.get_conf(1073741944) rpath = my_rig.get_conf("rig_pathname") retry = my_rig.get_conf("retry") - print "status(str):\t\t",Hamlib.rigerror(my_rig.error_status) + + print "status(str):\t\t", Hamlib.rigerror(my_rig.error_status) print "get_conf:\t\tpath = %s, retry = %s, ITU region = %s" \ - % (rpath, retry, region) + % (rpath, retry, region) - my_rig.set_freq (Hamlib.RIG_VFO_B, 5700000000) - my_rig.set_vfo (Hamlib.RIG_VFO_B) - print "freq:\t\t\t",my_rig.get_freq() - my_rig.set_freq (Hamlib.RIG_VFO_A, 145550000) - #my_rig.set_vfo ("VFOA") + my_rig.set_freq(Hamlib.RIG_VFO_B, 5700000000) + my_rig.set_vfo(Hamlib.RIG_VFO_B) + print "freq:\t\t\t", my_rig.get_freq() + + my_rig.set_freq(Hamlib.RIG_VFO_A, 145550000) (mode, width) = my_rig.get_mode() - print "mode:\t\t\t",Hamlib.rig_strrmode(mode),"\nbandwidth:\t\t",width + + print "mode:\t\t\t", Hamlib.rig_strrmode(mode), "\nbandwidth:\t\t", width + my_rig.set_mode(Hamlib.RIG_MODE_CW) (mode, width) = my_rig.get_mode() - print "mode:\t\t\t",Hamlib.rig_strrmode(mode),"\nbandwidth:\t\t",width - print "ITU_region:\t\t",my_rig.state.itu_region - print "Backend copyright:\t",my_rig.caps.copyright + print "mode:\t\t\t", Hamlib.rig_strrmode(mode), "\nbandwidth:\t\t", width - print "Model:\t\t\t",my_rig.caps.model_name - print "Manufacturer:\t\t",my_rig.caps.mfg_name - print "Backend version:\t",my_rig.caps.version - print "Backend license:\t",my_rig.caps.copyright + print "ITU_region:\t\t", my_rig.state.itu_region + print "Backend copyright:\t", my_rig.caps.copyright + print "Model:\t\t\t", my_rig.caps.model_name + print "Manufacturer:\t\t", my_rig.caps.mfg_name + print "Backend version:\t", my_rig.caps.version + print "Backend license:\t", my_rig.caps.copyright print "Rig info:\t\t", my_rig.get_info() - my_rig.set_level ("VOX", 1) - print "VOX level:\t\t",my_rig.get_level_i("VOX") - my_rig.set_level (Hamlib.RIG_LEVEL_VOX, 5) + my_rig.set_level("VOX", 1) + + print "VOX level:\t\t", my_rig.get_level_i("VOX") + + my_rig.set_level(Hamlib.RIG_LEVEL_VOX, 5) + print "VOX level:\t\t", my_rig.get_level_i(Hamlib.RIG_LEVEL_VOX) af = 12.34 - print "Setting AF to %f...." % (af) - my_rig.set_level ("AF", af) - print "status:\t\t\t%s - %s" % (my_rig.error_status, Hamlib.rigerror(my_rig.error_status)) - print "AF level:\t\t", my_rig.get_level_f(Hamlib.RIG_LEVEL_AF) + print "Setting AF to %0.2f...." % (af) + + my_rig.set_level ("AF", af) + + print "status:\t\t\t%s - %s" % (my_rig.error_status, + Hamlib.rigerror(my_rig.error_status)) + + print "AF level:\t\t%0.2f" % my_rig.get_level_f(Hamlib.RIG_LEVEL_AF) print "strength:\t\t", my_rig.get_level_i(Hamlib.RIG_LEVEL_STRENGTH) - print "status:\t\t\t",my_rig.error_status - print "status(str):\t\t",Hamlib.rigerror(my_rig.error_status) + print "status:\t\t\t", my_rig.error_status + print "status(str):\t\t", Hamlib.rigerror(my_rig.error_status) chan = Hamlib.channel(Hamlib.RIG_VFO_B) - my_rig.get_channel(chan) - print "get_channel status:\t",my_rig.error_status - print "VFO:\t\t\t",Hamlib.rig_strvfo(chan.vfo),", ",chan.freq + print "get_channel status:\t", my_rig.error_status + print "VFO:\t\t\t", Hamlib.rig_strvfo(chan.vfo), ", ", chan.freq print "Attenuators:\t\t", my_rig.caps.attenuator - print "\nSending Morse, '73'" - my_rig.send_morse(Hamlib.RIG_VFO_A, "73") + my_rig.send_morse(Hamlib.RIG_VFO_A, "73") my_rig.close () print "\nSome static functions:" @@ -85,13 +97,15 @@ def StartUp (): err, lon2, lat2 = Hamlib.locator2longlat("DM33DX") err, loc1 = Hamlib.longlat2locator(lon1, lat1, 3) err, loc2 = Hamlib.longlat2locator(lon2, lat2, 3) + print "Loc1:\t\tIN98XC -> %9.4f, %9.4f -> %s" % (lon1, lat1, loc1) print "Loc2:\t\tDM33DX -> %9.4f, %9.4f -> %s" % (lon2, lat2, loc2) err, dist, az = Hamlib.qrb(lon1, lat1, lon2, lat2) longpath = Hamlib.distance_long_path(dist) + print "Distance:\t%.3f km, azimuth %.2f, long path:\t%.3f km" \ - % (dist, az, longpath) + % (dist, az, longpath) # dec2dms expects values from 180 to -180 # sw is 1 when deg is negative (west or south) as 0 cannot be signed @@ -102,10 +116,11 @@ def StartUp (): lat3 = Hamlib.dms2dec(deg2, mins2, sec2, sw2) print 'Longitude:\t%4.4f, %4d° %2d\' %2d" %1s\trecoded: %9.4f' \ - % (lon1, deg1, mins1, sec1, ('W' if sw1 else 'E'), lon3) + % (lon1, deg1, mins1, sec1, ('W' if sw1 else 'E'), lon3) print 'Latitude:\t%4.4f, %4d° %2d\' %2d" %1s\trecoded: %9.4f' \ - % (lat1, deg2, mins2, sec2, ('S' if sw2 else 'N'), lat3) + % (lat1, deg2, mins2, sec2, ('S' if sw2 else 'N'), lat3) + if __name__ == '__main__': - StartUp () + StartUp()