diff --git a/.travis.yml b/.travis.yml index 742fe98..4bce1d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ os: - linux - osx julia: - - 0.4 - 0.5 - nightly notifications: diff --git a/README.md b/README.md index f400792..cc13393 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ [![Build Status](https://travis-ci.org/rdeits/PyLCM.jl.svg?branch=master)](https://travis-ci.org/rdeits/PyLCM.jl) [![codecov.io](https://codecov.io/github/rdeits/PyLCM.jl/coverage.svg?branch=master)](https://codecov.io/github/rdeits/PyLCM.jl?branch=master) -PyLCM provides an interface to the [Lightweight Communications and Marshalling (LCM) library](https://lcm-proj.github.io/) in Julia. It wraps the LCM Python interface using [PyCall](https://github.com/stevengj/PyCall.jl), so it will be slower than calling the LCM C-API directly. +PyLCM provides an interface to the [Lightweight Communications and Marshalling (LCM) library](https://lcm-proj.github.io/) in Julia. Most of the functionality is provided by [LCMCore.jl](https://github.com/rdeits/LCMCore.jl), which interacts with LCM through its C API. PyLCM builds on LCMCore by allowing you to send and receive Python LCM types from Julia. # Installation -If you have a systemwide installation of LCM, PyLCM will try to use it. If you don't, then running `Pkg.build("PyLCM")` will download and install a private copy of LCM and the python bindings for you. +If you have a systemwide installation of LCM, PyLCM will try to use it. If you don't, then running `Pkg.build("LCMCore")` will download and install a private copy of LCM and the python bindings for you. # Usage @@ -65,3 +65,13 @@ while true handle(lc) end ``` + +### Asynchronously handling messages + +Creating an asynchronous handler just requires the `@async` macro: + +```julia +@async while true + handle(lc) +end +``` diff --git a/REQUIRE b/REQUIRE index 2af2df6..a1f3dfd 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,5 +1,3 @@ -julia 0.4 +julia 0.5 PyCall 1.4.0 -BinDeps 0.4.0 -Compat 0.8.0 -@osx Homebrew 0.3.0 +LCMCore 0.0.1 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 383868d..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,34 +0,0 @@ -environment: - matrix: - - JULIAVERSION: "julialang/bin/winnt/x86/0.4/julia-0.4-latest-win32.exe" - - JULIAVERSION: "julialang/bin/winnt/x64/0.4/julia-0.4-latest-win64.exe" - - JULIAVERSION: "julianightlies/bin/winnt/x86/julia-latest-win32.exe" - - JULIAVERSION: "julianightlies/bin/winnt/x64/julia-latest-win64.exe" - -branches: - only: - - master - - /release-.*/ - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: -# Download most recent Julia Windows binary - - ps: (new-object net.webclient).DownloadFile( - $("http://s3.amazonaws.com/"+$env:JULIAVERSION), - "C:\projects\julia-binary.exe") -# Run installer silently, output to C:\projects\julia - - C:\projects\julia-binary.exe /S /D=C:\projects\julia - -build_script: -# Need to convert from shallow to complete for Pkg.clone to work - - IF EXIST .git\shallow (git fetch --unshallow) - - C:\projects\julia\bin\julia -e "versioninfo(); - Pkg.clone(pwd(), \"PyLCM\"); Pkg.build(\"PyLCM\")" - -test_script: - - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"PyLCM\")" diff --git a/deps/build.jl b/deps/build.jl deleted file mode 100644 index 0a1ce76..0000000 --- a/deps/build.jl +++ /dev/null @@ -1,85 +0,0 @@ -using BinDeps -using Compat - -@BinDeps.setup - -function cflags_validator(pkg_name) - return (name, handle) -> begin - try - run(`pkg-config --cflags $(pkg_name)`) - return true - catch ErrorException - return false - end - end -end - -@static if is_linux() - deps = [ - python = library_dependency("python", aliases=["libpython2.7.so", "libpython3.2.so", "libpython3.3.so", "libpython3.4.so", "libpython3.5.so", "libpython3.6.so", "libpython3.7.so"], validate=cflags_validator("python")) - glib = library_dependency("glib", aliases=["libglib-2.0-0", "libglib-2.0", "libglib-2.0.so.0"], depends=[python], validate=cflags_validator("glib-2.0")) - lcm = library_dependency("lcm", aliases=["liblcm", "liblcm.1"], depends=[glib]) - - provides(AptGet, Dict("python-dev" => python, "libglib2.0-dev" => glib)) - ] -else - deps = [ - glib = library_dependency("glib", aliases = ["libglib-2.0-0", "libglib-2.0", "libglib-2.0.so.0"]) - lcm = library_dependency("lcm", aliases=["liblcm", "liblcm.1"], depends=[glib]) - ] -end - -prefix = joinpath(BinDeps.depsdir(lcm), "usr") - -lcm_cmake_arguments = String[] -@static if is_apple() - if Pkg.installed("Homebrew") === nothing - error("Homebrew package not installed, please run Pkg.add(\"Homebrew\")") - end - using Homebrew - provides(Homebrew.HB, "glib", glib, os=:Darwin) - push!(lcm_cmake_arguments, - "-DCMAKE_LIBRARY_PATH=$(joinpath(Pkg.dir("Homebrew"), "deps", "usr", "lib"))") - push!(lcm_cmake_arguments, - "-DCMAKE_INCLUDE_PATH=$(joinpath(Pkg.dir("Homebrew"), "deps", "usr", "include"))") - -end - -provides(Yum, - Dict("glib" => glib)) - -lcm_sha = "9e53469cd0713ca8fbf37a968f6fd314f5f11584" -lcm_folder = "lcm-$(lcm_sha)" - -provides(Sources, - URI("https://github.com/lcm-proj/lcm/archive/$(lcm_sha).zip"), - lcm, - unpacked_dir=lcm_folder) - -lcm_builddir = joinpath(BinDeps.depsdir(lcm), "builds", "lcm") -lcm_srcdir = joinpath(BinDeps.depsdir(lcm), "src", lcm_folder) -lcm_cmake_command = `cmake -DCMAKE_INSTALL_PREFIX=$(prefix)` -for arg in lcm_cmake_arguments - lcm_cmake_command = `$lcm_cmake_command $arg` -end -lcm_cmake_command = `$lcm_cmake_command $lcm_srcdir` - -provides(BuildProcess, - (@build_steps begin - GetSources(lcm) - CreateDirectory(lcm_builddir) - @build_steps begin - ChangeDirectory(lcm_builddir) - lcm_cmake_command - `cmake --build . --target install` - end - end), - lcm, - onload=""" -using PyCall -sys = pyimport("sys") -unshift!(PyVector(sys["path"]), joinpath("$(prefix)", "lib", "python" * string(sys[:version_info][1]) * "." * string(sys[:version_info][2]), "site-packages")) -""" -) - -@BinDeps.install Dict(:lcm => :liblcm) diff --git a/src/PyLCM.jl b/src/PyLCM.jl index 7b92ae5..10a31c0 100644 --- a/src/PyLCM.jl +++ b/src/PyLCM.jl @@ -2,59 +2,35 @@ __precompile__() module PyLCM -using Base.Dates: Period, Millisecond using PyCall +using Base.Dates: Period, Millisecond +using LCMCore +import LCMCore: encode, subscribe, decode + export LCM, publish, subscribe, handle, @pyimport const pylcm = PyNULL() -immutable LCM - lcm_obj::PyObject - - LCM() = new(pylcm[:LCM]()) -end - -function publish(lc::LCM, channel::AbstractString, msg) - pycall(lc.lcm_obj[:publish], PyAny, channel, pycall(msg[:encode], PyObject)) -end - -function subscribe(lc::LCM, channel::AbstractString, handler::Function) - lc.lcm_obj[:subscribe](channel, pyeval("lambda chan, data, handler=h: handler(chan, bytearray(data))", h=handler)) -end - -function subscribe(lc::LCM, channel::AbstractString, handler::Function, msg_type::PyObject) - lc.lcm_obj[:subscribe](channel, pyeval("lambda chan, data, handler=h, msg_type=t: handler(chan, msg_type.decode(data))", h=handler, t=msg_type)) -end - -"Wait for and dispatch the next incoming message" -function handle(lc::LCM) - pycall(lc.lcm_obj[:handle], PyObject) - true -end - -""" - handle(lc, timeout) - -Wait for and dispatch the next incoming message, with a timeout expressed -as any Base.Dates.Period type. For example: - - handle(lc, Millisecond(10)) - -or - - handle(lc, Second(1)) - -Returns true if a message was handled, false if the function timed out. -""" -function handle(lc::LCM, timeout::Period) - timeout_ms = convert(Int, convert(Millisecond, timeout)) - convert(Bool, pycall(lc.lcm_obj[:handle_timeout], PyObject, timeout_ms)) +# This is the only method required to enable publishing python +# LCM messages +encode(msg::PyObject) = pycall(msg[:encode], Vector{UInt8}) + +# We override the subscribe() method that takes in a user-supplied +# message type. That's because the method in LCMCore assumes that +# the Julia type of the message is enough to determine how to +# decode the message. That's not the case for PyLCM because all +# python LCM types are just PyObjects. +function subscribe(lcm::LCM, channel::String, handler, msgtype::PyObject) + function inner_handler(channel, data) + pymsg = pycall(msgtype[:decode], PyObject, data) + handler(channel, pymsg) + end + subscribe(lcm, channel, inner_handler) end function __init__() - depsjl = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl") - isfile(depsjl) ? include(depsjl) : error("PyLCM not properly ", - "installed. Please run\nPkg.build(\"PyLCM\")") + sys = pyimport("sys") + unshift!(PyVector(sys["path"]), joinpath(LCMCore.lcm_prefix, "lib", "python" * string(sys[:version_info][1]) * "." * string(sys[:version_info][2]), "site-packages")) copy!(pylcm, pyimport("lcm")) end