├── libsweep ├── examples │ ├── .gitignore │ ├── net.proto │ ├── example.cc │ ├── README.md │ ├── CMakeLists.txt │ ├── example.c │ ├── net.cc │ └── viewer.cc ├── .gitignore ├── cmake │ ├── config.h.in │ ├── SweepConfig.cmake │ └── CMakeUninstall.cmake.in ├── man │ ├── build.sh │ ├── sweep-ctl.md │ └── sweep-ctl.1 ├── include │ ├── error.hpp │ ├── serial.hpp │ ├── queue.hpp │ ├── sweep │ │ ├── sweep.h │ │ └── sweep.hpp │ └── protocol.hpp ├── src │ ├── sweep-ctl.cc │ ├── protocol.cc │ ├── unix │ │ └── serial.cc │ ├── dummy.cc │ ├── win │ │ └── serial.cc │ └── sweep.cc ├── .clang-format ├── CMakeLists.txt ├── README.md └── doc │ └── serial_protocol_spec.md ├── jsweep ├── .gitignore ├── settings.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── scanse │ │ │ └── sweep │ │ │ ├── Sweep.java │ │ │ ├── jna │ │ │ ├── DeviceJNAPointer.java │ │ │ ├── ScanJNAPointer.java │ │ │ ├── ErrorJNAPointer.java │ │ │ ├── ErrorReturnJNA.java │ │ │ └── SweepJNA.java │ │ │ ├── SweepSample.java │ │ │ └── SweepDevice.java │ └── test │ │ └── java │ │ └── io │ │ └── scanse │ │ └── sweep │ │ └── Example.java ├── build.gradle ├── libsweep-bundling.gradle ├── gradlew.bat └── gradlew ├── sweepjs ├── .gitignore ├── examples │ ├── README.md │ └── websocket │ │ ├── package.json │ │ ├── index.html │ │ └── sweepjsd.js ├── package.json ├── index.js ├── common.gypi ├── sweepjs.h ├── binding.gyp ├── README.md ├── .clang-format └── sweepjs.cc ├── sweeppy ├── .gitignore ├── sweeppy │ ├── __main__.py │ ├── app.py │ └── __init__.py ├── setup.py └── README.md ├── travis ├── java-check.sh └── java-run-task.sh ├── Dockerfile ├── docker ├── build-utils.sh ├── build.sh └── install-yum.sh ├── RELEASE.md ├── README.md ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── LICENSE ├── CHANGELOG.md ├── appveyor.yml └── .travis.yml /libsweep/examples/.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | build 3 | -------------------------------------------------------------------------------- /jsweep/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | /bin/ 4 | -------------------------------------------------------------------------------- /libsweep/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | a.out 3 | *.so 4 | *.o -------------------------------------------------------------------------------- /jsweep/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jsweep' 2 | -------------------------------------------------------------------------------- /sweepjs/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /jsweep/gradle.properties: -------------------------------------------------------------------------------- 1 | group=io.scanse.sweep 2 | version=1.3.0 3 | -------------------------------------------------------------------------------- /sweeppy/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.py[cod] 3 | 4 | dist/ 5 | build/ 6 | *.egg-info 7 | 8 | examples/venv 9 | -------------------------------------------------------------------------------- /jsweep/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scanse/sweep-sdk/HEAD/jsweep/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /travis/java-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 3 | docker build -t "travis-sweep" . 4 | docker run -e GRADLE_TASK="build" "travis-sweep" 5 | else 6 | pushd jsweep 7 | ./gradlew build 8 | popd 9 | fi 10 | -------------------------------------------------------------------------------- /sweepjs/examples/README.md: -------------------------------------------------------------------------------- 1 | # SweepJs Examples 2 | 3 | Requires `libsweep.so` to be installed. 4 | Requires the SweepJs module to be installed. 5 | 6 | ### License 7 | 8 | Copyright © 2016 Daniel J. Hofmann 9 | 10 | Distributed under the MIT License (MIT). 11 | -------------------------------------------------------------------------------- /travis/java-run-task.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | task="$1" 3 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 4 | docker build -t "travis-sweep" . 5 | docker run -e GRADLE_TASK="$task" "travis-sweep" 6 | else 7 | pushd jsweep 8 | ./gradlew "$task" 9 | popd 10 | fi 11 | -------------------------------------------------------------------------------- /jsweep/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Apr 15 20:06:50 PDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Based upon the manylinux image from pypa 2 | FROM quay.io/pypa/manylinux1_x86_64 3 | 4 | COPY docker /opt/sweep/bin 5 | COPY libsweep /opt/sweep/libsweep 6 | COPY jsweep /opt/sweep/jsweep 7 | 8 | ENV PATH /opt/sweep/bin:$PATH 9 | 10 | RUN install-yum.sh 11 | 12 | CMD build.sh 13 | -------------------------------------------------------------------------------- /libsweep/cmake/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_CONFIG_7390A8657402_H 2 | #define SWEEP_CONFIG_7390A8657402_H 3 | 4 | #define SWEEP_VERSION_MAJOR @SWEEP_VERSION_MAJOR@ 5 | #define SWEEP_VERSION_MINOR @SWEEP_VERSION_MINOR@ 6 | #define SWEEP_VERSION ((SWEEP_VERSION_MAJOR << 16u) | SWEEP_VERSION_MINOR) 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /libsweep/examples/net.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package sweep.proto; 4 | 5 | option optimize_for = LITE_RUNTIME; 6 | 7 | message scan { 8 | repeated int32 angle = 1 [packed=true]; 9 | repeated int32 distance = 2 [packed=true]; 10 | repeated int32 signal_strength = 3 [packed=true]; 11 | } 12 | -------------------------------------------------------------------------------- /libsweep/man/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Builds man pages from markdown files. 4 | # Usage: ./build.sh page.md page.1 5 | 6 | set -o errexit 7 | set -o pipefail 8 | set -o nounset 9 | 10 | if [ $# -ne 2 ]; then 11 | exit 1 12 | fi 13 | 14 | pandoc --standalone "${1}" --output "${2}" 15 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/Sweep.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep; 2 | 3 | import io.scanse.sweep.jna.SweepJNA; 4 | 5 | public class Sweep { 6 | 7 | public static final int VERSION = SweepJNA.sweep_get_version(); 8 | public static final boolean ABI_COMPATIBLE = 9 | SweepJNA.sweep_is_abi_compatible(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /sweepjs/examples/websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sweepjsd", 3 | "version": "0.0.1", 4 | "description": "Scanse Sweep Websocket Example", 5 | "author": "Daniel J. Hofmann", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "node sweepjsd" 9 | }, 10 | "dependencies": { 11 | "koa": "^1.2", 12 | "koa-static": "^2.0", 13 | "ws": "^1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docker/build-utils.sh: -------------------------------------------------------------------------------- 1 | function check_var { 2 | if [ -z "$1" ]; then 3 | echo "required variable not defined" 4 | exit 1 5 | fi 6 | } 7 | 8 | function check_sha256sum { 9 | local fname=$1 10 | check_var ${fname} 11 | local sha256=$2 12 | check_var ${sha256} 13 | 14 | echo "${sha256} ${fname}" > ${fname}.sha256 15 | sha256sum -c ${fname}.sha256 16 | rm -f ${fname}.sha256 17 | } 18 | -------------------------------------------------------------------------------- /sweepjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sweepjs", 3 | "version": "1.3.0", 4 | "description": "Native module for the Sweep LiDAR", 5 | "keywords": [ 6 | "addon", 7 | "native", 8 | "module" 9 | ], 10 | "author": "Daniel J. Hofmann", 11 | "license": "MIT", 12 | "main": "./build/Release/sweepjs", 13 | "scripts": { 14 | "test": "node index /dev/ttyUSB0" 15 | }, 16 | "dependencies": { 17 | "nan": "^2.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/jna/DeviceJNAPointer.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep.jna; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import com.sun.jna.Pointer; 7 | import com.sun.jna.Structure; 8 | 9 | public class DeviceJNAPointer extends Structure { 10 | 11 | public Pointer device; 12 | 13 | @Override 14 | protected List getFieldOrder() { 15 | return Arrays.asList("device"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | # set cmake as cmake28 6 | ln -s "$(which cmake28)" /opt/sweep/bin/cmake 7 | 8 | gradleTask="$GRADLE_TASK" 9 | if [[ "$gradleTask" == "" ]]; then gradleTask="build"; fi 10 | 11 | cd /opt/sweep 12 | pushd libsweep 13 | rm -rf build 14 | mkdir -p build 15 | cd build 16 | cmake .. -DCMAKE_BUILD_TYPE=Release -DDUMMY=On 17 | cmake --build . 18 | popd 19 | 20 | pushd jsweep 21 | ./gradlew "$gradleTask" 22 | popd 23 | -------------------------------------------------------------------------------- /libsweep/include/error.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_ERROR_1E0FA029CE94_HPP 2 | #define SWEEP_ERROR_1E0FA029CE94_HPP 3 | 4 | /* 5 | * Internal base error sub-system errors inherit from. 6 | * Implementation detail; not exported. 7 | */ 8 | 9 | #include 10 | 11 | namespace sweep { 12 | namespace error { 13 | 14 | struct error : std::runtime_error { 15 | using base = std::runtime_error; 16 | using base::base; 17 | }; 18 | 19 | } // ns errors 20 | } // ns sweep 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /sweepjs/examples/websocket/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scanse Sweep Websocket Example 6 | 7 | 8 | 9 | 10 |

Open Console

11 | 12 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/jna/ScanJNAPointer.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep.jna; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import com.sun.jna.Pointer; 7 | import com.sun.jna.Structure; 8 | 9 | /** 10 | * Pointer to a scan object with unknown content, must be queried through API. 11 | */ 12 | public class ScanJNAPointer extends Structure { 13 | 14 | public Pointer scan; 15 | 16 | @Override 17 | protected List getFieldOrder() { 18 | return Arrays.asList("scan"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /libsweep/cmake/SweepConfig.cmake: -------------------------------------------------------------------------------- 1 | # Exports: 2 | # LIBSWEEP_FOUND 3 | # LIBSWEEP_INCLUDE_DIR 4 | # LIBSWEEP_LIBRARY 5 | # Hints: 6 | # LIBSWEEP_LIBRARY_DIR 7 | 8 | find_path(LIBSWEEP_INCLUDE_DIR 9 | sweep/sweep.h sweep/sweep.hpp) 10 | 11 | find_library(LIBSWEEP_LIBRARY 12 | NAMES sweep 13 | HINTS "${LIBSWEEP_LIBRARY_DIR}") 14 | 15 | include(FindPackageHandleStandardArgs) 16 | find_package_handle_standard_args(Sweep DEFAULT_MSG LIBSWEEP_LIBRARY LIBSWEEP_INCLUDE_DIR) 17 | mark_as_advanced(LIBSWEEP_INCLUDE_DIR LIBSWEEP_LIBRARY) 18 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Publishing a new version 2 | 3 | - [ ] Sync. `CHANGELOG.md` with latest changes and fixes 4 | - [ ] Sync. version number in `libsweep/CMakeLists.txt`, `sweeppy/setup.py`, `sweepjs/package.json`, `jsweep/gradle.properties` 5 | - [ ] Make sure Travis and AppVeyor are green and show no warnings 6 | - [ ] Continuous Integration only tests dummy library: test with real device 7 | - [ ] Tag a commit `git tag -a vx.y.z gitsha` (we use [semantic versioning](http://semver.org/)) 8 | - [ ] Push tag to Github `git push origin vx.y.z` 9 | - [ ] Head over to https://github.com/scanse/sweep-sdk/releases and make a release 10 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/jna/ErrorJNAPointer.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep.jna; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import com.sun.jna.Pointer; 7 | import com.sun.jna.Structure; 8 | 9 | public class ErrorJNAPointer extends Structure { 10 | 11 | public ErrorJNAPointer() { 12 | } 13 | 14 | public ErrorJNAPointer(Pointer p) { 15 | super(p); 16 | } 17 | 18 | // the actual pointer at the location, opaque data 19 | public Pointer ref; 20 | 21 | @Override 22 | protected List getFieldOrder() { 23 | return Arrays.asList("ref"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/jna/ErrorReturnJNA.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep.jna; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import com.sun.jna.Pointer; 7 | import com.sun.jna.Structure; 8 | 9 | public class ErrorReturnJNA extends Structure { 10 | 11 | public static class EJNAPtrPtr extends ErrorJNAPointer 12 | implements Structure.ByReference { 13 | public EJNAPtrPtr(Pointer p) { 14 | super(p); 15 | } 16 | } 17 | 18 | // the actual pointer at the location, opaque data 19 | public EJNAPtrPtr returnedError; 20 | 21 | @Override 22 | protected List getFieldOrder() { 23 | return Arrays.asList("returnedError"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /jsweep/src/test/java/io/scanse/sweep/Example.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep; 2 | 3 | import java.util.List; 4 | 5 | public class Example { 6 | 7 | public static void main(String[] args) throws Exception { 8 | try (SweepDevice device = new SweepDevice("/dev/ttyUSB0")) { 9 | int speed = device.getMotorSpeed(); 10 | int rate = device.getSampleRate(); 11 | 12 | System.out.println(String.format("Motor Speed: %s Hz", speed)); 13 | System.out.println(String.format("Sample Rate: %s Hz", rate)); 14 | 15 | device.startScanning(); 16 | 17 | for (List s : device.scans()) { 18 | System.err.println(s); 19 | } 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /sweeppy/sweeppy/__main__.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import sys 3 | from . import Sweep 4 | 5 | 6 | def main(): 7 | if len(sys.argv) < 2: 8 | sys.exit('python -m sweeppy /dev/ttyUSB0') 9 | 10 | dev = sys.argv[1] 11 | 12 | with Sweep(dev) as sweep: 13 | speed = sweep.get_motor_speed() 14 | rate = sweep.get_sample_rate() 15 | 16 | print('Motor Speed: {} Hz'.format(speed)) 17 | print('Sample Rate: {} Hz'.format(rate)) 18 | 19 | # Starts scanning as soon as the motor is ready 20 | sweep.start_scanning() 21 | 22 | # get_scans is coroutine-based generator lazily returning scans ad infinitum 23 | for scan in itertools.islice(sweep.get_scans(), 3): 24 | print(scan) 25 | 26 | main() 27 | -------------------------------------------------------------------------------- /libsweep/man/sweep-ctl.md: -------------------------------------------------------------------------------- 1 | % sweep-ctl(1) User Manuals 2 | % Daniel Hofmann 3 | % February 18, 2017 4 | 5 | # NAME 6 | 7 | sweep-ctl - get and set Sweep LiDAR hardware properties 8 | 9 | # SYNOPSIS 10 | 11 | sweep-ctl dev get|set key [value] 12 | 13 | # DESCRIPTION 14 | 15 | Command line tool to interact with the Sweep LiDAR device. 16 | 17 | # OPTIONS 18 | 19 | get property 20 | : Returns value currently set for property. 21 | 22 | set property value 23 | : Sets value for property. 24 | 25 | # PROPERTIES 26 | 27 | motor\_speed 28 | : The device's motor speed in Hz. 29 | 30 | sample\_rate 31 | : The device's sample rate in Hz. 32 | 33 | # EXAMPLE 34 | 35 | $ sweep-ctl /dev/ttyUSB0 get motor_speed 36 | 3 37 | 38 | $ sweep-ctl /dev/ttyUSB0 set motor_speed 5 39 | 5 40 | -------------------------------------------------------------------------------- /jsweep/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | apply plugin: 'eclipse' 4 | 5 | targetCompatibility = sourceCompatibility = '1.7' 6 | 7 | repositories { 8 | jcenter() 9 | } 10 | 11 | apply from: 'libsweep-bundling.gradle' 12 | 13 | dependencies { 14 | compile group: 'net.java.dev.jna', name: 'jna-platform', version: '4.4.0' 15 | compile files(libSweepJar.outputs) 16 | testCompile 'junit:junit:4.12' 17 | } 18 | 19 | task('sourcesJar', type: Jar, dependsOn: classes) { 20 | classifier = 'sources' 21 | from project.sourceSets.main.allSource 22 | } 23 | 24 | task('javadocJar', type: Jar, dependsOn: javadoc) { 25 | classifier = 'javadoc' 26 | from project.javadoc.destinationDir 27 | } 28 | artifacts { 29 | archives project.sourcesJar 30 | archives project.javadocJar 31 | } 32 | -------------------------------------------------------------------------------- /libsweep/include/serial.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_SERIAL_575F0FB571D1_HPP 2 | #define SWEEP_SERIAL_575F0FB571D1_HPP 3 | 4 | /* 5 | * Communication with serial devices. 6 | * Implementation detail; not exported. 7 | */ 8 | 9 | #include "error.hpp" 10 | 11 | #include "sweep.h" 12 | 13 | #include 14 | 15 | namespace sweep { 16 | namespace serial { 17 | 18 | struct error : sweep::error::error { 19 | using base = sweep::error::error; 20 | using base::base; 21 | }; 22 | 23 | using device_s = struct device*; 24 | 25 | device_s device_construct(const char* port, int32_t bitrate); 26 | void device_destruct(device_s serial); 27 | 28 | void device_read(device_s serial, void* to, int32_t len); 29 | void device_write(device_s serial, const void* from, int32_t len); 30 | void device_flush(device_s serial); 31 | 32 | } // ns serial 33 | } // ns sweep 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/SweepSample.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep; 2 | 3 | public final class SweepSample { 4 | 5 | private final int angle; 6 | private final int distance; 7 | private final int signalStrength; 8 | 9 | public SweepSample(int angle, int distance, int signalStrength) { 10 | this.angle = angle; 11 | this.distance = distance; 12 | this.signalStrength = signalStrength; 13 | } 14 | 15 | public int getAngle() { 16 | return angle; 17 | } 18 | 19 | public int getDistance() { 20 | return distance; 21 | } 22 | 23 | public int getSignalStrength() { 24 | return signalStrength; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "Sample[angle=" + angle + ",distance=" + distance 30 | + ",signalStrength=" + signalStrength + "]"; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /libsweep/man/sweep-ctl.1: -------------------------------------------------------------------------------- 1 | .TH "sweep\-ctl" "1" "February 18, 2017" "User Manuals" "" 2 | .SH NAME 3 | .PP 4 | sweep\-ctl \- get and set Sweep LiDAR hardware properties 5 | .SH SYNOPSIS 6 | .PP 7 | sweep\-ctl dev get|set key [value] 8 | .SH DESCRIPTION 9 | .PP 10 | Command line tool to interact with the Sweep LiDAR device. 11 | .SH OPTIONS 12 | .TP 13 | .B get property 14 | Returns value currently set for property. 15 | .RS 16 | .RE 17 | .TP 18 | .B set property value 19 | Sets value for property. 20 | .RS 21 | .RE 22 | .SH PROPERTIES 23 | .TP 24 | .B motor_speed 25 | The device\[aq]s motor speed in Hz. 26 | .RS 27 | .RE 28 | .TP 29 | .B sample_rate 30 | The device\[aq]s sample rate in Hz. 31 | .RS 32 | .RE 33 | .SH EXAMPLE 34 | .IP 35 | .nf 36 | \f[C] 37 | $\ sweep\-ctl\ /dev/ttyUSB0\ get\ motor_speed 38 | 3 39 | 40 | $\ sweep\-ctl\ /dev/ttyUSB0\ set\ motor_speed\ 5 41 | 5 42 | \f[] 43 | .fi 44 | .SH AUTHORS 45 | Daniel Hofmann. 46 | -------------------------------------------------------------------------------- /sweepjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sweepjs = require('.'); 4 | var assert = require('assert'); 5 | var util = require('util') 6 | 7 | 8 | if (require.main === module) { 9 | console.log('self-testing module'); 10 | 11 | var portName = process.argv[2]; 12 | var sweep = new sweepjs.Sweep(portName); 13 | 14 | var speed = sweep.getMotorSpeed(); 15 | var rate = sweep.getSampleRate(); 16 | 17 | console.log(util.format('Motor speed: %d Hz', speed)); 18 | console.log(util.format('Sample rate: %d Hz', rate)); 19 | 20 | console.log('Starting data acquisition as soon as motor is ready...'); 21 | sweep.startScanning(); 22 | 23 | sweep.scan(function (err, samples) { 24 | if (err) { 25 | return console.log(err); 26 | } 27 | 28 | samples.forEach(function (sample) { 29 | var fmt = util.format('angle: %d distance %d signal strength: %d', 30 | sample.angle, sample.distance, sample.signal); 31 | console.log(fmt); 32 | }); 33 | }); 34 | } 35 | 36 | module.exports = sweepjs; 37 | -------------------------------------------------------------------------------- /sweepjs/examples/websocket/sweepjsd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sweepjs = require('../..'); 4 | 5 | const koa = require('koa'); 6 | const srv = require('koa-static'); 7 | const wss = require('ws').Server({port: 5000}); 8 | 9 | 10 | const app = koa(); 11 | app.use(srv(__dirname, {index: 'index.html'})); 12 | app.listen(8080); 13 | 14 | 15 | const sweep = new sweepjs.Sweep('/dev/ttyUSB0'); 16 | sweep.startScanning(); 17 | 18 | 19 | function pushScanInto(ws) { 20 | return function push() { 21 | 22 | sweep.scan((err, samples) => { 23 | if (err) return; 24 | 25 | samples.forEach((sample) => { 26 | // x = cos(angle) * distance 27 | // y = sin(angle) * distance 28 | const message = {angle: sample.angle, distance: sample.distance}; 29 | ws.send(JSON.stringify(message), error => {/**/}); 30 | }); 31 | }); 32 | }; 33 | } 34 | 35 | wss.on('connection', ws => { 36 | setInterval(pushScanInto(ws), 2000); 37 | }); 38 | 39 | console.log('Now open browser at http://localhost:8080') 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sweep SDK 2 | 3 | [![Continuous Integration](https://travis-ci.org/scanse/sweep-sdk.svg?branch=master)](https://travis-ci.org/scanse/sweep-sdk) 4 | [![Continuous Integration](https://ci.appveyor.com/api/projects/status/github/scanse/sweep-sdk?svg=true)](https://ci.appveyor.com/project/kent-williams/sweep-sdk) 5 | 6 | SDK for Scanse Sweep LiDAR. 7 | 8 | - [libsweep](libsweep/README.md): low-level ABI/API-stable C library 9 | - [SweepPy](sweeppy/README.md): Python bindings 10 | - [SweepJs](sweepjs/README.md): NodeJS bindings 11 | - [JSweep](jsweep/): Java bindings 12 | 13 | Real-time viewer for a device speed of 5 Hz: 14 | 15 | ![viewer](https://cloud.githubusercontent.com/assets/527241/20300444/92ade432-ab1f-11e6-9d96-a585df3fe471.png) 16 | 17 | Density-based clustering on the point cloud: 18 | 19 | ![dbscan](https://cloud.githubusercontent.com/assets/527241/20300478/b5ae968e-ab1f-11e6-8ee0-d24aedd835f9.png) 20 | 21 | ### License 22 | 23 | Copyright © 2016 Daniel J. Hofmann 24 | 25 | Distributed under the MIT License (MIT). 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | #### sweep firmware version 8 | 9 | 10 | #### libsweep version + affected bindings 11 | 12 | 13 | 14 | #### operating system 15 | 16 | 17 | #### Platform/Hardware Setup 18 | 19 | 20 | #### Description: 21 | 26 | -------------------------------------------------------------------------------- /libsweep/cmake/CMakeUninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 3 | endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 4 | 5 | file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif(NOT "${rm_retval}" STREQUAL 0) 18 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 21 | endforeach(file) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Daniel J. Hofmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /sweepjs/common.gypi: -------------------------------------------------------------------------------- 1 | { 2 | "target_defaults": { 3 | "default_configuration": "Release", 4 | "cflags_cc" : [ "-std=c++11", "-Wall", "-Wextra", "-pedantic", ], 5 | "cflags_cc!": ["-std=gnu++0x", "-fno-rtti", "-fno-exceptions"], 6 | "configurations": { 7 | "Debug": { 8 | "defines!": [ "NDEBUG" ], 9 | "cflags_cc!": [ "-O3", "-Os", "-DNDEBUG" ], 10 | "xcode_settings": { 11 | "OTHER_CPLUSPLUSFLAGS!": [ "-O3", "-Os", "-DDEBUG" ], 12 | "GCC_OPTIMIZATION_LEVEL": "0", 13 | "GCC_GENERATE_DEBUGGING_SYMBOLS": "YES" 14 | } 15 | }, 16 | "Release": { 17 | "defines": [ "NDEBUG" ], 18 | "xcode_settings": { 19 | "OTHER_CPLUSPLUSFLAGS!": [ "-Os", "-std=gnu++0x", "-O2" ], 20 | "GCC_OPTIMIZATION_LEVEL": "3", 21 | "GCC_GENERATE_DEBUGGING_SYMBOLS": "NO", 22 | "DEAD_CODE_STRIPPING": "YES", 23 | "GCC_INLINES_ARE_PRIVATE_EXTERN": "YES" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sweepjs/sweepjs.h: -------------------------------------------------------------------------------- 1 | #ifndef SWEEPJS_3153F5A5574F_H 2 | #define SWEEPJS_3153F5A5574F_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | class Sweep final : public Nan::ObjectWrap { 11 | public: 12 | static NAN_MODULE_INIT(Init); 13 | 14 | private: 15 | static NAN_METHOD(New); 16 | 17 | static NAN_METHOD(startScanning); 18 | static NAN_METHOD(stopScanning); 19 | 20 | static NAN_METHOD(scan); 21 | 22 | static NAN_METHOD(getMotorReady); 23 | static NAN_METHOD(getMotorSpeed); 24 | static NAN_METHOD(setMotorSpeed); 25 | 26 | static NAN_METHOD(getSampleRate); 27 | static NAN_METHOD(setSampleRate); 28 | 29 | static NAN_METHOD(reset); 30 | 31 | static Nan::Persistent& constructor(); 32 | 33 | // Wrapped Object 34 | 35 | Sweep(const char* port); 36 | Sweep(const char* port, int32_t bitrate); 37 | 38 | // Non-Copyable 39 | Sweep(const Sweep&) = delete; 40 | Sweep& operator=(const Sweep&) = delete; 41 | 42 | // Non-Moveable 43 | Sweep(Sweep&&) = delete; 44 | Sweep& operator=(Sweep&&) = delete; 45 | 46 | // Ref-counted to keep alive until last in-flight callback is finished 47 | std::shared_ptr<::sweep_device> device; 48 | }; 49 | 50 | NODE_MODULE(sweepjs, Sweep::Init) 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | #### Scope of changes 15 | 20 | 21 | #### Known Limitations 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sweeppy/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os.path 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f: 8 | readme_content = f.read() 9 | 10 | setup(name='sweeppy', 11 | version='1.3.0', 12 | description='Python bindings for libsweep', 13 | long_description=readme_content, 14 | author='Scanse', 15 | url='http://scanse.io', 16 | packages=find_packages(exclude=['tests']), 17 | license='MIT', 18 | classifiers=[ 19 | 'Development Status :: 3 - Alpha', 20 | 21 | 'Intended Audience :: Developers', 22 | 23 | 'License :: OSI Approved :: MIT License', 24 | 25 | 'Programming Language :: Python :: 2', 26 | 'Programming Language :: Python :: 2.6', 27 | 'Programming Language :: Python :: 2.7', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.2', 30 | 'Programming Language :: Python :: 3.3', 31 | 'Programming Language :: Python :: 3.4', 32 | 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: 3.6', 34 | 35 | 'Operating System :: Unix', 36 | ], 37 | keywords='libsweep sweep sweeppy scanse') 38 | -------------------------------------------------------------------------------- /libsweep/examples/example.cc: -------------------------------------------------------------------------------- 1 | // Make use of the CMake build system or compile manually, e.g. with: 2 | // g++ -std=c++11 example.cc -lsweep 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | int main(int argc, char* argv[]) try { 10 | if (argc != 2) { 11 | std::cerr << "Usage: ./example-c++ device\n"; 12 | return EXIT_FAILURE; 13 | } 14 | 15 | std::cout << "Constructing sweep device..." << std::endl; 16 | sweep::sweep device{argv[1]}; 17 | 18 | std::cout << "Motor Speed Setting: " << device.get_motor_speed() << " Hz" << std::endl; 19 | std::cout << "Sample Rate Setting: " << device.get_sample_rate() << " Hz" << std::endl; 20 | 21 | std::cout << "Beginning data acquisition as soon as motor speed stabilizes..." << std::endl; 22 | device.start_scanning(); 23 | 24 | for (auto n = 0; n < 10; ++n) { 25 | const sweep::scan scan = device.get_scan(); 26 | 27 | std::cout << "Scan #" << n << ":" << std::endl; 28 | for (const sweep::sample& sample : scan.samples) { 29 | std::cout << "angle " << sample.angle << " distance " << sample.distance << " strength " << sample.signal_strength << "\n"; 30 | } 31 | } 32 | 33 | device.stop_scanning(); 34 | } catch (const sweep::device_error& e) { 35 | std::cerr << "Error: " << e.what() << std::endl; 36 | } 37 | -------------------------------------------------------------------------------- /docker/install-yum.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | EPEL_RPM_HASH=0dcc89f9bf67a2a515bad64569b7a9615edc5e018f676a578d5fd0f17d3c81d4 6 | DEVTOOLS_HASH=a8ebeb4bed624700f727179e6ef771dafe47651131a00a78b342251415646acc 7 | 8 | # CentOS 5 is only available through vault. 9 | sed -i 's/enabled=1/enabled=0/' /etc/yum/pluginconf.d/fastestmirror.conf 10 | sed -i 's/mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo 11 | sed -i 's|#\(baseurl.*\)mirror.centos.org/centos/$releasever|\1vault.centos.org/5.11|' /etc/yum.repos.d/*.repo 12 | sed -i 's|#\(baseurl.*\)download.fedoraproject.org/pub|\1archives.fedoraproject.org/pub/archive|' /etc/yum.repos.d/*.repo 13 | 14 | MY_DIR=$(dirname "${BASH_SOURCE[0]}") 15 | source "$MY_DIR/build-utils.sh" 16 | 17 | # EPEL support 18 | yum -y install wget curl 19 | curl -sLO https://dl.fedoraproject.org/pub/archive/epel/5/x86_64/epel-release-5-4.noarch.rpm 20 | check_sha256sum epel-release-5-4.noarch.rpm $EPEL_RPM_HASH 21 | 22 | # Dev toolset (for LLVM and other projects requiring C++11 support) 23 | curl -sLO http://people.centos.org/tru/devtools-2/devtools-2.repo 24 | check_sha256sum devtools-2.repo $DEVTOOLS_HASH 25 | mv devtools-2.repo /etc/yum.repos.d/devtools-2.repo 26 | rpm -Uvh --replacepkgs epel-release-5*.rpm 27 | rm -f epel-release-5*.rpm 28 | 29 | yum install -y java-1.7.0-openjdk java-1.7.0-openjdk-devel 30 | -------------------------------------------------------------------------------- /sweepjs/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ "common.gypi" ], 3 | "targets": [ 4 | { 5 | "target_name": "sweepjs", 6 | "sources": [ "sweepjs.cc" ], 7 | "xcode_settings": { 8 | "GCC_ENABLE_CPP_RTTI": "YES", 9 | "GCC_ENABLE_CPP_EXCEPTIONS": "YES", 10 | "MACOSX_DEPLOYMENT_TARGET": "10.8", 11 | "CLANG_CXX_LIBRARY": "libc++", 12 | "CLANG_CXX_LANGUAGE_STANDARD": "c++11", 13 | "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0" 14 | }, 15 | "conditions": [ 16 | ["OS != 'win'",{ 17 | "include_dirs": [">> mkdir -p build' 12 | def buildDir = project.mkdir '../libsweep/build' 13 | println '>>> cd ' + buildDir 14 | 15 | def dummy = inputs.properties.dummy ? 'On': 'Off' 16 | println '>>> cmake .. -DCMAKE_BUILD_TYPE=Release' 17 | (project.exec { 18 | workingDir buildDir 19 | commandLine 'cmake', file('../libsweep').absolutePath, '-DCMAKE_BUILD_TYPE=Release', "-DDUMMY=${dummy}" 20 | }).assertNormalExitValue().rethrowFailure() 21 | 22 | println '>>> cmake --build .' 23 | (project.exec { 24 | workingDir buildDir 25 | commandLine 'cmake', '--build', '.' 26 | }).assertNormalExitValue().rethrowFailure() 27 | } 28 | } 29 | task libSweepJar(type: Jar, dependsOn: [buildLibSweep], group: 'build') { 30 | description = 'Assembles a jar archive containing the libsweep.so file.' 31 | from buildLibSweep.outputs 32 | into 'linux-x86-64' 33 | classifier = 'natives' 34 | } 35 | artifacts { 36 | archives project.libSweepJar 37 | } 38 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Help 2 | The [issues](https://github.com/scanse/sweep-sdk/issues) section is for bug reports and feature requests. If you need help with a project, please use the [community](http://community.scanse.io/) forum. 3 | 4 | --- 5 | # Bugs 6 | #### Before reporting a bug 7 | 8 | 1. Search [issue tracker](https://github.com/scanse/sweep-sdk/issues) for similar issues. 9 | 2. Make sure you are using the latest version of the library and that you have thoroughly reviewed all provided documentation. 10 | 11 | #### How to report a bug 12 | 13 | 1. Specify the version of the library in use when the bug occurred. 14 | 2. Specify your operating system, platform and any other details you think might be relevant. 15 | 3. Describe the problem in detail. Explain what happened, and what you expected would happen. 16 | 4. If possible, provide a small code-snippet or test-case that reproduces the issue. 17 | 5. If it makes things more clear, include an annotated screenshot. 18 | 19 | --- 20 | # Contribution 21 | We encourage community contribution! We will work hard to integrate valuable changes, improvements and features. To make the process more efficient, here are the general steps to contribute. 22 | 23 | #### How to contribute 24 | 25 | 1. Login or create a GitHub account. 26 | 2. Fork the repository on GitHub. 27 | 3. Make changes to your fork of the repository. 28 | 4. Check the [Contribution Guidelines](PULL_REQUEST_TEMPLATE.md) for pull requests and make sure your modifications adhere to the guidelines. 29 | 5. Submit your modifications as a new pull request [here](https://github.com/scanse/sweep-sdk/pulls). -------------------------------------------------------------------------------- /libsweep/src/sweep-ctl.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | static const auto kMotorSpeedCmd = "motor_speed"; 11 | static const auto kSampleRateCmd = "sample_rate"; 12 | 13 | static void usage() { 14 | std::fprintf(stderr, "Usage:\n"); 15 | std::fprintf(stderr, " sweep-ctl dev get (motor_speed|sample_rate)\n"); 16 | std::fprintf(stderr, " sweep-ctl dev set (motor_speed|sample_rate) \n"); 17 | std::exit(EXIT_FAILURE); 18 | } 19 | 20 | int main(int argc, char** argv) try { 21 | std::vector args{argv, argv + argc}; 22 | 23 | const auto get = args.size() == 4 && args[2] == "get"; 24 | const auto set = args.size() == 5 && args[2] == "set"; 25 | 26 | if (!get && !set) 27 | usage(); 28 | 29 | const auto& dev = args[1]; 30 | const auto& cmd = args[3]; 31 | 32 | sweep::sweep device{dev.c_str(), 115200}; // TODO: simple dev ctor 33 | 34 | if (get && cmd == kMotorSpeedCmd) { 35 | std::printf("%" PRId32 "\n", device.get_motor_speed()); 36 | return EXIT_SUCCESS; 37 | } 38 | 39 | if (get && cmd == kSampleRateCmd) { 40 | std::printf("%" PRId32 "\n", device.get_sample_rate()); 41 | return EXIT_SUCCESS; 42 | } 43 | 44 | if (set && cmd == kMotorSpeedCmd) { 45 | device.set_motor_speed(std::stoi(args[4])); 46 | std::printf("%" PRId32 "\n", device.get_motor_speed()); 47 | return EXIT_SUCCESS; 48 | } 49 | 50 | if (set && cmd == kSampleRateCmd) { 51 | device.set_sample_rate(std::stoi(args[4])); 52 | std::printf("%" PRId32 "\n", device.get_sample_rate()); 53 | return EXIT_SUCCESS; 54 | } 55 | 56 | usage(); 57 | 58 | } catch (const std::exception& e) { 59 | std::fprintf(stderr, "Error: %s\n", e.what()); 60 | return EXIT_FAILURE; 61 | } 62 | -------------------------------------------------------------------------------- /libsweep/include/queue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_QUEUE_62C8F42E8DD5_HPP 2 | #define SWEEP_QUEUE_62C8F42E8DD5_HPP 3 | 4 | /* 5 | * Thread-safe queue. 6 | * Implementation detail; not exported. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace sweep { 18 | namespace queue { 19 | 20 | template class queue { 21 | public: 22 | queue(int32_t max) : max_size(max) {} 23 | 24 | // Empty the queue 25 | void clear() { 26 | std::unique_lock lock(the_mutex); 27 | while (!the_queue.empty()) { 28 | the_queue.pop(); 29 | } 30 | } 31 | 32 | // Add an element to the queue. 33 | void enqueue(T v) { 34 | std::lock_guard lock(the_mutex); 35 | 36 | // if necessary, remove the oldest scan to make room for new 37 | if (static_cast(the_queue.size()) >= max_size) 38 | the_queue.pop(); 39 | 40 | the_queue.push(std::move(v)); 41 | the_cond_var.notify_one(); 42 | } 43 | 44 | // If the queue is empty, wait till an element is avaiable. 45 | T dequeue() { 46 | std::unique_lock lock(the_mutex); 47 | // wait until queue is not empty 48 | while (the_queue.empty()) { 49 | // the_cond_var could wake up the thread spontaneously, even if the queue is still empty... 50 | // so put this wakeup inside a while loop, such that the empty check is performed whenever it wakes up 51 | the_cond_var.wait(lock); // release lock as long as the wait and reaquire it afterwards. 52 | } 53 | auto v = std::move(the_queue.front()); 54 | the_queue.pop(); 55 | return v; 56 | } 57 | 58 | private: 59 | int32_t max_size; 60 | std::queue the_queue; 61 | mutable std::mutex the_mutex; 62 | mutable std::condition_variable the_cond_var; 63 | }; 64 | 65 | } // ns queue 66 | } // ns sweep 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /libsweep/examples/README.md: -------------------------------------------------------------------------------- 1 | # Libsweep Examples 2 | 3 | Examples for the `libsweep` library. 4 | 5 | This can be either the dummy library always returning static point cloud data or the device library requiring the Scanse Sweep device to be plugged in. 6 | 7 | ### Quick Start 8 | 9 | #### Linux 10 | 11 | Requires `libsweep.so` be installed. 12 | 13 | ```bash 14 | # build the examples 15 | mkdir build 16 | cd build 17 | cmake .. -DCMAKE_BUILD_TYPE=Release 18 | cmake --build . 19 | ``` 20 | 21 | ```bash 22 | # run the examples (use your device's COM port number) 23 | ./example-c /dev/ttyUSB0 24 | ./example-c++ /dev/ttyUSB0 25 | ``` 26 | 27 | Real-time viewer: 28 | 29 | **Note:** The viewer requires SFML2 to be installed. 30 | 31 | ```bash 32 | ./example-viewer /dev/ttyUSB0 33 | ``` 34 | 35 | Pub-Sub networking example: 36 | 37 | **Note:** The pub-sub networking example requires Protobuf and ZeroMQ to be installed. 38 | 39 | Start a publisher sending out full 360 degree scans via the network (localhost). 40 | Then start some subscribers connecting to the publisher. 41 | 42 | ```bash 43 | ./example-net publisher 44 | ./example-net subscriber 45 | ``` 46 | 47 | 48 | #### Windows 49 | 50 | First make sure that the installation directories for the library and includes have been added to the environment variable `PATH`. 51 | 52 | ```bash 53 | # build the examples 54 | mkdir build 55 | cd build 56 | # Use the appropriate VS for your computer 57 | cmake .. -G "Visual Studio 14 2015 Win64" 58 | cmake --build . --config Release 59 | ``` 60 | 61 | **Note:** in the above command, `Visual Studio 14 2015 Win64` will build x64 (64bit) versions of the examples. This requires that the x64 verison of `libsweep` be installed. If you installed the x86 version, drop the `Win64`. See `libsweep/README` for more info. 62 | 63 | If you do not know the COM port number of your device, check under Device Manager -> Ports (COM & LP). 64 | 65 | ```bash 66 | cd Release 67 | # run the examples (use your device's COM port number) 68 | example-c.exe COM5 69 | example-c++.exe COM5 70 | ``` 71 | 72 | ### License 73 | 74 | Copyright © 2016 Daniel J. Hofmann 75 | 76 | Distributed under the MIT License (MIT). 77 | -------------------------------------------------------------------------------- /sweepjs/README.md: -------------------------------------------------------------------------------- 1 | # SweepJs 2 | 3 | NodeJS Scanse Sweep LiDAR library. 4 | 5 | Requires `libsweep.so` to be installed. 6 | 7 | ### Quick Start 8 | 9 | On Linux: 10 | 11 | ```bash 12 | npm install 13 | npm test 14 | ``` 15 | 16 | See the [examples](examples) directory for an example streaming data from the device to the browser in real-time using a Websocket server. 17 | 18 | On Windows: 19 | 20 | The verison (x86 or x64) of the installed libsweep library must match the installed version of node-gyp. The provided `binding.gyp` file supports the default installation directories shown below. 21 | 22 | | Files | Installation Directory (x64) | Installation Directory (x86) | 23 | | ------------ | :--------------------------------------: | :--------------------------------------------: | 24 | | header files | C:/Program Files/sweep/includes/sweep/ | C:/Program Files (x86)/sweep/includes/sweep/ | 25 | | lib files | C:/Program Files/sweep/lib/ | C:/Program Files (x86)/sweep/lib/ | 26 | 27 | If your installation path differs, modify the `binding.gyp` file. 28 | 29 | Install module and run tests with your device's portname. For a device on com port 5: 30 | 31 | ```bash 32 | npm install 33 | node index COM5 34 | ``` 35 | 36 | 37 | ### Interface 38 | 39 | ```javascript 40 | sweep = new Sweep('/dev/ttyUSB0'); 41 | 42 | sweep.startScanning(); 43 | sweep.stopScanning(); 44 | 45 | // true if device is ready (calibration routine complete + motor speed stabilized) 46 | ready = sweep.getMotorReady(); 47 | // integer value between 0:10 (in HZ) 48 | speed = sweep.getMotorSpeed(); 49 | // integer value between 0:10 (in HZ) 50 | sweep.setMotorSpeed(Number); 51 | 52 | // integer value, either 500, 750 or 1000 (in HZ) 53 | rate = sweep.getSampleRate(); 54 | // integer value, either 500, 750 or 1000 (in HZ) 55 | sweep.setSampleRate(Number); 56 | 57 | sweep.scan(function (err, samples) { 58 | handle(err); 59 | 60 | samples.forEach(function (sample) { 61 | use(sample.angle, sample.distance, sample.signal); 62 | }); 63 | }); 64 | 65 | sweep.reset(); 66 | ``` 67 | 68 | ### License 69 | 70 | Copyright © 2016 Daniel J. Hofmann 71 | 72 | Distributed under the MIT License (MIT). 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## v1.3.0 5 | This release is compatible with device firmware v1.4. 6 | - libsweep: 7 | - Added FreeBSD support, thanks to [Hyun Hwang](https://github.com/hyunh90) 8 | - jsweep: 9 | - Added new Java bindings `jsweep`, thanks to [Kenzie Togami](https://github.com/kenzierocks) 10 | 11 | 12 | ## v1.2.3 13 | This release is compatible with device firmware v1.4. 14 | 15 | Changes: 16 | - libsweep: 17 | - Fixed bug in serial impl. on Linux causing device creation to hang on some systems 18 | 19 | ## v1.2.2 20 | This release is compatible with device firmware v1.4. 21 | 22 | Changes: 23 | - libsweep: 24 | - Fixed a bug causing sweep object to hang during device creation. 25 | - Refactor libsweep internals 26 | 27 | ## v1.2.1 28 | This release is compatible with device firmware v1.4. 29 | 30 | Changes: 31 | - libsweep: 32 | - Fixed c++ interface, to resolve linker errors when header is included in multiple translation units. 33 | 34 | ## v1.2.0 35 | This release is compatible with device firmware v1.4. 36 | 37 | Changes: 38 | - libsweep: 39 | - Mac OS support 40 | - Propagate dynamic type of exception from background thread 41 | - sweeppy 42 | - Added patch version (ie: MAJOR.MINOR.PATCH) 43 | 44 | ## v1.1.2 45 | 46 | This release is compatible with device firmware v1.4. 47 | 48 | Changes: 49 | - libsweep: 50 | - Adapts device construction to be compatible with RaspberryPi 51 | - Removes implementation methods from API 52 | - Propagates errors from background thread when accumulating scans 53 | 54 | ## v1.1.1 55 | 56 | This release is compatible with device firmware v1.2. 57 | 58 | Changes: 59 | - libsweep: Adapts motor calibration timeout to firmware changes 60 | 61 | 62 | ## v1.1.0 63 | 64 | This release is compatible with device firmware v1.1. 65 | 66 | Changes: 67 | - libsweep: Adaptes protocol implementation to firmware changes 68 | - libsweep: Accumulates scans in a background worker thread 69 | 70 | 71 | ## v0.1.0 72 | 73 | This is the initial release v0.1.0 and is compatible with the Sweep device firmware version v1.0. 74 | 75 | It comes with C99, C++11, Python2 / Python3, and Node.js bindings. 76 | 77 | Some examples we include: 78 | 79 | Real-time point cloud viewer 80 | Pub-Sub networking example 81 | Websocket daemon for device 82 | 83 | Happy scanning! 84 | -------------------------------------------------------------------------------- /jsweep/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/jna/SweepJNA.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep.jna; 2 | 3 | import com.sun.jna.Native; 4 | 5 | public class SweepJNA { 6 | 7 | static { 8 | Native.register("sweep"); 9 | } 10 | 11 | public static native int sweep_get_version(); 12 | 13 | public static native boolean sweep_is_abi_compatible(); 14 | 15 | public static native String sweep_error_message(ErrorJNAPointer error); 16 | 17 | public static native void sweep_error_destruct(ErrorJNAPointer error); 18 | 19 | public static native DeviceJNAPointer sweep_device_construct_simple(String port, 20 | ErrorReturnJNA error); 21 | 22 | public static native DeviceJNAPointer sweep_device_construct(String port, 23 | int bitrate, ErrorReturnJNA error); 24 | 25 | public static native void sweep_device_destruct(DeviceJNAPointer device); 26 | 27 | public static native void sweep_device_start_scanning( 28 | DeviceJNAPointer device, 29 | ErrorReturnJNA error); 30 | 31 | public static native void sweep_device_stop_scanning( 32 | DeviceJNAPointer device, 33 | ErrorReturnJNA error); 34 | 35 | public static native boolean sweep_device_get_motor_ready( 36 | DeviceJNAPointer device, 37 | ErrorReturnJNA error); 38 | 39 | public static native ScanJNAPointer sweep_device_get_scan(DeviceJNAPointer device, 40 | ErrorReturnJNA error); 41 | 42 | public static native void sweep_scan_destruct(ScanJNAPointer scan); 43 | 44 | public static native int 45 | sweep_scan_get_number_of_samples(ScanJNAPointer scan); 46 | 47 | public static native int sweep_scan_get_angle(ScanJNAPointer scan, 48 | int sample); 49 | 50 | public static native int sweep_scan_get_distance(ScanJNAPointer scan, 51 | int sample); 52 | 53 | public static native int sweep_scan_get_signal_strength(ScanJNAPointer scan, 54 | int sample); 55 | 56 | public static native int sweep_device_get_motor_speed( 57 | DeviceJNAPointer device, 58 | ErrorReturnJNA error); 59 | 60 | public static native void sweep_device_set_motor_speed( 61 | DeviceJNAPointer device, 62 | int hz, ErrorReturnJNA error); 63 | 64 | public static native int sweep_device_get_sample_rate( 65 | DeviceJNAPointer device, 66 | ErrorReturnJNA error); 67 | 68 | public static native void sweep_device_set_sample_rate( 69 | DeviceJNAPointer device, 70 | int hz, ErrorReturnJNA error); 71 | 72 | public static native void sweep_device_reset(DeviceJNAPointer device, 73 | ErrorReturnJNA error); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /libsweep/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.11) 2 | project(sweep-examples C CXX) 3 | 4 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -pedantic") 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -pedantic") 7 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 8 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Weverything") 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Weverything") 10 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 11 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -pedantic") 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -pedantic") 13 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 14 | # Turn on the ability to create folders to organize projects (.vcproj) 15 | # It creates "CMakePredefinedTargets" folder by default and adds CMake 16 | # defined projects like INSTALL.vcproj and ZERO_CHECK.vcproj 17 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 18 | else() 19 | message(FATAL_ERROR "System not yet supported. Please open a ticket.") 20 | endif() 21 | 22 | find_package(Threads REQUIRED) 23 | find_package(Sweep REQUIRED) 24 | 25 | add_executable(example-c++ example.cc) 26 | target_link_libraries(example-c++ PRIVATE ${LIBSWEEP_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) 27 | target_include_directories(example-c++ SYSTEM PRIVATE ${LIBSWEEP_INCLUDE_DIR}) 28 | 29 | add_executable(example-c example.c) 30 | target_link_libraries(example-c PRIVATE ${LIBSWEEP_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) 31 | target_include_directories(example-c SYSTEM PRIVATE ${LIBSWEEP_INCLUDE_DIR}) 32 | 33 | 34 | # Optional SFML2 based viewer 35 | include(FindPkgConfig) 36 | pkg_check_modules(LIBSFML sfml-all) 37 | 38 | if (LIBSFML_FOUND) 39 | add_executable(example-viewer viewer.cc) 40 | target_link_libraries(example-viewer PRIVATE ${LIBSWEEP_LIBRARY} ${LIBSFML_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 41 | target_include_directories(example-viewer SYSTEM PRIVATE ${LIBSWEEP_INCLUDE_DIR} ${LIBSFML_INCLUDE_DIRS}) 42 | else() 43 | message(STATUS "SFML2 required for real-time viewer (libsfml-dev)") 44 | endif() 45 | 46 | 47 | # Optional Protobuf + ZeroMQ networking example 48 | find_package(Protobuf 2.4.1) 49 | pkg_check_modules(LIBZMQ libzmq) 50 | 51 | if (PROTOBUF_FOUND AND LIBZMQ_FOUND) 52 | protobuf_generate_cpp(ProtoSources ProtoHeaders net.proto) 53 | add_executable(example-net net.cc ${ProtoSources} ${ProtoHeaders}) 54 | target_link_libraries(example-net PRIVATE ${LIBSWEEP_LIBRARY} ${PROTOBUF_LITE_LIBRARIES} ${LIBZMQ_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 55 | target_include_directories(example-net SYSTEM PRIVATE ${LIBSWEEP_INCLUDE_DIR} ${PROTOBUF_INCLUDE_DIRS} ${LIBZMQ_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 56 | else() 57 | message(STATUS "Protobuf2 and ZeroMQ required for networking example (libprotobuf-dev protobuf-compiler libzmq-dev)") 58 | endif() 59 | -------------------------------------------------------------------------------- /sweeppy/sweeppy/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | import queue 6 | import threading 7 | 8 | from sweeppy import Sweep 9 | 10 | 11 | # Below we create three worker threads: 12 | # - The producer thread continuously putting scans into an unbounded queue 13 | # - The consumer thread continuously pulling scans out of an unbounded queue 14 | # - The timer thread setting a stop event after a few seconds 15 | # 16 | # A few things you probably want to look out for: 17 | # - Make the queue bounded or make sure you can consume faster than you can 18 | # produce otherwise memory usage will grow over time. 19 | # - If you make the queue bounded look into queue `put` and `get` functions 20 | # and their blocking behavior. You probably want a ringbuffer semantic. 21 | 22 | 23 | 24 | class Scanner(threading.Thread): 25 | def __init__(self, dev, queue, done): 26 | super().__init__() 27 | self.dev = dev 28 | self.queue = queue 29 | self.done = done 30 | 31 | # Iterate over an infinite scan generator and only stop 32 | # if we got asked to do so. In that case put a sentinel 33 | # value into the queue signaling the consumer to stop. 34 | def run(self): 35 | with Sweep(self.dev) as sweep: 36 | sweep.start_scanning() 37 | 38 | for scan in sweep.get_scans(): 39 | if self.done.is_set(): 40 | self.queue.put_nowait(None) 41 | break 42 | else: 43 | self.queue.put_nowait(scan) 44 | 45 | sweep.stop_scanning() 46 | 47 | 48 | class SampleCounter(threading.Thread): 49 | def __init__(self, queue): 50 | super().__init__() 51 | self.queue = queue 52 | 53 | # Iterate over the queue's scans blocking if the queue is 54 | # empty. If we see the producer's sentinel value we exit. 55 | # 56 | # We can not use the same stop event for both producer and 57 | # consumer because of the following potential scenario: 58 | # - the queue is empty (we block in `get`) 59 | # - the producer sees the event and stops 60 | # - the consumer waits forever 61 | def run(self): 62 | while True: 63 | scan = self.queue.get() 64 | 65 | if not scan: 66 | break 67 | 68 | print(len(scan.samples)) 69 | self.queue.task_done() 70 | 71 | 72 | def main(): 73 | if len(sys.argv) < 2: 74 | sys.exit('python threading.py /dev/ttyUSB0') 75 | 76 | dev = sys.argv[1] 77 | 78 | done = threading.Event() 79 | timer = threading.Timer(3, lambda: done.set()) 80 | 81 | fifo = queue.Queue() 82 | 83 | scanner = Scanner(dev, fifo, done) 84 | counter = SampleCounter(fifo) 85 | 86 | scanner.start() 87 | counter.start() 88 | timer.start() 89 | 90 | 91 | if __name__ == '__main__': 92 | main() 93 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | environment: 3 | fast_finish: true 4 | install: 5 | - ps: >- 6 | # Make sure building libsweep works 7 | 8 | pushd libsweep 9 | 10 | mkdir build 11 | 12 | cd build 13 | 14 | cmake .. -G "Visual Studio 14 2015" 15 | 16 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 17 | 18 | cmake --build . --config Release 19 | 20 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 21 | 22 | 23 | # Then build and install dummy library to test bindings against without device attached 24 | 25 | cmake .. -DDUMMY=On -G "Visual Studio 14 2015" 26 | 27 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 28 | 29 | cmake --build . --config Release 30 | 31 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 32 | 33 | cmake --build . --target install --config Release 34 | 35 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 36 | 37 | popd 38 | 39 | 40 | # Add the installation dir to the environment variable PATH 41 | 42 | $env:path += ";C:\Program Files (x86)\sweep\lib;C:\Program Files (x86)\sweep\include;C:\Program Files (x86)\sweep\include\sweep" 43 | 44 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 45 | 46 | 47 | # Install node bindings 48 | 49 | pushd sweepjs 50 | 51 | npm install 52 | 53 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 54 | 55 | popd 56 | 57 | 58 | 59 | # Install python bindings 60 | 61 | pushd sweeppy 62 | 63 | python setup.py install 64 | 65 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 66 | 67 | popd 68 | build_script: 69 | - ps: >- 70 | # Test libsweep examples against the dummy library 71 | 72 | pushd libsweep/examples 73 | 74 | mkdir build 75 | 76 | cd build 77 | 78 | cmake .. -G "Visual Studio 14 2015" 79 | 80 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 81 | 82 | cmake --build . --config Release 83 | 84 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 85 | 86 | cd Release 87 | 88 | ./example-c PLACEHOLDER 89 | 90 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 91 | 92 | ./example-c++ PLACEHOLDER 93 | 94 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 95 | 96 | popd 97 | 98 | 99 | # Test SweepPy bindings against the dummy library 100 | 101 | pushd sweeppy 102 | 103 | python -m sweeppy COM0 104 | 105 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 106 | 107 | popd 108 | 109 | 110 | # Test SweepJs bindings against the dummy library 111 | 112 | pushd sweepjs 113 | 114 | node index.js COM0 115 | 116 | if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) } 117 | 118 | popd -------------------------------------------------------------------------------- /libsweep/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 130 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 2 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: true 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 8 88 | UseTab: Never 89 | ... 90 | 91 | -------------------------------------------------------------------------------- /sweepjs/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 130 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ExperimentalAutoDetectBinPacking: false 48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 49 | IncludeCategories: 50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 51 | Priority: 2 52 | - Regex: '^(<|"(gtest|isl|json)/)' 53 | Priority: 3 54 | - Regex: '.*' 55 | Priority: 1 56 | IndentCaseLabels: false 57 | IndentWidth: 2 58 | IndentWrappedFunctionNames: false 59 | KeepEmptyLinesAtTheStartOfBlocks: true 60 | MacroBlockBegin: '' 61 | MacroBlockEnd: '' 62 | MaxEmptyLinesToKeep: 1 63 | NamespaceIndentation: None 64 | ObjCBlockIndentWidth: 2 65 | ObjCSpaceAfterProperty: false 66 | ObjCSpaceBeforeProtocolList: true 67 | PenaltyBreakBeforeFirstCallParameter: 19 68 | PenaltyBreakComment: 300 69 | PenaltyBreakFirstLessLess: 120 70 | PenaltyBreakString: 1000 71 | PenaltyExcessCharacter: 1000000 72 | PenaltyReturnTypeOnItsOwnLine: 60 73 | PointerAlignment: Left 74 | ReflowComments: true 75 | SortIncludes: true 76 | SpaceAfterCStyleCast: false 77 | SpaceBeforeAssignmentOperators: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInContainerLiterals: true 83 | SpacesInCStyleCastParentheses: false 84 | SpacesInParentheses: false 85 | SpacesInSquareBrackets: false 86 | Standard: Cpp11 87 | TabWidth: 8 88 | UseTab: Never 89 | ... 90 | 91 | -------------------------------------------------------------------------------- /libsweep/examples/example.c: -------------------------------------------------------------------------------- 1 | // Make use of the CMake build system or compile manually, e.g. with: 2 | // gcc -std=c99 example.c -lsweep 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | // Utility functions for error handling: we simply shut down here; you should do a better job 13 | static void die(sweep_error_s error) { 14 | assert(error); 15 | fprintf(stderr, "Error: %s\n", sweep_error_message(error)); 16 | sweep_error_destruct(error); 17 | exit(EXIT_FAILURE); 18 | } 19 | 20 | static void check(sweep_error_s error) { 21 | if (error) 22 | die(error); 23 | } 24 | 25 | int main(int argc, char* argv[]) { 26 | if (argc != 2) { 27 | fprintf(stdout, "Usage: ./example-c device\n"); 28 | return EXIT_FAILURE; 29 | } 30 | 31 | // Makes sure the installed library is compatible with the interface 32 | assert(sweep_is_abi_compatible()); 33 | 34 | // Grab the port name from the input argument 35 | const char* port = argv[1]; 36 | 37 | // All functions which can potentially fail write into an error object 38 | sweep_error_s error = NULL; 39 | 40 | // Create a Sweep device from the specified USB serial port; there is a second constructor for advanced usage 41 | sweep_device_s sweep = sweep_device_construct_simple(port, &error); 42 | check(error); 43 | 44 | // The Sweep's rotating speed in Hz 45 | int32_t speed = sweep_device_get_motor_speed(sweep, &error); 46 | check(error); 47 | 48 | fprintf(stdout, "Motor Speed Setting: %" PRId32 " Hz\n", speed); 49 | 50 | // The Sweep's sample rate in Hz 51 | int32_t rate = sweep_device_get_sample_rate(sweep, &error); 52 | check(error); 53 | 54 | fprintf(stdout, "Sample Rate Setting: %" PRId32 " Hz\n", rate); 55 | 56 | // Capture scans 57 | fprintf(stdout, "Beginning data acquisition as soon as motor speed stabilizes...\n"); 58 | sweep_device_start_scanning(sweep, &error); 59 | check(error); 60 | 61 | // Let's do 10 full 360 degree scans 62 | for (int32_t num_scans = 0; num_scans < 10; ++num_scans) { 63 | // This blocks until a full 360 degree scan is available 64 | sweep_scan_s scan = sweep_device_get_scan(sweep, &error); 65 | check(error); 66 | 67 | // For each sample in a full 360 degree scan print angle and distance. 68 | // In case you're doing expensive work here consider using a decoupled producer / consumer pattern. 69 | for (int32_t n = 0; n < sweep_scan_get_number_of_samples(scan); ++n) { 70 | int32_t angle = sweep_scan_get_angle(scan, n); 71 | int32_t distance = sweep_scan_get_distance(scan, n); 72 | int32_t signal = sweep_scan_get_signal_strength(scan, n); 73 | fprintf(stdout, "Angle %" PRId32 ", Distance %" PRId32 ", Signal Strength: %" PRId32 "\n", angle, distance, signal); 74 | } 75 | 76 | // Cleanup scan response 77 | sweep_scan_destruct(scan); 78 | } 79 | 80 | // Stop capturing scans 81 | sweep_device_stop_scanning(sweep, &error); 82 | check(error); 83 | 84 | // Shut down and cleanup Sweep device 85 | sweep_device_destruct(sweep); 86 | return EXIT_SUCCESS; 87 | } 88 | -------------------------------------------------------------------------------- /libsweep/examples/net.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "net.pb.h" 14 | 15 | static void usage() { 16 | std::cout << "Usage: example-net /dev/ttyUSB0 [ publisher | subscriber ]\n"; 17 | std::exit(EXIT_SUCCESS); 18 | } 19 | 20 | void subscriber() { 21 | zmq::context_t ctx{/*io_threads=*/1}; 22 | zmq::socket_t sub{ctx, ZMQ_SUB}; 23 | 24 | sub.connect("tcp://127.0.0.1:5555"); 25 | sub.setsockopt(ZMQ_SUBSCRIBE, "", 0); 26 | 27 | std::cout << "Subscribing." << std::endl; 28 | 29 | for (;;) { 30 | zmq::message_t msg; 31 | 32 | if (!sub.recv(&msg)) 33 | continue; 34 | 35 | sweep::proto::scan in; 36 | in.ParseFromArray(msg.data(), msg.size()); 37 | 38 | const auto n = in.angle_size(); 39 | 40 | for (auto i = 0; i < n; ++i) { 41 | std::cout << "Angle: " << in.angle(i) // 42 | << " Distance: " << in.distance(i) // 43 | << " Signal strength: " << in.signal_strength(i) // 44 | << std::endl; 45 | } 46 | } 47 | } 48 | 49 | void publisher(const std::string& dev) try { 50 | zmq::context_t ctx{/*io_threads=*/1}; 51 | zmq::socket_t pub{ctx, ZMQ_PUB}; 52 | 53 | pub.bind("tcp://127.0.0.1:5555"); 54 | 55 | sweep::sweep device{dev.c_str()}; 56 | // Begins data acquisition as soon as motor speed stabilizes 57 | device.start_scanning(); 58 | 59 | std::cout << "Publishing. Each dot is a full 360 degree scan." << std::endl; 60 | 61 | for (;;) { 62 | const sweep::scan scan = device.get_scan(); 63 | 64 | sweep::proto::scan out; 65 | 66 | for (const sweep::sample& sample : scan.samples) { 67 | out.add_angle(sample.angle); 68 | out.add_distance(sample.distance); 69 | out.add_signal_strength(sample.signal_strength); 70 | } 71 | 72 | auto encoded = out.SerializeAsString(); 73 | 74 | zmq::message_t msg{encoded.size()}; 75 | std::memcpy(msg.data(), encoded.data(), encoded.size()); 76 | 77 | const auto ok = pub.send(msg); 78 | 79 | if (ok) 80 | std::cout << "." << std::flush; 81 | } 82 | 83 | device.stop_scanning(); 84 | 85 | } catch (const sweep::device_error& e) { 86 | std::cerr << "Error: " << e.what() << '\n'; 87 | } 88 | 89 | int main(int argc, char* argv[]) { 90 | std::vector args{argv, argv + argc}; 91 | 92 | if (args.size() != 3) 93 | usage(); 94 | 95 | const auto isPublisher = args[2] == "publisher"; 96 | const auto isSubscriber = args[2] == "subscriber"; 97 | 98 | if (!isPublisher && !isSubscriber) 99 | usage(); 100 | 101 | GOOGLE_PROTOBUF_VERIFY_VERSION; 102 | 103 | struct AtExit { 104 | ~AtExit() { ::google::protobuf::ShutdownProtobufLibrary(); } 105 | } sentry; 106 | 107 | if (isPublisher) 108 | publisher(args[1]); 109 | 110 | if (isSubscriber) 111 | subscriber(); 112 | } 113 | -------------------------------------------------------------------------------- /libsweep/include/sweep/sweep.h: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_6144CCE8BA67_H 2 | #define SWEEP_6144CCE8BA67_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #if defined _WIN32 || defined __CYGWIN__ || defined __MINGW32__ 14 | // If we are building a dll, set SWEEP_API to export symbols 15 | #ifdef SWEEP_EXPORTS 16 | #ifdef __GNUC__ 17 | #define SWEEP_API __attribute__((dllexport)) 18 | #else 19 | #define SWEEP_API __declspec(dllexport) 20 | #endif 21 | #else 22 | #ifdef __GNUC__ 23 | #define SWEEP_API __attribute__((dllimport)) 24 | #else 25 | #define SWEEP_API __declspec(dllimport) 26 | #endif 27 | #endif 28 | #else 29 | #if __GNUC__ >= 4 30 | #define SWEEP_API __attribute__((visibility("default"))) 31 | #else 32 | #define SWEEP_API 33 | #endif 34 | #endif 35 | 36 | #ifndef SWEEP_ASSERT 37 | #include 38 | #define SWEEP_ASSERT(x) assert(x) 39 | #endif 40 | 41 | SWEEP_API int32_t sweep_get_version(void); 42 | SWEEP_API bool sweep_is_abi_compatible(void); 43 | 44 | typedef struct sweep_error* sweep_error_s; 45 | typedef struct sweep_device* sweep_device_s; 46 | typedef struct sweep_scan* sweep_scan_s; 47 | 48 | SWEEP_API const char* sweep_error_message(sweep_error_s error); 49 | SWEEP_API void sweep_error_destruct(sweep_error_s error); 50 | 51 | SWEEP_API sweep_device_s sweep_device_construct_simple(const char* port, sweep_error_s* error); 52 | SWEEP_API sweep_device_s sweep_device_construct(const char* port, int32_t bitrate, sweep_error_s* error); 53 | SWEEP_API void sweep_device_destruct(sweep_device_s device); 54 | 55 | // Blocks until device is ready to start scanning, then starts scanning 56 | SWEEP_API void sweep_device_start_scanning(sweep_device_s device, sweep_error_s* error); 57 | // Stops stream, blocks while leftover stream is flushed, and sends stop once more to validate response 58 | SWEEP_API void sweep_device_stop_scanning(sweep_device_s device, sweep_error_s* error); 59 | 60 | // Retrieves a scan from the queue (will block until scan is available) 61 | SWEEP_API sweep_scan_s sweep_device_get_scan(sweep_device_s device, sweep_error_s* error); 62 | 63 | SWEEP_API bool sweep_device_get_motor_ready(sweep_device_s device, sweep_error_s* error); 64 | SWEEP_API int32_t sweep_device_get_motor_speed(sweep_device_s device, sweep_error_s* error); 65 | // Blocks until device is ready to adjust motor speed, then adjusts motor speed 66 | SWEEP_API void sweep_device_set_motor_speed(sweep_device_s device, int32_t hz, sweep_error_s* error); 67 | 68 | SWEEP_API int32_t sweep_device_get_sample_rate(sweep_device_s device, sweep_error_s* error); 69 | SWEEP_API void sweep_device_set_sample_rate(sweep_device_s device, int32_t hz, sweep_error_s* error); 70 | 71 | SWEEP_API int32_t sweep_scan_get_number_of_samples(sweep_scan_s scan); 72 | SWEEP_API int32_t sweep_scan_get_angle(sweep_scan_s scan, int32_t sample); 73 | SWEEP_API int32_t sweep_scan_get_distance(sweep_scan_s scan, int32_t sample); 74 | SWEEP_API int32_t sweep_scan_get_signal_strength(sweep_scan_s scan, int32_t sample); 75 | 76 | SWEEP_API void sweep_scan_destruct(sweep_scan_s scan); 77 | 78 | SWEEP_API void sweep_device_reset(sweep_device_s device, sweep_error_s* error); 79 | 80 | #ifdef __cplusplus 81 | } 82 | #endif 83 | 84 | #endif // SWEEP_6144CCE8BA67_H 85 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: required 3 | dist: trusty 4 | 5 | services: 6 | - docker 7 | 8 | matrix: 9 | fast_finish: true 10 | 11 | include: 12 | - os: linux 13 | addons: 14 | apt: 15 | sources: 16 | - ubuntu-toolchain-r-test 17 | - sourceline: 'ppa:webupd8team/java' 18 | packages: ['g++-6', 'cmake', 'python3', 'python3-pip', 'python3-setuptools', 'nodejs', 'npm', 'libsfml-dev', 'libprotobuf-dev', 'protobuf-compiler', 'libzmq-dev'] 19 | env: CCOMPILER='gcc-6' CXXCOMPILER='g++-6' 20 | 21 | - os: linux 22 | addons: 23 | apt: 24 | sources: 25 | - ubuntu-toolchain-r-test 26 | - sourceline: 'ppa:webupd8team/java' 27 | packages: ['g++-5', 'cmake', 'python3', 'python3-pip', 'python3-setuptools', 'nodejs', 'npm', 'libsfml-dev', 'libprotobuf-dev', 'protobuf-compiler', 'libzmq-dev'] 28 | env: CCOMPILER='gcc-5' CXXCOMPILER='g++-5' 29 | 30 | - os: linux 31 | addons: 32 | apt: 33 | sources: 34 | - llvm-toolchain-trusty-3.9 35 | - sourceline: 'ppa:webupd8team/java' 36 | packages: ['clang-3.9', 'cmake', 'python3', 'python3-pip', 'python3-setuptools', 'nodejs', 'npm', 'libsfml-dev', 'libprotobuf-dev', 'protobuf-compiler', 'libzmq-dev'] 37 | env: CCOMPILER='clang-3.9' CXXCOMPILER='clang++-3.9' 38 | 39 | - os: linux 40 | addons: 41 | apt: 42 | sources: 43 | - llvm-toolchain-trusty-4.0 44 | - sourceline: 'ppa:webupd8team/java' 45 | packages: ['clang-4.0', 'cmake', 'python3', 'python3-pip', 'python3-setuptools', 'nodejs', 'npm', 'libsfml-dev', 'libprotobuf-dev', 'protobuf-compiler', 'libzmq-dev'] 46 | env: CCOMPILER='clang-4.0' CXXCOMPILER='clang++-4.0' 47 | 48 | - os: osx 49 | osx_image: xcode8.2 50 | node_js: 4 51 | env: CCOMPILER='clang' CXXCOMPILER='clang++' 52 | 53 | - os: osx 54 | osx_image: xcode8.2 55 | node_js: 6 56 | env: CCOMPILER='clang' CXXCOMPILER='clang++' 57 | 58 | before_install: 59 | - export CC=${CCOMPILER} CXX=${CXXCOMPILER} 60 | - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10; fi 61 | - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then brew update && brew install python3 && brew cask install java; fi 62 | 63 | install: 64 | # Make sure building libsweep works 65 | - pushd libsweep 66 | - mkdir -p build 67 | - cd build 68 | - cmake .. -DCMAKE_BUILD_TYPE=Debug 69 | - cmake --build . 70 | # Then build and install dummy library to test bindings against without device attached 71 | - cmake .. -DCMAKE_BUILD_TYPE=Debug -DDUMMY=On 72 | - cmake --build . 73 | - sudo cmake --build . --target install 74 | - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then sudo ldconfig; fi 75 | - popd 76 | # Install Python bindings 77 | - pushd sweeppy 78 | - sudo python2 setup.py install 79 | - sudo python3 setup.py install 80 | - popd 81 | # Build Java Bindings 82 | - ./travis/java-run-task.sh assemble 83 | 84 | script: 85 | # Test libsweep examples against the dummy library 86 | - pushd libsweep/examples 87 | - mkdir build 88 | - cd build 89 | - cmake .. 90 | - cmake --build . 91 | - ./example-c /dev/ttyUSB0 92 | - ./example-c++ /dev/ttyUSB0 93 | - popd 94 | # Test SweepPy bindings against the dummy library 95 | - pushd sweeppy 96 | - python2 -m sweeppy /dev/ttyUSB0 97 | - python3 -m sweeppy /dev/ttyUSB0 98 | - popd 99 | # Test SweepJs bindings against the dummy library 100 | - pushd sweepjs 101 | - npm install 102 | - popd 103 | # Test JSweep bindings against the dummy library 104 | - ./travis/java-run-task.sh check 105 | -------------------------------------------------------------------------------- /libsweep/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.11) 2 | project(sweep C CXX) 3 | 4 | 5 | # Major for breaking ABI changes. Minor for features. Patch for bugfixes. 6 | 7 | set(SWEEP_VERSION_MAJOR 1) 8 | set(SWEEP_VERSION_MINOR 3) 9 | set(SWEEP_VERSION_PATCH 0) 10 | 11 | 12 | option(DUMMY "Build dummy libsweep always returning static point cloud data. No device needed." OFF) 13 | 14 | 15 | # Platform specific compiler and linker options. 16 | 17 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -pedantic -fvisibility=hidden -fPIC -fno-rtti") 19 | set(libsweep_OS unix) 20 | 21 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fvisibility=hidden -fPIC -fno-rtti") 23 | set(libsweep_OS unix) 24 | 25 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -pedantic -fvisibility=hidden -fPIC -fno-rtti") 27 | set(libsweep_OS unix) 28 | 29 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 30 | set(libsweep_OS win) 31 | 32 | # Turn on the ability to create folders to organize projects (.vcproj) 33 | # It creates "CMakePredefinedTargets" folder by default and adds CMake 34 | # defined projects like INSTALL.vcproj and ZERO_CHECK.vcproj to that folder 35 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 36 | 37 | # define SWEEP_EXPORTS such that sweep.h will export symbols marked by SWEEP_API 38 | add_definitions(-DSWEEP_EXPORTS) 39 | 40 | else() 41 | message(FATAL_ERROR "System not yet supported. Please open a ticket.") 42 | 43 | endif() 44 | 45 | 46 | # Generate include/sweep/config.h on the fly and insert version numbers. 47 | 48 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.h.in 49 | ${CMAKE_CURRENT_BINARY_DIR}/include/sweep/config.h) 50 | 51 | 52 | # C++11 requires us to link in pthreads and equivalents. 53 | 54 | find_package(Threads REQUIRED) 55 | 56 | 57 | # libsweep target. 58 | 59 | file(GLOB libsweep_OS_SOURCES src/${libsweep_OS}/*.cc) 60 | 61 | if (DUMMY) 62 | set(libsweep_IMPL_SOURCES src/dummy.cc) 63 | else() 64 | set(libsweep_IMPL_SOURCES src/sweep.cc) 65 | endif() 66 | 67 | set(libsweep_SOURCES ${libsweep_OS_SOURCES} ${libsweep_IMPL_SOURCES} src/protocol.cc) 68 | file(GLOB libsweep_HEADERS include/*.h include/sweep/*.h include/sweep/*.hpp) 69 | 70 | add_library(sweep SHARED ${libsweep_SOURCES} ${libsweep_HEADERS}) 71 | target_include_directories(sweep PRIVATE include include/sweep ${CMAKE_CURRENT_BINARY_DIR}/include) 72 | target_link_libraries(sweep ${CMAKE_THREAD_LIBS_INIT}) 73 | 74 | set_property(TARGET sweep PROPERTY VERSION "${SWEEP_VERSION_MAJOR}.${SWEEP_VERSION_MINOR}.${SWEEP_VERSION_PATCH}") 75 | set_property(TARGET sweep PROPERTY SOVERSION "${SWEEP_VERSION_MAJOR}") 76 | 77 | install(TARGETS sweep DESTINATION lib) 78 | install(DIRECTORY include/sweep DESTINATION include) 79 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/sweep/config.h DESTINATION include/sweep) 80 | 81 | 82 | # sweep-ctl target. 83 | 84 | 85 | add_executable(sweep-ctl src/sweep-ctl.cc) 86 | target_include_directories(sweep-ctl PRIVATE include include/sweep ${CMAKE_CURRENT_BINARY_DIR}/include) 87 | target_link_libraries(sweep-ctl sweep ${CMAKE_THREAD_LIBS_INIT}) 88 | 89 | include(GNUInstallDirs) 90 | install(TARGETS sweep-ctl DESTINATION bin) 91 | install(FILES man/sweep-ctl.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) 92 | 93 | # Make FindPackage(Sweep) work for CMake users. 94 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/SweepConfig.cmake DESTINATION lib/cmake/sweep) 95 | 96 | 97 | # make uninstall target. 98 | 99 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeUninstall.cmake.in" 100 | "${CMAKE_CURRENT_BINARY_DIR}/cmake/CMakeUninstall.cmake" 101 | IMMEDIATE @ONLY) 102 | 103 | add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/CMakeUninstall.cmake) 104 | -------------------------------------------------------------------------------- /sweeppy/README.md: -------------------------------------------------------------------------------- 1 | # SweepPy 2 | 3 | Python Scanse Sweep LiDAR library. Work with Python2 and Python3. 4 | 5 | Requires `libsweep.so` to be installed. 6 | 7 | ### Installation 8 | 9 | Install `sweeppy` module for Python3 locally: 10 | 11 | ```bash 12 | python3 setup.py install --user 13 | ``` 14 | 15 | ### Example for testing 16 | 17 | In the following, replace `/dev/ttyUSB0` with your device's port name. This executes [`__main__.py`](sweeppy/__main__.py) (also works without the installation step). 18 | 19 | ```bash 20 | python -m sweeppy /dev/ttyUSB0 21 | ``` 22 | 23 | See [`app.py`](sweeppy/app.py) for the common use-case where you have to coordinate between scanning and your app's work: 24 | - we start a producer thread putting scans from the Sweep device into a shared queue (non-blocking) 25 | - we start a consumer thread pulling scans out of the shared queue and doing work on them (blocking) 26 | - we use a third thread to set off a stop event and cleanly shutdown the pipeline 27 | 28 | ### Windows: 29 | 30 | The installed sweep library architecture must match the python version. Ie: if you are using a x86 (32bit) version of python, you must install the x86 (32bit) verison of libsweep. 31 | 32 | ```bash 33 | python.exe setup.py install --user 34 | ``` 35 | 36 | In the following: replace `COM5` with your device's port name (check "Device Manager -> COM Ports"). 37 | 38 | ```bash 39 | python.exe -m sweeppy COM5 40 | ``` 41 | 42 | ### Quick Start 43 | 44 | ```python 45 | from sweeppy import Sweep 46 | 47 | with Sweep('/dev/ttyUSB0') as sweep: 48 | sweep.start_scanning() 49 | 50 | for scan in sweep.get_scans(): 51 | print('{}\n'.format(scan)) 52 | ``` 53 | 54 | Note: `Sweep` objects need to be scoped using the `with` statement for resource management. 55 | 56 | See [sweeppy.py](sweeppy/__init__.py) for interface and [example](sweeppy/__main__.py) for example usage. 57 | 58 | 59 | 60 | 61 | ### Interface 62 | 63 | ``` 64 | class Sweep: 65 | def __init__(self, port, bitrate = None) -> Sweep 66 | 67 | def start_scanning(self) -> None 68 | def stop_scanning(self) -> None 69 | 70 | def get_motor_ready(self) -> bool 71 | def get_motor_speed(self) -> int (Hz) 72 | def set_motor_speed(self, speed) -> None 73 | 74 | def get_sample_rate(self) -> int (Hz) 75 | def set_sample_rate(self, speed) -> None 76 | 77 | def get_scans(self) -> Iterable[Scan] 78 | 79 | def reset(self) -> None 80 | 81 | class Scan: 82 | self.samples -> Sample 83 | 84 | class Sample: 85 | self.angle -> int (milli-degree) 86 | self.distance -> int (cm) 87 | self.signal_strength -> int ([0:255]) 88 | ``` 89 | 90 | See the [libsweep README](https://github.com/scanse/sweep-sdk/tree/master/libsweep#device-interaction) for a description of how to use a related interface. 91 | 92 | Additionally, it is recommended that you read through the sweep [Theory of Operation](https://support.scanse.io/hc/en-us/articles/115006333327-Theory-of-Operation) and [Best Practices](https://support.scanse.io/hc/en-us/articles/115006055388-Best-Practices). 93 | 94 | ### Interpret Sample 95 | ```python 96 | sample.angle 97 | ``` 98 | The sample's angle (azimuth) represents the rotation of the sensor when the range measurment was taken. The value is reported in milli-degrees, or 1/1000 of a degree. For example, a `sample.angle` value of `180000 milli-degrees` equates to `180 degrees` (half of a complete rotation). Successive samples of the same scan will have increasing angles in the range 0-360000. 99 | 100 | ```python 101 | sample.distance 102 | ``` 103 | The sample's distance is the range measurement (in cm). 104 | 105 | ```python 106 | sample.distance 107 | ``` 108 | The sample's signal strength is the strength or confidence of the range measurement. The value is reported in the range 0-255, where larger values are better. 109 | 110 | 111 | ### License 112 | 113 | Copyright © 2016 Daniel J. Hofmann 114 | 115 | Distributed under the MIT License (MIT). 116 | -------------------------------------------------------------------------------- /libsweep/include/sweep/sweep.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_DC649F4E94D3_HPP 2 | #define SWEEP_DC649F4E94D3_HPP 3 | 4 | /* 5 | * C++ Wrapper around the low-level primitives. 6 | * Automatically handles resource management. 7 | * 8 | * sweep::sweep - device to interact with 9 | * sweep::scan - a full scan returned by the device 10 | * sweep::sample - a single sample in a full scan 11 | * 12 | * On error sweep::device_error gets thrown. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | namespace sweep { 23 | 24 | // Error reporting 25 | 26 | struct device_error final : std::runtime_error { 27 | using base = std::runtime_error; 28 | using base::base; 29 | }; 30 | 31 | // Interface 32 | 33 | struct sample { 34 | std::int32_t angle; 35 | std::int32_t distance; 36 | std::int32_t signal_strength; 37 | }; 38 | 39 | struct scan { 40 | std::vector samples; 41 | }; 42 | 43 | class sweep { 44 | public: 45 | sweep(const char* port); 46 | sweep(const char* port, std::int32_t bitrate); 47 | void start_scanning(); 48 | void stop_scanning(); 49 | bool get_motor_ready(); 50 | std::int32_t get_motor_speed(); 51 | void set_motor_speed(std::int32_t speed); 52 | std::int32_t get_sample_rate(); 53 | void set_sample_rate(std::int32_t speed); 54 | scan get_scan(); 55 | void reset(); 56 | 57 | private: 58 | std::unique_ptr<::sweep_device, decltype(&::sweep_device_destruct)> device; 59 | }; 60 | 61 | // Implementation 62 | 63 | namespace detail { 64 | struct error_to_exception { 65 | operator ::sweep_error_s*() { return &error; } 66 | 67 | ~error_to_exception() noexcept(false) { 68 | if (error) { 69 | device_error e{::sweep_error_message(error)}; 70 | ::sweep_error_destruct(error); 71 | throw e; 72 | } 73 | } 74 | 75 | ::sweep_error_s error = nullptr; 76 | }; 77 | } // namespace detail 78 | 79 | inline sweep::sweep(const char* port) 80 | : device{::sweep_device_construct_simple(port, detail::error_to_exception{}), &::sweep_device_destruct} {} 81 | 82 | inline sweep::sweep(const char* port, std::int32_t bitrate) 83 | : device{::sweep_device_construct(port, bitrate, detail::error_to_exception{}), &::sweep_device_destruct} {} 84 | 85 | inline void sweep::start_scanning() { ::sweep_device_start_scanning(device.get(), detail::error_to_exception{}); } 86 | 87 | inline void sweep::stop_scanning() { ::sweep_device_stop_scanning(device.get(), detail::error_to_exception{}); } 88 | 89 | inline bool sweep::get_motor_ready() { return ::sweep_device_get_motor_ready(device.get(), detail::error_to_exception{}); } 90 | 91 | inline std::int32_t sweep::get_motor_speed() { 92 | return ::sweep_device_get_motor_speed(device.get(), detail::error_to_exception{}); 93 | } 94 | 95 | inline void sweep::set_motor_speed(std::int32_t speed) { 96 | ::sweep_device_set_motor_speed(device.get(), speed, detail::error_to_exception{}); 97 | } 98 | 99 | inline std::int32_t sweep::get_sample_rate() { 100 | return ::sweep_device_get_sample_rate(device.get(), detail::error_to_exception{}); 101 | } 102 | 103 | inline void sweep::set_sample_rate(std::int32_t rate) { 104 | ::sweep_device_set_sample_rate(device.get(), rate, detail::error_to_exception{}); 105 | } 106 | 107 | inline scan sweep::get_scan() { 108 | using scan_owner = std::unique_ptr<::sweep_scan, decltype(&::sweep_scan_destruct)>; 109 | 110 | const scan_owner releasing_scan{::sweep_device_get_scan(device.get(), detail::error_to_exception{}), &::sweep_scan_destruct}; 111 | 112 | const auto num_samples = ::sweep_scan_get_number_of_samples(releasing_scan.get()); 113 | 114 | scan result{std::vector(num_samples)}; 115 | for (std::int32_t n = 0; n < num_samples; ++n) { 116 | // clang-format off 117 | result.samples[n].angle = ::sweep_scan_get_angle (releasing_scan.get(), n); 118 | result.samples[n].distance = ::sweep_scan_get_distance (releasing_scan.get(), n); 119 | result.samples[n].signal_strength = ::sweep_scan_get_signal_strength(releasing_scan.get(), n); 120 | // clang-format on 121 | } 122 | 123 | return result; 124 | } 125 | 126 | inline void sweep::reset() { ::sweep_device_reset(device.get(), detail::error_to_exception{}); } 127 | 128 | } // namespace sweep 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /libsweep/examples/viewer.cc: -------------------------------------------------------------------------------- 1 | // Make use of the CMake build system or compile manually, e.g. with: 2 | // g++ -std=c++11 viewer.cc -lsweep -lsfml-graphics -lsfml-window -lsfml-system 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | // Zoom into 5x5 meter area 18 | const constexpr auto kMaxLaserDistance = 5 * 100.; 19 | 20 | // Use cream for the background and denim for points 21 | static const sf::Color kColorCenter{255, 0, 0}; 22 | static const sf::Color kColorCream{250, 240, 230}; 23 | static const sf::Color kColorDenim{80, 102, 127}; 24 | 25 | // One circle per angle / distance measurement 26 | using PointCloud = std::vector; 27 | using PointCloudMutex = std::mutex; 28 | 29 | int main(int argc, char* argv[]) try { 30 | if (argc != 2) { 31 | std::cout << "Usage: ./example-viewer /dev/ttyUSB0\n"; 32 | return EXIT_FAILURE; 33 | } 34 | 35 | sf::ContextSettings settings; 36 | settings.antialiasingLevel = 8; 37 | sf::RenderWindow window(sf::VideoMode::getDesktopMode(), "Example Viewer for Scanse Sweep LiDAR", sf::Style::Default, settings); 38 | 39 | window.setFramerateLimit(30); 40 | window.setActive(false); // activated on render thread 41 | 42 | PointCloud pointCloud; 43 | PointCloudMutex pointCloudMutex; 44 | 45 | // Render thread displays the point cloud 46 | const auto worker = [&](sf::RenderWindow* window) { 47 | while (window->isOpen()) { 48 | sf::Event event; 49 | 50 | while (window->pollEvent(event)) { 51 | if (event.type == sf::Event::Closed) 52 | window->close(); 53 | else if (event.type == sf::Event::KeyPressed) 54 | if (event.key.code == sf::Keyboard::Escape) 55 | window->close(); 56 | } 57 | 58 | window->clear(kColorCream); 59 | 60 | { 61 | std::lock_guard sentry{pointCloudMutex}; 62 | 63 | for (auto point : pointCloud) 64 | window->draw(point); 65 | } 66 | 67 | window->display(); 68 | } 69 | }; 70 | 71 | sf::Thread thread(worker, &window); 72 | thread.launch(); 73 | 74 | // Now start scanning in the second thread, swapping in new points for every scan 75 | sweep::sweep device{argv[1]}; 76 | // Begins data acquisition as soon as motor is ready 77 | device.start_scanning(); 78 | 79 | sweep::scan scan; 80 | 81 | while (window.isOpen()) { 82 | scan = device.get_scan(); 83 | 84 | // 40m max radius => display as 80m x 80m square 85 | const auto windowSize = window.getSize(); 86 | const auto windowMinSize = std::min(windowSize.x, windowSize.y); 87 | 88 | PointCloud localPointCloud; 89 | 90 | for (auto sample : scan.samples) { 91 | const constexpr auto kDegreeToRadian = 0.017453292519943295; 92 | 93 | const auto distance = static_cast(sample.distance); 94 | 95 | // Discard samples above our we zoomed-in view box 96 | if (distance > kMaxLaserDistance) 97 | continue; 98 | 99 | // From milli degree to degree and adjust to device orientation 100 | const auto degree = std::fmod((static_cast(sample.angle) / 1000. + 90.), 360.); 101 | const auto radian = degree * kDegreeToRadian; 102 | 103 | // From angle / distance to to Cartesian 104 | auto x = std::cos(radian) * distance; 105 | auto y = std::sin(radian) * distance; 106 | 107 | // Make positive 108 | x = x + kMaxLaserDistance; 109 | y = y + kMaxLaserDistance; 110 | 111 | // Scale to window size 112 | x = (x / (2 * kMaxLaserDistance)) * windowMinSize; 113 | y = (y / (2 * kMaxLaserDistance)) * windowMinSize; 114 | 115 | sf::CircleShape point{3.0f, 8}; 116 | point.setPosition(x, windowMinSize - y); 117 | 118 | // Base transparency on signal strength 119 | auto color = kColorDenim; 120 | color.a = sample.signal_strength; 121 | point.setFillColor(color); 122 | 123 | localPointCloud.push_back(std::move(point)); 124 | } 125 | 126 | // display LiDAR position 127 | sf::CircleShape point{3.0f, 8}; 128 | point.setPosition(windowMinSize / 2, windowMinSize / 2); 129 | point.setFillColor(kColorCenter); 130 | localPointCloud.push_back(std::move(point)); 131 | 132 | { 133 | // Now swap in the new point cloud 134 | std::lock_guard sentry{pointCloudMutex}; 135 | pointCloud = std::move(localPointCloud); 136 | } 137 | } 138 | 139 | device.stop_scanning(); 140 | 141 | } catch (const sweep::device_error& e) { 142 | std::cerr << "Error: " << e.what() << std::endl; 143 | } 144 | -------------------------------------------------------------------------------- /libsweep/src/protocol.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "protocol.hpp" 5 | 6 | namespace sweep { 7 | namespace protocol { 8 | 9 | static uint8_t checksum_response_header(const response_header_s& v) { 10 | return ((v.cmdStatusByte1 + v.cmdStatusByte2) & 0x3F) + 0x30; 11 | } 12 | 13 | static uint8_t checksum_response_param(const response_param_s& v) { 14 | return ((v.cmdStatusByte1 + v.cmdStatusByte2) & 0x3F) + 0x30; 15 | } 16 | 17 | static uint8_t checksum_response_scan_packet(const response_scan_packet_s& v) { 18 | uint64_t checksum = 0; 19 | checksum += v.sync_error; 20 | checksum += v.angle & 0xff00; 21 | checksum += v.angle & 0x00ff; 22 | checksum += v.distance & 0xff00; 23 | checksum += v.distance & 0x00ff; 24 | checksum += v.signal_strength; 25 | return checksum % 255; 26 | } 27 | 28 | void write_command(serial::device_s serial, const uint8_t cmd[2]) { 29 | SWEEP_ASSERT(serial); 30 | SWEEP_ASSERT(cmd); 31 | 32 | cmd_packet_s packet; 33 | packet.cmdByte1 = cmd[0]; 34 | packet.cmdByte2 = cmd[1]; 35 | packet.cmdParamTerm = '\n'; 36 | 37 | // pause for 2ms, so the device is never bombarded with back to back commands 38 | std::this_thread::sleep_for(std::chrono::milliseconds(2)); 39 | 40 | serial::device_write(serial, &packet, sizeof(cmd_packet_s)); 41 | } 42 | 43 | void write_command_with_arguments(serial::device_s serial, const uint8_t cmd[2], const uint8_t arg[2]) { 44 | SWEEP_ASSERT(serial); 45 | SWEEP_ASSERT(cmd); 46 | SWEEP_ASSERT(arg); 47 | 48 | cmd_param_packet_s packet; 49 | packet.cmdByte1 = cmd[0]; 50 | packet.cmdByte2 = cmd[1]; 51 | packet.cmdParamByte1 = arg[0]; 52 | packet.cmdParamByte2 = arg[1]; 53 | packet.cmdParamTerm = '\n'; 54 | 55 | serial::device_write(serial, &packet, sizeof(cmd_param_packet_s)); 56 | } 57 | 58 | response_header_s read_response_header(serial::device_s serial, const uint8_t cmd[2]) { 59 | SWEEP_ASSERT(serial); 60 | SWEEP_ASSERT(cmd); 61 | 62 | response_header_s header; 63 | serial::device_read(serial, &header, sizeof(header)); 64 | 65 | uint8_t checksum = checksum_response_header(header); 66 | 67 | if (checksum != header.cmdSum) 68 | throw error{"invalid response header checksum"}; 69 | 70 | bool ok = header.cmdByte1 == cmd[0] && header.cmdByte2 == cmd[1]; 71 | 72 | if (!ok) 73 | throw error{"invalid header response commands"}; 74 | 75 | return header; 76 | } 77 | 78 | response_param_s read_response_param(serial::device_s serial, const uint8_t cmd[2]) { 79 | SWEEP_ASSERT(serial); 80 | SWEEP_ASSERT(cmd); 81 | 82 | response_param_s param; 83 | serial::device_read(serial, ¶m, sizeof(param)); 84 | 85 | uint8_t checksum = checksum_response_param(param); 86 | 87 | if (checksum != param.cmdSum) 88 | throw error{"invalid response param header checksum"}; 89 | 90 | bool ok = param.cmdByte1 == cmd[0] && param.cmdByte2 == cmd[1]; 91 | 92 | if (!ok) 93 | throw error{"invalid param response commands"}; 94 | 95 | return param; 96 | } 97 | 98 | response_scan_packet_s read_response_scan(serial::device_s serial) { 99 | SWEEP_ASSERT(serial); 100 | 101 | response_scan_packet_s scan; 102 | serial::device_read(serial, &scan, sizeof(scan)); 103 | 104 | uint8_t checksum = checksum_response_scan_packet(scan); 105 | 106 | if (checksum != scan.checksum) 107 | throw error{"invalid scan response commands"}; 108 | 109 | return scan; 110 | } 111 | 112 | response_info_motor_ready_s read_response_info_motor_ready(serial::device_s serial) { 113 | SWEEP_ASSERT(serial); 114 | 115 | response_info_motor_ready_s info; 116 | serial::device_read(serial, &info, sizeof(info)); 117 | 118 | bool ok = info.cmdByte1 == MOTOR_READY[0] && info.cmdByte2 == MOTOR_READY[1]; 119 | 120 | if (!ok) 121 | throw error{"invalid motor ready response commands"}; 122 | 123 | return info; 124 | } 125 | 126 | response_info_motor_speed_s read_response_info_motor_speed(serial::device_s serial) { 127 | SWEEP_ASSERT(serial); 128 | 129 | response_info_motor_speed_s info; 130 | serial::device_read(serial, &info, sizeof(info)); 131 | 132 | bool ok = info.cmdByte1 == MOTOR_INFORMATION[0] && info.cmdByte2 == MOTOR_INFORMATION[1]; 133 | 134 | if (!ok) 135 | throw error{"invalid motor info response commands"}; 136 | 137 | return info; 138 | } 139 | 140 | response_info_sample_rate_s read_response_info_sample_rate(sweep::serial::device_s serial) { 141 | SWEEP_ASSERT(serial); 142 | 143 | response_info_sample_rate_s info; 144 | serial::device_read(serial, &info, sizeof(info)); 145 | 146 | bool ok = info.cmdByte1 == SAMPLE_RATE_INFORMATION[0] && info.cmdByte2 == SAMPLE_RATE_INFORMATION[1]; 147 | 148 | if (!ok) 149 | throw error{"invalid sample rate info response commands"}; 150 | 151 | return info; 152 | } 153 | 154 | } // ns protocol 155 | } // ns sweep 156 | -------------------------------------------------------------------------------- /jsweep/src/main/java/io/scanse/sweep/SweepDevice.java: -------------------------------------------------------------------------------- 1 | package io.scanse.sweep; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | import java.util.NoSuchElementException; 7 | import java.util.Objects; 8 | 9 | import io.scanse.sweep.jna.DeviceJNAPointer; 10 | import io.scanse.sweep.jna.ErrorJNAPointer; 11 | import io.scanse.sweep.jna.ErrorReturnJNA; 12 | import io.scanse.sweep.jna.ScanJNAPointer; 13 | import io.scanse.sweep.jna.SweepJNA; 14 | 15 | public class SweepDevice implements AutoCloseable { 16 | 17 | private static final ThreadLocal ERROR = new ThreadLocal() { 18 | 19 | @Override 20 | protected ErrorReturnJNA initialValue() { 21 | return new ErrorReturnJNA(); 22 | } 23 | }; 24 | 25 | private static T maybeThrow(T result) { 26 | ErrorJNAPointer error = ERROR.get().returnedError; 27 | if (error == null) { 28 | return result; 29 | } 30 | try { 31 | String msg = SweepJNA.sweep_error_message(error); 32 | if (msg != null) { 33 | throw new RuntimeException(msg); 34 | } 35 | } finally { 36 | SweepJNA.sweep_error_destruct(error); 37 | } 38 | return result; 39 | } 40 | 41 | private DeviceJNAPointer handle; 42 | 43 | public SweepDevice(String port) { 44 | this.handle = maybeThrow(SweepJNA.sweep_device_construct_simple(port, ERROR.get())); 45 | } 46 | 47 | public SweepDevice(String port, int bitrate) { 48 | this.handle = maybeThrow(SweepJNA.sweep_device_construct(port, bitrate, ERROR.get())); 49 | } 50 | 51 | private void checkHandle() { 52 | Objects.requireNonNull(this.handle, "handle was destroyed"); 53 | } 54 | 55 | public void startScanning() { 56 | checkHandle(); 57 | if (!Sweep.ABI_COMPATIBLE) { 58 | throw new AssertionError( 59 | "Your installed libsweep is not ABI compatible with these bindings"); 60 | } 61 | SweepJNA.sweep_device_start_scanning(this.handle, ERROR.get()); 62 | maybeThrow(null); 63 | } 64 | 65 | public void stopScanning() { 66 | checkHandle(); 67 | SweepJNA.sweep_device_stop_scanning(this.handle, ERROR.get()); 68 | maybeThrow(null); 69 | } 70 | 71 | public boolean isMotorReady() { 72 | checkHandle(); 73 | return maybeThrow(SweepJNA.sweep_device_get_motor_ready(this.handle, ERROR.get())); 74 | } 75 | 76 | public List nextScan() { 77 | checkHandle(); 78 | ScanJNAPointer scan = maybeThrow(SweepJNA.sweep_device_get_scan(this.handle, ERROR.get())); 79 | int count = SweepJNA.sweep_scan_get_number_of_samples(scan); 80 | List samples = new ArrayList<>(count); 81 | for (int i = 0; i < count; i++) { 82 | int angle = SweepJNA.sweep_scan_get_angle(scan, i); 83 | int dist = SweepJNA.sweep_scan_get_distance(scan, i); 84 | int sigStr = SweepJNA.sweep_scan_get_signal_strength(scan, i); 85 | samples.add(new SweepSample(angle, dist, sigStr)); 86 | } 87 | return samples; 88 | } 89 | 90 | private final Iterable> iterable = new Iterable>() { 91 | 92 | @Override 93 | public Iterator> iterator() { 94 | return new Iterator>() { 95 | 96 | private boolean hasErrored = false; 97 | 98 | @Override 99 | public List next() { 100 | if (hasErrored) { 101 | throw new NoSuchElementException( 102 | "An error previously occurred."); 103 | } 104 | try { 105 | return nextScan(); 106 | } catch (RuntimeException e) { 107 | hasErrored = true; 108 | throw e; 109 | } 110 | } 111 | 112 | @Override 113 | public boolean hasNext() { 114 | return !hasErrored; 115 | } 116 | 117 | @Override 118 | public void remove() { 119 | throw new UnsupportedOperationException(); 120 | } 121 | 122 | }; 123 | } 124 | }; 125 | 126 | public Iterable> scans() { 127 | return iterable; 128 | } 129 | 130 | public int getMotorSpeed() { 131 | checkHandle(); 132 | return maybeThrow(SweepJNA.sweep_device_get_motor_speed(this.handle, ERROR.get())); 133 | } 134 | 135 | public void setMotorSpeed(int hz) { 136 | checkHandle(); 137 | SweepJNA.sweep_device_set_motor_speed(this.handle, hz, ERROR.get()); 138 | maybeThrow(null); 139 | } 140 | 141 | public int getSampleRate() { 142 | checkHandle(); 143 | return maybeThrow(SweepJNA.sweep_device_get_sample_rate(this.handle, ERROR.get())); 144 | } 145 | 146 | public void setSampleRate(int hz) { 147 | checkHandle(); 148 | SweepJNA.sweep_device_set_sample_rate(this.handle, hz, ERROR.get()); 149 | maybeThrow(null); 150 | } 151 | 152 | public void reset() { 153 | checkHandle(); 154 | SweepJNA.sweep_device_reset(this.handle, ERROR.get()); 155 | maybeThrow(null); 156 | } 157 | 158 | @Override 159 | public void close() { 160 | if (handle != null) { 161 | SweepJNA.sweep_device_destruct(handle); 162 | handle = null; 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /libsweep/src/unix/serial.cc: -------------------------------------------------------------------------------- 1 | #ifndef __FreeBSD__ 2 | /// The reason behind this conditional is to correctly set feature test macros 3 | /// on FreeBSD; by NOT defining `_POSIX_C_SOURCE` the test macro `__BSD_VISIBLE` 4 | /// is defined, enabling the use of `cfmakeraw(3)`. 5 | #define _POSIX_C_SOURCE 200809L 6 | #endif 7 | 8 | #include "serial.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace sweep { 22 | namespace serial { 23 | 24 | struct device { 25 | int32_t fd; 26 | }; 27 | 28 | static speed_t get_baud(int32_t bitrate) { 29 | SWEEP_ASSERT(bitrate > 0); 30 | if (bitrate != 115200) { 31 | throw error{"Only baud rate 115200 is supported at this time."}; 32 | return -1; 33 | } 34 | 35 | // translate human readable bitrate to termios bitrate 36 | #ifdef B115200 37 | return B115200; 38 | #endif 39 | return bitrate; 40 | } 41 | 42 | static bool wait_readable(device_s serial) { 43 | SWEEP_ASSERT(serial); 44 | 45 | // Setup a select call to block for serial data 46 | fd_set readfds; 47 | FD_ZERO(&readfds); 48 | FD_SET(serial->fd, &readfds); 49 | 50 | int32_t ret = select(serial->fd + 1, &readfds, nullptr, nullptr, nullptr); 51 | 52 | if (ret == -1) { 53 | // Select was interrupted 54 | if (errno == EINTR) { 55 | return false; 56 | } 57 | 58 | // Otherwise there was some error 59 | throw error{"blocking on data to read failed"}; 60 | 61 | } else if (ret) { 62 | // Data Available 63 | return true; 64 | } else { 65 | return false; 66 | } 67 | 68 | return false; 69 | } 70 | 71 | device_s device_construct(const char* port, int32_t bitrate) { 72 | SWEEP_ASSERT(port); 73 | SWEEP_ASSERT(bitrate > 0); 74 | 75 | int32_t fd = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK); 76 | 77 | if (fd == -1) 78 | throw error{"opening serial port failed"}; 79 | 80 | if (!isatty(fd)) 81 | throw error{"serial port is not a TTY"}; 82 | 83 | struct termios options; 84 | 85 | if (tcgetattr(fd, &options) == -1) 86 | throw error{"querying terminal options failed"}; 87 | 88 | #ifndef __FreeBSD__ 89 | // Input Flags 90 | options.c_iflag &= ~(INLCR | IGNCR | ICRNL | IGNBRK); 91 | 92 | // SW Flow Control OFF 93 | options.c_iflag &= ~(IXON | IXOFF | IXANY); 94 | 95 | // Output Flags 96 | options.c_oflag &= ~(OPOST); 97 | 98 | // Local Flags 99 | options.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG); 100 | 101 | // IEXTEN 102 | 103 | // Control Flags 104 | options.c_cflag &= ~(PARENB | CSTOPB | CSIZE); 105 | options.c_cflag |= (CLOCAL | CREAD | CS8); 106 | #else // __FreeBSD__ 107 | cfmakeraw(&options); 108 | #endif 109 | 110 | // setup baud rate 111 | speed_t baud = get_baud(bitrate); 112 | 113 | cfsetispeed(&options, baud); 114 | cfsetospeed(&options, baud); 115 | 116 | // flush the port 117 | if (tcflush(fd, TCIFLUSH) == -1) 118 | throw error{"flushing the serial port failed"}; 119 | 120 | // set port attributes 121 | if (tcsetattr(fd, TCSANOW, &options) == -1) { 122 | if (close(fd) == -1) 123 | SWEEP_ASSERT(false && "closing file descriptor during error handling failed"); 124 | 125 | throw error{"setting terminal options failed"}; 126 | } 127 | 128 | auto out = new device{fd}; 129 | return out; 130 | } 131 | 132 | void device_destruct(device_s serial) { 133 | SWEEP_ASSERT(serial); 134 | 135 | try { 136 | device_flush(serial); 137 | } catch (...) { 138 | // nothing we can do here 139 | } 140 | 141 | if (close(serial->fd) == -1) 142 | SWEEP_ASSERT(false && "closing file descriptor during destruct failed"); 143 | 144 | delete serial; 145 | } 146 | 147 | void device_read(device_s serial, void* to, int32_t len) { 148 | SWEEP_ASSERT(serial); 149 | SWEEP_ASSERT(to); 150 | SWEEP_ASSERT(len >= 0); 151 | 152 | // the following implements reliable full read xor error 153 | int32_t bytes_read = 0; 154 | 155 | while (bytes_read < len) { 156 | if (wait_readable(serial)) { 157 | int ret = read(serial->fd, (char*)to + bytes_read, len - bytes_read); 158 | 159 | if (ret == -1) { 160 | if (errno == EAGAIN || errno == EINTR) { 161 | continue; 162 | } else { 163 | throw error{"reading from serial device failed"}; 164 | } 165 | } else if (ret == 0) { 166 | throw error{"encountered EOF on serial device"}; 167 | } else { 168 | bytes_read += ret; 169 | } 170 | } 171 | } 172 | 173 | SWEEP_ASSERT(bytes_read == len && "reliable read failed to read requested size of bytes"); 174 | } 175 | 176 | void device_write(device_s serial, const void* from, int32_t len) { 177 | SWEEP_ASSERT(serial); 178 | SWEEP_ASSERT(from); 179 | SWEEP_ASSERT(len >= 0); 180 | 181 | // the following implements reliable full write xor error 182 | int32_t bytes_written = 0; 183 | 184 | while (bytes_written < len) { 185 | int32_t ret = write(serial->fd, (const char*)from + bytes_written, len - bytes_written); 186 | 187 | if (ret == -1) { 188 | if (errno == EAGAIN || errno == EINTR) { 189 | continue; 190 | } else { 191 | throw error{"writing to serial device failed"}; 192 | } 193 | } else { 194 | bytes_written += ret; 195 | } 196 | } 197 | 198 | SWEEP_ASSERT(bytes_written == len && "reliable write failed to write requested size of bytes"); 199 | } 200 | 201 | void device_flush(device_s serial) { 202 | SWEEP_ASSERT(serial); 203 | 204 | if (tcflush(serial->fd, TCIFLUSH) == -1) 205 | throw error{"flushing the serial port failed"}; 206 | } 207 | 208 | } // ns serial 209 | } // ns sweep 210 | -------------------------------------------------------------------------------- /jsweep/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /libsweep/src/dummy.cc: -------------------------------------------------------------------------------- 1 | #include "sweep.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int32_t sweep_get_version(void) { return SWEEP_VERSION; } 8 | bool sweep_is_abi_compatible(void) { return sweep_get_version() >> 16u == SWEEP_VERSION_MAJOR; } 9 | 10 | struct sweep_error { 11 | std::string what; 12 | }; 13 | 14 | struct sweep_device { 15 | bool is_scanning; 16 | int32_t motor_speed; 17 | int32_t sample_rate; 18 | int32_t nth_scan_request; 19 | }; 20 | 21 | struct sweep_scan { 22 | int32_t count; 23 | int32_t nth; 24 | }; 25 | 26 | const char* sweep_error_message(sweep_error_s error) { 27 | SWEEP_ASSERT(error); 28 | 29 | return error->what.c_str(); 30 | } 31 | 32 | void sweep_error_destruct(sweep_error_s error) { 33 | SWEEP_ASSERT(error); 34 | 35 | delete error; 36 | } 37 | 38 | static void sweep_device_wait_until_motor_ready(sweep_device_s device, sweep_error_s* error) { 39 | SWEEP_ASSERT(device); 40 | SWEEP_ASSERT(error); 41 | SWEEP_ASSERT(!device->is_scanning); 42 | 43 | (void)device; 44 | (void)error; 45 | } 46 | 47 | static void sweep_device_attempt_start_scanning(sweep_device_s device, sweep_error_s* error) { 48 | SWEEP_ASSERT(device); 49 | SWEEP_ASSERT(error); 50 | SWEEP_ASSERT(!device->is_scanning); 51 | (void)error; 52 | 53 | if (device->is_scanning) 54 | return; 55 | 56 | device->is_scanning = true; 57 | } 58 | 59 | static void sweep_device_attempt_set_motor_speed(sweep_device_s device, int32_t hz, sweep_error_s* error) { 60 | SWEEP_ASSERT(device); 61 | SWEEP_ASSERT(hz >= 0 && hz <= 10); 62 | SWEEP_ASSERT(error); 63 | SWEEP_ASSERT(!device->is_scanning); 64 | (void)error; 65 | 66 | device->motor_speed = hz; 67 | } 68 | 69 | sweep_device_s sweep_device_construct_simple(const char* port, sweep_error_s* error) { 70 | SWEEP_ASSERT(error); 71 | 72 | return sweep_device_construct(port, 115200, error); 73 | } 74 | 75 | sweep_device_s sweep_device_construct(const char* port, int32_t bitrate, sweep_error_s* error) { 76 | SWEEP_ASSERT(port); 77 | SWEEP_ASSERT(bitrate > 0); 78 | SWEEP_ASSERT(error); 79 | (void)port; 80 | (void)bitrate; 81 | (void)error; 82 | 83 | auto out = new sweep_device{/*is_scanning=*/false, /*motor_speed=*/5, /*sample_rate*/ 500, /*nth_scan_request=*/0}; 84 | return out; 85 | } 86 | 87 | void sweep_device_destruct(sweep_device_s device) { 88 | SWEEP_ASSERT(device); 89 | 90 | delete device; 91 | } 92 | 93 | void sweep_device_start_scanning(sweep_device_s device, sweep_error_s* error) { 94 | SWEEP_ASSERT(device); 95 | SWEEP_ASSERT(error); 96 | SWEEP_ASSERT(!device->is_scanning); 97 | (void)error; 98 | 99 | if (device->is_scanning) 100 | return; 101 | 102 | device->is_scanning = true; 103 | } 104 | 105 | void sweep_device_stop_scanning(sweep_device_s device, sweep_error_s* error) { 106 | SWEEP_ASSERT(device); 107 | SWEEP_ASSERT(error); 108 | (void)error; 109 | 110 | device->is_scanning = false; 111 | } 112 | 113 | sweep_scan_s sweep_device_get_scan(sweep_device_s device, sweep_error_s* error) { 114 | SWEEP_ASSERT(device); 115 | SWEEP_ASSERT(error); 116 | SWEEP_ASSERT(device->is_scanning); 117 | (void)error; 118 | 119 | auto out = new sweep_scan{/*count=*/device->is_scanning ? 16 : 0, /*nth=*/device->nth_scan_request}; 120 | 121 | device->nth_scan_request += 1; 122 | 123 | // Artificially introduce slowdown, to simulate device rotation 124 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 125 | 126 | return out; 127 | } 128 | 129 | bool sweep_device_get_motor_ready(sweep_device_s device, sweep_error_s* error) { 130 | SWEEP_ASSERT(device); 131 | SWEEP_ASSERT(error); 132 | SWEEP_ASSERT(!device->is_scanning); 133 | (void)device; 134 | (void)error; 135 | 136 | return true; 137 | } 138 | 139 | int32_t sweep_device_get_motor_speed(sweep_device_s device, sweep_error_s* error) { 140 | SWEEP_ASSERT(device); 141 | SWEEP_ASSERT(error); 142 | SWEEP_ASSERT(!device->is_scanning); 143 | (void)device; 144 | (void)error; 145 | 146 | return device->motor_speed; 147 | } 148 | 149 | void sweep_device_set_motor_speed(sweep_device_s device, int32_t hz, sweep_error_s* error) { 150 | SWEEP_ASSERT(device); 151 | SWEEP_ASSERT(hz >= 0 && hz <= 10); 152 | SWEEP_ASSERT(error); 153 | SWEEP_ASSERT(!device->is_scanning); 154 | (void)error; 155 | 156 | device->motor_speed = hz; 157 | } 158 | 159 | int32_t sweep_device_get_sample_rate(sweep_device_s device, sweep_error_s* error) { 160 | SWEEP_ASSERT(device); 161 | SWEEP_ASSERT(error); 162 | SWEEP_ASSERT(!device->is_scanning); 163 | (void)error; 164 | 165 | return device->sample_rate; 166 | } 167 | 168 | void sweep_device_set_sample_rate(sweep_device_s device, int32_t hz, sweep_error_s* error) { 169 | SWEEP_ASSERT(device); 170 | SWEEP_ASSERT(hz == 500 || hz == 750 || hz == 1000); 171 | SWEEP_ASSERT(error); 172 | SWEEP_ASSERT(!device->is_scanning); 173 | (void)error; 174 | 175 | device->sample_rate = hz; 176 | } 177 | 178 | int32_t sweep_scan_get_number_of_samples(sweep_scan_s scan) { 179 | SWEEP_ASSERT(scan); 180 | 181 | return scan->count; 182 | } 183 | 184 | int32_t sweep_scan_get_angle(sweep_scan_s scan, int32_t sample) { 185 | SWEEP_ASSERT(scan); 186 | SWEEP_ASSERT(sample >= 0 && sample < scan->count && "sample index out of bounds"); 187 | 188 | int32_t angle = 360; 189 | int32_t delta = (sample % 4) * 2 + scan->nth; 190 | 191 | switch (sample / 4) { 192 | case 0: 193 | angle = 0; 194 | break; 195 | case 1: 196 | angle = 90; 197 | break; 198 | case 2: 199 | angle = 180; 200 | break; 201 | case 3: 202 | angle = 270; 203 | break; 204 | } 205 | 206 | return ((angle + delta) % 360) * 1000; 207 | } 208 | 209 | int32_t sweep_scan_get_distance(sweep_scan_s scan, int32_t sample) { 210 | SWEEP_ASSERT(scan); 211 | SWEEP_ASSERT(sample >= 0 && sample < scan->count && "sample index out of bounds"); 212 | (void)scan; 213 | (void)sample; 214 | 215 | return 2 * 100; // 2 meter 216 | } 217 | 218 | int32_t sweep_scan_get_signal_strength(sweep_scan_s scan, int32_t sample) { 219 | SWEEP_ASSERT(scan); 220 | SWEEP_ASSERT(sample >= 0 && sample < scan->count && "sample index out of bounds"); 221 | (void)scan; 222 | (void)sample; 223 | 224 | return 200; 225 | } 226 | 227 | void sweep_scan_destruct(sweep_scan_s scan) { 228 | SWEEP_ASSERT(scan); 229 | 230 | delete scan; 231 | } 232 | 233 | void sweep_device_reset(sweep_device_s device, sweep_error_s* error) { 234 | SWEEP_ASSERT(device); 235 | SWEEP_ASSERT(error); 236 | SWEEP_ASSERT(!device->is_scanning); 237 | (void)device; 238 | (void)error; 239 | } 240 | -------------------------------------------------------------------------------- /libsweep/include/protocol.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_PROTOCOL_2EADE195E243_HPP 2 | #define SWEEP_PROTOCOL_2EADE195E243_HPP 3 | 4 | /* 5 | * Device communication protocol specifics. 6 | * Implementation detail; not exported. 7 | */ 8 | 9 | #include "error.hpp" 10 | #include "serial.hpp" 11 | 12 | #include "sweep.h" 13 | 14 | #include 15 | 16 | namespace sweep { 17 | namespace protocol { 18 | 19 | struct error : sweep::error::error { 20 | using base = sweep::error::error; 21 | using base::base; 22 | }; 23 | 24 | // Command Symbols 25 | 26 | constexpr uint8_t DATA_ACQUISITION_START[2] = {'D', 'S'}; 27 | constexpr uint8_t DATA_ACQUISITION_STOP[2] = {'D', 'X'}; 28 | constexpr uint8_t MOTOR_SPEED_ADJUST[2] = {'M', 'S'}; 29 | constexpr uint8_t MOTOR_READY[2] = {'M', 'Z'}; 30 | constexpr uint8_t MOTOR_INFORMATION[2] = {'M', 'I'}; 31 | constexpr uint8_t SAMPLE_RATE_ADJUST[2] = {'L', 'R'}; 32 | constexpr uint8_t SAMPLE_RATE_INFORMATION[2] = {'L', 'I'}; 33 | constexpr uint8_t VERSION_INFORMATION[2] = {'I', 'V'}; 34 | constexpr uint8_t DEVICE_INFORMATION[2] = {'I', 'D'}; 35 | constexpr uint8_t RESET_DEVICE[2] = {'R', 'R'}; 36 | 37 | // Packets for communication 38 | 39 | // Make in-memory representations correspond to bytes we send over the wire. 40 | #pragma pack(push, 1) 41 | 42 | struct cmd_packet_s { 43 | uint8_t cmdByte1; 44 | uint8_t cmdByte2; 45 | uint8_t cmdParamTerm; 46 | }; 47 | 48 | static_assert(sizeof(cmd_packet_s) == 3, "cmd packet size mismatch"); 49 | 50 | struct cmd_param_packet_s { 51 | uint8_t cmdByte1; 52 | uint8_t cmdByte2; 53 | uint8_t cmdParamByte1; 54 | uint8_t cmdParamByte2; 55 | uint8_t cmdParamTerm; 56 | }; 57 | 58 | static_assert(sizeof(cmd_param_packet_s) == 5, "cmd param packet size mismatch"); 59 | 60 | struct response_header_s { 61 | uint8_t cmdByte1; 62 | uint8_t cmdByte2; 63 | uint8_t cmdStatusByte1; 64 | uint8_t cmdStatusByte2; 65 | uint8_t cmdSum; 66 | uint8_t term1; 67 | }; 68 | 69 | static_assert(sizeof(response_header_s) == 6, "response header size mismatch"); 70 | 71 | struct response_param_s { 72 | uint8_t cmdByte1; 73 | uint8_t cmdByte2; 74 | uint8_t cmdParamByte1; 75 | uint8_t cmdParamByte2; 76 | uint8_t term1; 77 | uint8_t cmdStatusByte1; 78 | uint8_t cmdStatusByte2; 79 | uint8_t cmdSum; 80 | uint8_t term2; 81 | }; 82 | 83 | static_assert(sizeof(response_param_s) == 9, "response param size mismatch"); 84 | 85 | struct response_scan_packet_s { 86 | uint8_t sync_error; // see sync_error_bits::bits below 87 | uint16_t angle; // see get_angle_millideg below 88 | uint16_t distance; 89 | uint8_t signal_strength; 90 | uint8_t checksum; 91 | 92 | struct sync_error_bits { 93 | enum bits : uint8_t { 94 | sync = 1 << 0, // beginning of new full scan 95 | communication_error = 1 << 1, // communication error 96 | 97 | // Reserved for future error bits 98 | reserved2 = 1 << 2, 99 | reserved3 = 1 << 3, 100 | reserved4 = 1 << 4, 101 | reserved5 = 1 << 5, 102 | reserved6 = 1 << 6, 103 | reserved7 = 1 << 7, 104 | }; 105 | }; 106 | 107 | bool is_sync() const { return sync_error & sync_error_bits::sync; } 108 | bool has_error() const { return sync_error >> 1 != 0; } // shift out sync bit, others are errors 109 | int get_angle_millideg() const { 110 | // angle is transmitted as fixed point integer with scaling factor of 16 111 | return static_cast(angle * 1000 / 16.0f); 112 | } 113 | }; 114 | 115 | static_assert(sizeof(response_scan_packet_s) == 7, "response scan packet size mismatch"); 116 | 117 | struct response_info_device_s { 118 | uint8_t cmdByte1; 119 | uint8_t cmdByte2; 120 | uint8_t bit_rate[6]; 121 | uint8_t laser_state; 122 | uint8_t mode; 123 | uint8_t diagnostic; 124 | uint8_t motor_speed[2]; 125 | uint8_t sample_rate[4]; 126 | uint8_t term; 127 | }; 128 | 129 | static_assert(sizeof(response_info_device_s) == 18, "response info device size mismatch"); 130 | 131 | struct response_info_version_s { 132 | uint8_t cmdByte1; 133 | uint8_t cmdByte2; 134 | uint8_t model[5]; 135 | uint8_t protocol_major; 136 | uint8_t protocol_min; 137 | uint8_t firmware_major; 138 | uint8_t firmware_minor; 139 | uint8_t hardware_version; 140 | uint8_t serial_no[8]; 141 | uint8_t term; 142 | }; 143 | 144 | static_assert(sizeof(response_info_version_s) == 21, "response info version size mismatch"); 145 | 146 | struct response_info_motor_ready_s { 147 | uint8_t cmdByte1; 148 | uint8_t cmdByte2; 149 | uint8_t motor_ready[2]; 150 | uint8_t term; 151 | }; 152 | 153 | static_assert(sizeof(response_info_motor_ready_s) == 5, "response info motor ready size mismatch"); 154 | 155 | struct response_info_motor_speed_s { 156 | uint8_t cmdByte1; 157 | uint8_t cmdByte2; 158 | uint8_t motor_speed[2]; 159 | uint8_t term; 160 | }; 161 | 162 | static_assert(sizeof(response_info_motor_speed_s) == 5, "response info motor speed size mismatch"); 163 | 164 | struct response_info_sample_rate_s { 165 | uint8_t cmdByte1; 166 | uint8_t cmdByte2; 167 | uint8_t sample_rate[2]; 168 | uint8_t term; 169 | }; 170 | 171 | static_assert(sizeof(response_info_sample_rate_s) == 5, "response info sample rate size mismatch"); 172 | 173 | // Done with in-memory representations for packets we send over the wire. 174 | #pragma pack(pop) 175 | 176 | // Read and write specific packets 177 | 178 | void write_command(sweep::serial::device_s serial, const uint8_t cmd[2]); 179 | 180 | void write_command_with_arguments(sweep::serial::device_s serial, const uint8_t cmd[2], const uint8_t arg[2]); 181 | 182 | response_header_s read_response_header(sweep::serial::device_s serial, const uint8_t cmd[2]); 183 | 184 | response_param_s read_response_param(sweep::serial::device_s serial, const uint8_t cmd[2]); 185 | 186 | response_scan_packet_s read_response_scan(sweep::serial::device_s serial); 187 | 188 | response_info_motor_ready_s read_response_info_motor_ready(sweep::serial::device_s serial); 189 | 190 | response_info_motor_speed_s read_response_info_motor_speed(sweep::serial::device_s serial); 191 | 192 | response_info_sample_rate_s read_response_info_sample_rate(sweep::serial::device_s serial); 193 | 194 | inline void integral_to_ascii_bytes(const int32_t integral, uint8_t bytes[2]) { 195 | SWEEP_ASSERT(integral >= 0); 196 | SWEEP_ASSERT(integral <= 99); 197 | SWEEP_ASSERT(bytes); 198 | 199 | // Numbers begin at ASCII code point 48 200 | const uint8_t ASCIINumberBlockOffset = '0'; 201 | 202 | const uint8_t num1 = (integral / 10) + ASCIINumberBlockOffset; 203 | const uint8_t num2 = (integral % 10) + ASCIINumberBlockOffset; 204 | 205 | bytes[0] = num1; 206 | bytes[1] = num2; 207 | } 208 | 209 | inline int32_t ascii_bytes_to_integral(const uint8_t bytes[2]) { 210 | SWEEP_ASSERT(bytes); 211 | 212 | const uint8_t ASCIINumberBlockOffset = '0'; 213 | 214 | SWEEP_ASSERT(bytes[0] >= ASCIINumberBlockOffset); 215 | SWEEP_ASSERT(bytes[1] >= ASCIINumberBlockOffset); 216 | 217 | const uint8_t num1 = static_cast(bytes[0] - ASCIINumberBlockOffset); 218 | const uint8_t num2 = static_cast(bytes[1] - ASCIINumberBlockOffset); 219 | 220 | SWEEP_ASSERT(num1 <= 9); 221 | SWEEP_ASSERT(num2 <= 9); 222 | 223 | const int32_t integral = (num1 * 10) + (num2 * 1); 224 | 225 | SWEEP_ASSERT(integral >= 0); 226 | SWEEP_ASSERT(integral <= 99); 227 | 228 | return integral; 229 | } 230 | 231 | } // namespace protocol 232 | } // namespace sweep 233 | 234 | #endif 235 | -------------------------------------------------------------------------------- /sweeppy/sweeppy/__init__.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import ctypes.util 3 | import collections 4 | 5 | libsweep = ctypes.cdll.LoadLibrary(ctypes.util.find_library('sweep')) 6 | 7 | libsweep.sweep_get_version.restype = ctypes.c_int32 8 | libsweep.sweep_get_version.argtypes = None 9 | 10 | libsweep.sweep_is_abi_compatible.restype = ctypes.c_bool 11 | libsweep.sweep_is_abi_compatible.argtypes = None 12 | 13 | libsweep.sweep_error_message.restype = ctypes.c_char_p 14 | libsweep.sweep_error_message.argtypes = [ctypes.c_void_p] 15 | 16 | libsweep.sweep_error_destruct.restype = None 17 | libsweep.sweep_error_destruct.argtypes = [ctypes.c_void_p] 18 | 19 | libsweep.sweep_device_construct_simple.restype = ctypes.c_void_p 20 | libsweep.sweep_device_construct_simple.argtypes = [ctypes.c_char_p, ctypes.c_void_p] 21 | 22 | libsweep.sweep_device_construct.restype = ctypes.c_void_p 23 | libsweep.sweep_device_construct.argtypes = [ctypes.c_char_p, ctypes.c_int32, ctypes.c_void_p] 24 | 25 | libsweep.sweep_device_destruct.restype = None 26 | libsweep.sweep_device_destruct.argtypes = [ctypes.c_void_p] 27 | 28 | libsweep.sweep_device_start_scanning.restype = None 29 | libsweep.sweep_device_start_scanning.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 30 | 31 | libsweep.sweep_device_stop_scanning.restype = None 32 | libsweep.sweep_device_stop_scanning.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 33 | 34 | libsweep.sweep_device_get_scan.restype = ctypes.c_void_p 35 | libsweep.sweep_device_get_scan.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 36 | 37 | libsweep.sweep_scan_destruct.restype = None 38 | libsweep.sweep_scan_destruct.argtypes = [ctypes.c_void_p] 39 | 40 | libsweep.sweep_scan_get_number_of_samples.restype = ctypes.c_int32 41 | libsweep.sweep_scan_get_number_of_samples.argtypes = [ctypes.c_void_p] 42 | 43 | libsweep.sweep_scan_get_angle.restype = ctypes.c_int32 44 | libsweep.sweep_scan_get_angle.argtypes = [ctypes.c_void_p, ctypes.c_int32] 45 | 46 | libsweep.sweep_scan_get_distance.restype = ctypes.c_int32 47 | libsweep.sweep_scan_get_distance.argtypes = [ctypes.c_void_p, ctypes.c_int32] 48 | 49 | libsweep.sweep_scan_get_signal_strength.restype = ctypes.c_int32 50 | libsweep.sweep_scan_get_signal_strength.argtypes = [ctypes.c_void_p, ctypes.c_int32] 51 | 52 | libsweep.sweep_device_get_motor_ready.restype = ctypes.c_bool 53 | libsweep.sweep_device_get_motor_ready.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 54 | 55 | libsweep.sweep_device_get_motor_speed.restype = ctypes.c_int32 56 | libsweep.sweep_device_get_motor_speed.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 57 | 58 | libsweep.sweep_device_set_motor_speed.restype = None 59 | libsweep.sweep_device_set_motor_speed.argtypes = [ctypes.c_void_p, ctypes.c_int32, ctypes.c_void_p] 60 | 61 | libsweep.sweep_device_get_sample_rate.restype = ctypes.c_int32 62 | libsweep.sweep_device_get_sample_rate.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 63 | 64 | libsweep.sweep_device_set_sample_rate.restype = None 65 | libsweep.sweep_device_set_sample_rate.argtypes = [ctypes.c_void_p, ctypes.c_int32, ctypes.c_void_p] 66 | 67 | libsweep.sweep_device_reset.restype = None 68 | libsweep.sweep_device_reset.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 69 | 70 | 71 | def _error_to_exception(error): 72 | assert error 73 | what = libsweep.sweep_error_message(error) 74 | libsweep.sweep_error_destruct(error) 75 | return RuntimeError(what.decode('ascii')) 76 | 77 | 78 | class Scan(collections.namedtuple('Scan', 'samples')): 79 | pass 80 | 81 | 82 | class Sample(collections.namedtuple('Sample', 'angle distance signal_strength')): 83 | pass 84 | 85 | 86 | class Sweep: 87 | def __init__(_, port, bitrate = None): 88 | _.scoped = False 89 | _.args = [port, bitrate] 90 | 91 | def __enter__(_): 92 | _.scoped = True 93 | _.device = None 94 | 95 | assert libsweep.sweep_is_abi_compatible(), 'Your installed libsweep is not ABI compatible with these bindings' 96 | 97 | error = ctypes.c_void_p() 98 | 99 | simple = not _.args[1] 100 | config = all(_.args) 101 | 102 | assert simple or config, 'No arguments for bitrate, required' 103 | 104 | if simple: 105 | port = ctypes.string_at(_.args[0].encode('ascii')) 106 | device = libsweep.sweep_device_construct_simple(port, ctypes.byref(error)) 107 | 108 | if config: 109 | port = ctypes.string_at(_.args[0].encode('ascii')) 110 | bitrate = ctypes.c_int32(_.args[1]) 111 | device = libsweep.sweep_device_construct(port, bitrate, ctypes.byref(error)) 112 | 113 | if error: 114 | raise _error_to_exception(error) 115 | 116 | _.device = device 117 | 118 | return _ 119 | 120 | def __exit__(_, *args): 121 | _.scoped = False 122 | 123 | if _.device: 124 | libsweep.sweep_device_destruct(_.device) 125 | 126 | def _assert_scoped(_): 127 | assert _.scoped, 'Use the `with` statement to guarantee for deterministic resource management' 128 | 129 | def start_scanning(_): 130 | _._assert_scoped() 131 | 132 | error = ctypes.c_void_p(); 133 | libsweep.sweep_device_start_scanning(_.device, ctypes.byref(error)) 134 | 135 | if error: 136 | raise _error_to_exception(error) 137 | 138 | def stop_scanning(_): 139 | _._assert_scoped() 140 | 141 | error = ctypes.c_void_p() 142 | libsweep.sweep_device_stop_scanning(_.device, ctypes.byref(error)) 143 | 144 | if error: 145 | raise _error_to_exception(error) 146 | 147 | def get_motor_ready(_): 148 | _._assert_scoped() 149 | 150 | error = ctypes.c_void_p() 151 | is_ready = libsweep.sweep_device_get_motor_ready(_.device, ctypes.byref(error)) 152 | 153 | if error: 154 | raise _error_to_exception(error) 155 | 156 | return is_ready 157 | 158 | def get_motor_speed(_): 159 | _._assert_scoped() 160 | 161 | error = ctypes.c_void_p() 162 | speed = libsweep.sweep_device_get_motor_speed(_.device, ctypes.byref(error)) 163 | 164 | if error: 165 | raise _error_to_exception(error) 166 | 167 | return speed 168 | 169 | def set_motor_speed(_, speed): 170 | _._assert_scoped() 171 | 172 | error = ctypes.c_void_p() 173 | libsweep.sweep_device_set_motor_speed(_.device, speed, ctypes.byref(error)) 174 | 175 | if error: 176 | raise _error_to_exception(error) 177 | 178 | def get_sample_rate(_): 179 | _._assert_scoped() 180 | 181 | error = ctypes.c_void_p() 182 | speed = libsweep.sweep_device_get_sample_rate(_.device, ctypes.byref(error)) 183 | 184 | if error: 185 | raise _error_to_exception(error) 186 | 187 | return speed 188 | 189 | def set_sample_rate(_, speed): 190 | _._assert_scoped() 191 | 192 | error = ctypes.c_void_p() 193 | libsweep.sweep_device_set_sample_rate(_.device, speed, ctypes.byref(error)) 194 | 195 | if error: 196 | raise _error_to_exception(error) 197 | 198 | def get_scans(_): 199 | _._assert_scoped() 200 | 201 | error = ctypes.c_void_p() 202 | 203 | while True: 204 | scan = libsweep.sweep_device_get_scan(_.device, ctypes.byref(error)) 205 | 206 | if error: 207 | raise _error_to_exception(error) 208 | 209 | num_samples = libsweep.sweep_scan_get_number_of_samples(scan) 210 | 211 | samples = [Sample(angle=libsweep.sweep_scan_get_angle(scan, n), 212 | distance=libsweep.sweep_scan_get_distance(scan, n), 213 | signal_strength=libsweep.sweep_scan_get_signal_strength(scan, n)) 214 | for n in range(num_samples)] 215 | 216 | libsweep.sweep_scan_destruct(scan) 217 | 218 | yield Scan(samples=samples) 219 | 220 | 221 | def reset(_): 222 | _._assert_scoped() 223 | 224 | error = ctypes.c_void_p() 225 | libsweep.sweep_device_reset(_.device, ctypes.byref(error)) 226 | 227 | if error: 228 | raise _error_to_exception(error) 229 | -------------------------------------------------------------------------------- /sweepjs/sweepjs.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "sweepjs.h" 5 | 6 | struct SweepError final : std::runtime_error { 7 | using Base = std::runtime_error; 8 | using Base::Base; 9 | }; 10 | 11 | // Translates error to throwing an exception 12 | struct ErrorToException { 13 | operator ::sweep_error_s*() { return &error; } 14 | 15 | ~ErrorToException() noexcept(false) { 16 | if (error) { 17 | SweepError e{::sweep_error_message(error)}; 18 | ::sweep_error_destruct(error); 19 | throw e; 20 | } 21 | } 22 | 23 | ::sweep_error_s error = nullptr; 24 | }; 25 | 26 | // Translates error to setting exception state in v8 27 | struct ErrorToNanException { 28 | operator ::sweep_error_s*() { return &error; } 29 | 30 | ~ErrorToNanException() { 31 | if (error) { 32 | auto* what = ::sweep_error_message(error); 33 | ::sweep_error_destruct(error); 34 | Nan::ThrowError(what); 35 | } 36 | } 37 | 38 | ::sweep_error_s error = nullptr; 39 | }; 40 | 41 | Sweep::Sweep(const char* port) { 42 | auto devptr = ::sweep_device_construct_simple(port, ErrorToException{}); 43 | auto defer = [](::sweep_device_s dev) { ::sweep_device_destruct(dev); }; 44 | 45 | device = {devptr, defer}; 46 | } 47 | 48 | Sweep::Sweep(const char* port, int32_t bitrate) { 49 | auto devptr = ::sweep_device_construct(port, bitrate, ErrorToException{}); 50 | auto defer = [](::sweep_device_s dev) { ::sweep_device_destruct(dev); }; 51 | 52 | device = {devptr, defer}; 53 | } 54 | 55 | NAN_MODULE_INIT(Sweep::Init) { 56 | const auto whoami = Nan::New("Sweep").ToLocalChecked(); 57 | 58 | auto fnTp = Nan::New(New); 59 | fnTp->SetClassName(whoami); 60 | fnTp->InstanceTemplate()->SetInternalFieldCount(1); 61 | 62 | SetPrototypeMethod(fnTp, "startScanning", startScanning); 63 | SetPrototypeMethod(fnTp, "stopScanning", stopScanning); 64 | SetPrototypeMethod(fnTp, "scan", scan); 65 | SetPrototypeMethod(fnTp, "getMotorReady", getMotorReady); 66 | SetPrototypeMethod(fnTp, "getMotorSpeed", getMotorSpeed); 67 | SetPrototypeMethod(fnTp, "setMotorSpeed", setMotorSpeed); 68 | SetPrototypeMethod(fnTp, "getSampleRate", getSampleRate); 69 | SetPrototypeMethod(fnTp, "setSampleRate", setSampleRate); 70 | SetPrototypeMethod(fnTp, "reset", reset); 71 | 72 | const auto fn = Nan::GetFunction(fnTp).ToLocalChecked(); 73 | constructor().Reset(fn); 74 | 75 | Nan::Set(target, whoami, fn); 76 | } 77 | 78 | NAN_METHOD(Sweep::New) { 79 | // auto-detect or port, bitrate 80 | const auto simple = info.Length() == 1 && info[0]->IsString(); 81 | const auto config = info.Length() == 2 && info[0]->IsString() && info[1]->IsNumber(); 82 | 83 | if (!simple && !config) { 84 | return Nan::ThrowTypeError("No arguments for auto-detection or serial port, bitrate expected"); 85 | } 86 | 87 | if (info.IsConstructCall()) { 88 | Sweep* self = nullptr; 89 | 90 | try { 91 | const Nan::Utf8String utf8port{info[0]}; 92 | if (!(*utf8port)) { 93 | return Nan::ThrowError("UTF-8 conversion error for serial port string"); 94 | } 95 | const auto port = *utf8port; 96 | 97 | if (simple) { 98 | self = new Sweep(port); 99 | } else if (config) { 100 | const auto bitrate = Nan::To(info[1]).FromJust(); 101 | self = new Sweep(port, bitrate); 102 | } else { 103 | return Nan::ThrowError("Unable to create device"); // unreachable 104 | } 105 | } catch (const SweepError& e) { 106 | return Nan::ThrowError(e.what()); 107 | } 108 | 109 | self->Wrap(info.This()); 110 | info.GetReturnValue().Set(info.This()); 111 | } else { 112 | auto init = Nan::New(constructor()); 113 | info.GetReturnValue().Set(init->NewInstance()); 114 | } 115 | } 116 | 117 | NAN_METHOD(Sweep::startScanning) { 118 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 119 | 120 | if (info.Length() != 0) { 121 | return Nan::ThrowTypeError("No arguments expected"); 122 | } 123 | 124 | ::sweep_device_start_scanning(self->device.get(), ErrorToNanException{}); 125 | } 126 | 127 | NAN_METHOD(Sweep::stopScanning) { 128 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 129 | 130 | if (info.Length() != 0) { 131 | return Nan::ThrowTypeError("No arguments expected"); 132 | } 133 | 134 | ::sweep_device_stop_scanning(self->device.get(), ErrorToNanException{}); 135 | } 136 | 137 | class AsyncScanWorker final : public Nan::AsyncWorker { 138 | public: 139 | AsyncScanWorker(Nan::Callback* callback, std::shared_ptr<::sweep_device> device) 140 | : Nan::AsyncWorker(callback), device{std::move(device)} {} 141 | 142 | ~AsyncScanWorker() {} 143 | 144 | // Executed inside the worker-thread. 145 | void Execute() { 146 | // Note: do not throw here (ErrorTo*) - Nan::AsyncWorker interface provides special SetErrorMessage 147 | ::sweep_error_s error = nullptr; 148 | scan = ::sweep_device_get_scan(device.get(), &error); 149 | 150 | if (error) { 151 | SetErrorMessage(::sweep_error_message(error)); 152 | ::sweep_error_destruct(error); 153 | } 154 | } 155 | 156 | // Executed when the async work is complete 157 | void HandleOKCallback() { 158 | Nan::HandleScope scope; 159 | 160 | auto n = ::sweep_scan_get_number_of_samples(scan); 161 | auto samples = Nan::New(n); 162 | 163 | for (int32_t i = 0; i < n; ++i) { 164 | const auto angle = Nan::New(::sweep_scan_get_angle(scan, i)); 165 | const auto distance = Nan::New(::sweep_scan_get_distance(scan, i)); 166 | const auto signal = Nan::New(::sweep_scan_get_signal_strength(scan, i)); 167 | 168 | const auto anglekey = Nan::New("angle").ToLocalChecked(); 169 | const auto distancekey = Nan::New("distance").ToLocalChecked(); 170 | const auto signalkey = Nan::New("signal").ToLocalChecked(); 171 | 172 | // sample = {'angle': 360, 'distance': 20, 'signal': 1} 173 | const auto sample = Nan::New(); 174 | Nan::Set(sample, anglekey, angle).FromJust(); 175 | Nan::Set(sample, distancekey, distance).FromJust(); 176 | Nan::Set(sample, signalkey, signal).FromJust(); 177 | 178 | Nan::Set(samples, i, sample).FromJust(); 179 | } 180 | 181 | const constexpr auto argc = 2u; 182 | v8::Local argv[argc] = {Nan::Null(), samples}; 183 | 184 | callback->Call(argc, argv); 185 | ; 186 | } 187 | 188 | private: 189 | std::shared_ptr<::sweep_device> device; 190 | ::sweep_scan_s scan; 191 | }; 192 | 193 | NAN_METHOD(Sweep::scan) { 194 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 195 | 196 | if (info.Length() != 1 || !info[0]->IsFunction()) { 197 | return Nan::ThrowTypeError("Callback expected"); 198 | } 199 | 200 | auto* callback = new Nan::Callback(info[0].As()); 201 | Nan::AsyncQueueWorker(new AsyncScanWorker(callback, self->device)); 202 | } 203 | 204 | NAN_METHOD(Sweep::getMotorReady) { 205 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 206 | 207 | if (info.Length() != 0) { 208 | return Nan::ThrowTypeError("No arguments expected"); 209 | } 210 | 211 | const auto ready = ::sweep_device_get_motor_ready(self->device.get(), ErrorToNanException{}); 212 | 213 | info.GetReturnValue().Set(Nan::New(ready)); 214 | } 215 | 216 | NAN_METHOD(Sweep::getMotorSpeed) { 217 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 218 | 219 | if (info.Length() != 0) { 220 | return Nan::ThrowTypeError("No arguments expected"); 221 | } 222 | 223 | const auto speed = ::sweep_device_get_motor_speed(self->device.get(), ErrorToNanException{}); 224 | 225 | info.GetReturnValue().Set(Nan::New(speed)); 226 | } 227 | 228 | NAN_METHOD(Sweep::setMotorSpeed) { 229 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 230 | 231 | if (info.Length() != 1 && !info[0]->IsNumber()) { 232 | return Nan::ThrowTypeError("Motor speed in Hz as number expected"); 233 | } 234 | 235 | const auto speed = Nan::To(info[0]).FromJust(); 236 | 237 | ::sweep_device_set_motor_speed(self->device.get(), speed, ErrorToNanException{}); 238 | } 239 | 240 | NAN_METHOD(Sweep::getSampleRate) { 241 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 242 | 243 | if (info.Length() != 0) { 244 | return Nan::ThrowTypeError("No arguments expected"); 245 | } 246 | 247 | const auto rate = ::sweep_device_get_sample_rate(self->device.get(), ErrorToNanException{}); 248 | 249 | info.GetReturnValue().Set(Nan::New(rate)); 250 | } 251 | 252 | NAN_METHOD(Sweep::setSampleRate) { 253 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 254 | 255 | if (info.Length() != 1 && !info[0]->IsNumber()) { 256 | return Nan::ThrowTypeError("Sample rate in Hz as number expected"); 257 | } 258 | 259 | const auto rate = Nan::To(info[0]).FromJust(); 260 | 261 | ::sweep_device_set_sample_rate(self->device.get(), rate, ErrorToNanException{}); 262 | } 263 | 264 | NAN_METHOD(Sweep::reset) { 265 | auto* const self = Nan::ObjectWrap::Unwrap(info.Holder()); 266 | 267 | if (info.Length() != 0) { 268 | return Nan::ThrowTypeError("No arguments expected"); 269 | } 270 | 271 | ::sweep_device_reset(self->device.get(), ErrorToNanException{}); 272 | } 273 | 274 | Nan::Persistent& Sweep::constructor() { 275 | static Nan::Persistent init; 276 | return init; 277 | } 278 | -------------------------------------------------------------------------------- /libsweep/src/win/serial.cc: -------------------------------------------------------------------------------- 1 | #include "serial.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace sweep { 12 | namespace serial { 13 | 14 | struct device { 15 | HANDLE h_comm; 16 | OVERLAPPED os_reader; 17 | bool waiting_on_read; // Used to prevent creation of new read operation if one is outstanding 18 | DWORD read_timeout_millis; // timeout interval for entire read operation 19 | }; 20 | 21 | static int32_t detail_get_port_number(const char* port) { 22 | SWEEP_ASSERT(port); 23 | 24 | if (strlen(port) <= 3) 25 | throw error{"invalid port name"}; 26 | 27 | auto parsed_int = std::strtoll(port + 3, nullptr, 10); 28 | 29 | if (parsed_int >= 0 && parsed_int <= 255) 30 | return static_cast(parsed_int); 31 | else 32 | throw error{"invalid port name"}; 33 | 34 | return -1; 35 | } 36 | 37 | device_s device_construct(const char* port, int32_t bitrate) { 38 | SWEEP_ASSERT(port); 39 | SWEEP_ASSERT(bitrate > 0); 40 | 41 | if (bitrate != 115200) 42 | throw error{"baud rate is not supported"}; 43 | 44 | const auto port_num = detail_get_port_number(port); 45 | 46 | // Construct formal serial port name 47 | std::string port_name{"\\\\.\\COM"}; 48 | port_name += std::to_string(port_num); 49 | 50 | // try to open the port 51 | auto h_comm = CreateFile(port_name.c_str(), // port name 52 | GENERIC_READ | GENERIC_WRITE, // read/write 53 | 0, // No Sharing (serial ports can't be shared) 54 | NULL, // No Security 55 | OPEN_EXISTING, // Open existing port only 56 | 0, // Non Overlapped I/O 57 | NULL); // Null for serial ports 58 | 59 | if (h_comm == INVALID_HANDLE_VALUE) 60 | throw error{"opening serial port failed"}; 61 | 62 | // retrieve the current comm state 63 | DCB dcb_serial_params; 64 | dcb_serial_params.DCBlength = sizeof(dcb_serial_params); 65 | if (!GetCommState(h_comm, &dcb_serial_params)) { 66 | CloseHandle(h_comm); 67 | throw error{"retrieving current serial port state failed"}; 68 | } 69 | 70 | // set the parameters to match the uART settings from the Sweep Comm Protocol 71 | dcb_serial_params.BaudRate = CBR_115200; // BaudRate = 115200 (115.2kb/s) 72 | dcb_serial_params.ByteSize = 8; // ByteSize = 8 73 | dcb_serial_params.StopBits = ONESTOPBIT; // # StopBits = 1 74 | dcb_serial_params.Parity = NOPARITY; // Parity = None 75 | dcb_serial_params.fDtrControl = DTR_CONTROL_DISABLE; 76 | 77 | // set the serial port parameters to the specified values 78 | if (!SetCommState(h_comm, &dcb_serial_params)) { 79 | CloseHandle(h_comm); 80 | throw error{"setting serial port parameters failed"}; 81 | } 82 | 83 | // specify timeouts (all values in milliseconds) 84 | COMMTIMEOUTS timeouts; 85 | if (!GetCommTimeouts(h_comm, &timeouts)) { 86 | CloseHandle(h_comm); 87 | throw error{"retrieving current serial port timeouts failed"}; 88 | } 89 | timeouts.ReadIntervalTimeout = 50; // max time between arrival of two bytes before ReadFile() returns 90 | timeouts.ReadTotalTimeoutConstant = 50; // used to calculate total time-out period for read operations 91 | timeouts.ReadTotalTimeoutMultiplier = 10; // used to calculate total time-out period for read operations 92 | timeouts.WriteTotalTimeoutConstant = 50; // used to calculate total time-out period for write operations 93 | timeouts.WriteTotalTimeoutMultiplier = 10; // used to calculate total time-out period for write operations 94 | 95 | // set the timeouts 96 | if (!SetCommTimeouts(h_comm, &timeouts)) { 97 | CloseHandle(h_comm); 98 | throw error{"setting serial port timeouts failed"}; 99 | } 100 | 101 | // create the overlapped event handle for reading 102 | OVERLAPPED os_reader = {0}; 103 | 104 | // Create the overlapped read event. Must be closed before exiting 105 | os_reader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 106 | if (os_reader.hEvent == NULL) { 107 | CloseHandle(h_comm); 108 | throw error{"creating overlapped read event failed"}; 109 | } 110 | 111 | // set the comm mask 112 | if (!SetCommMask(h_comm, EV_RXCHAR | EV_ERR)) { 113 | CloseHandle(h_comm); 114 | CloseHandle(os_reader.hEvent); 115 | throw error{"setting comm mask failed"}; 116 | } 117 | 118 | // purge the comm port of any pre-existing data or errors 119 | if (!PurgeComm(h_comm, PURGE_RXABORT | PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR)) { 120 | CloseHandle(h_comm); 121 | CloseHandle(os_reader.hEvent); 122 | throw error{"flushing serial port failed during serial device construction"}; 123 | } 124 | 125 | // create the serial device 126 | auto out = new device{h_comm, os_reader, FALSE, 500}; 127 | 128 | return out; 129 | } 130 | 131 | void device_destruct(device_s serial) { 132 | SWEEP_ASSERT(serial); 133 | 134 | try { 135 | device_flush(serial); 136 | } catch (...) { 137 | // nothing we can do here 138 | } 139 | 140 | // close the serial port 141 | CloseHandle(serial->h_comm); 142 | 143 | // close the overlapped read event 144 | CloseHandle(serial->os_reader.hEvent); 145 | 146 | delete serial; 147 | } 148 | 149 | void device_read(device_s serial, void* to, int32_t len) { 150 | SWEEP_ASSERT(serial); 151 | SWEEP_ASSERT(to); 152 | SWEEP_ASSERT(len >= 0); 153 | 154 | DWORD dw_num_read; 155 | DWORD dw_num_to_read = (DWORD)len; 156 | bool f_result = false; 157 | 158 | if (!serial->waiting_on_read) { 159 | // Issue read operation. 160 | if (!ReadFile(serial->h_comm, (unsigned char*)to, dw_num_to_read, &dw_num_read, &(serial->os_reader))) { 161 | // If the read did not return immediately, check if it was pending 162 | if (GetLastError() != ERROR_IO_PENDING) { 163 | // Error in communications; report it. 164 | throw error{"reading from serial device failed"}; 165 | } else { 166 | serial->waiting_on_read = true; 167 | } 168 | } else { 169 | // read completed immediately 170 | f_result = true; 171 | } 172 | } 173 | 174 | DWORD dwRes; 175 | 176 | // If the read is pending, wait for it to finish... but permit timeout 177 | if (serial->waiting_on_read) { 178 | dwRes = WaitForSingleObject(serial->os_reader.hEvent, serial->read_timeout_millis); 179 | switch (dwRes) { 180 | // Read completed. 181 | case WAIT_OBJECT_0: 182 | if (!GetOverlappedResult(serial->h_comm, &(serial->os_reader), &dw_num_read, FALSE)) { 183 | // Error in communications; report it. 184 | throw error{"error in communications during serial read"}; 185 | } else { 186 | // Read completed successfully. 187 | f_result = true; 188 | } 189 | 190 | // Reset flag so that another opertion can be issued. 191 | serial->waiting_on_read = false; 192 | break; 193 | 194 | case WAIT_TIMEOUT: 195 | // Operation isn't complete yet. serial->waiting_on_read flag isn't changed since we'll loop back 196 | // around, and we don't want to issue another read until the first one finishes. 197 | break; 198 | 199 | default: 200 | // Error in the WaitForSingleObject; abort. 201 | // Indicates a problem with the OVERLAPPED structure's event handle. 202 | throw error{"problem with overlapped structure during serial read"}; 203 | break; 204 | } 205 | } 206 | 207 | // Check that the number of bytes actually read is equal to the requested quantity 208 | if (f_result == true) 209 | f_result = (dw_num_read == (DWORD)len); 210 | 211 | SWEEP_ASSERT(f_result && "reliable read failed to read requested number of bytes"); 212 | if (!f_result) 213 | throw error{"reading from serial device failed"}; 214 | } 215 | 216 | void device_write(device_s serial, const void* from, int32_t len) { 217 | SWEEP_ASSERT(serial); 218 | SWEEP_ASSERT(from); 219 | SWEEP_ASSERT(len >= 0); 220 | 221 | DWORD err = 0; 222 | OVERLAPPED os_write = {0}; 223 | DWORD dw_written; 224 | DWORD dw_to_write = (DWORD)len; 225 | bool f_result; 226 | 227 | // Create this write's OVERLAPPED structure hEvent. 228 | os_write.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 229 | if (os_write.hEvent == NULL) { 230 | // Error creating overlapped event handle. 231 | throw error{"creating overlapped event handle failed during serial port write"}; 232 | } 233 | 234 | // check for a comm error (clears any error flag present) 235 | if (!ClearCommError(serial->h_comm, &err, NULL)) { 236 | throw error{"checking for/clearing comm error failed during serial port write"}; 237 | } 238 | // in the event of a comm error, purge before writing 239 | if (err > 0) { 240 | if (!PurgeComm(serial->h_comm, PURGE_TXABORT | PURGE_TXCLEAR)) { 241 | throw error{"purging tx buffer failed during serial port write"}; 242 | } 243 | } 244 | 245 | // Issue write. 246 | if (!WriteFile(serial->h_comm, (const unsigned char*)from, dw_to_write, &dw_written, &os_write)) { 247 | if (GetLastError() != ERROR_IO_PENDING) { 248 | // WriteFile failed, but it isn't delayed. Report error and abort. 249 | throw error{"WriteFile failed, but is not delayed during serial port write"}; 250 | } else { 251 | // Write is pending indefinitely (ie: fWait param is TRUE, so this is effectively NON-OVERLAPPED io) 252 | if (!GetOverlappedResult(serial->h_comm, &os_write, &dw_written, TRUE)) { 253 | throw error{"Failed to get result during serial port write"}; 254 | } else { 255 | // Write operation completed successfully. 256 | f_result = true; 257 | } 258 | } 259 | } else { 260 | // WriteFile completed immediately. 261 | f_result = true; 262 | } 263 | 264 | // Check that the number of bytes actually transferred is equal to the requested quantity 265 | if (dw_written != (DWORD)len) 266 | f_result = false; 267 | 268 | // Close the overlapped event handle 269 | CloseHandle(os_write.hEvent); 270 | 271 | SWEEP_ASSERT(f_result && "reliable write failed to write requested number of bytes"); 272 | if (f_result == false) 273 | throw error{"writing to serial device failed"}; 274 | } 275 | 276 | void device_flush(device_s serial) { 277 | SWEEP_ASSERT(serial); 278 | 279 | DWORD err = 0; 280 | // check for a comm error (clears any error flag present) 281 | if (!ClearCommError(serial->h_comm, &err, NULL)) { 282 | throw error{"checking for/clearing comm error failed during serial port flush"}; 283 | } 284 | 285 | // flush serial port 286 | // - Empty Tx and Rx buffers 287 | // - Abort any pending read/write operations 288 | if (!PurgeComm(serial->h_comm, PURGE_RXABORT | PURGE_TXABORT | PURGE_RXCLEAR | PURGE_TXCLEAR)) { 289 | throw error{"flushing serial port failed"}; 290 | } 291 | } 292 | 293 | } // ns serial 294 | } // ns sweep 295 | -------------------------------------------------------------------------------- /libsweep/README.md: -------------------------------------------------------------------------------- 1 | # libsweep 2 | 3 | Low-level Scanse Sweep LiDAR library. Comes as C99 library `sweep.h` with optional C++11 header `sweep.hpp` on top of it. 4 | 5 | 6 | 7 | ### Quick Start 8 | #### Linux 9 | 10 | ```bash 11 | mkdir -p build 12 | cd build 13 | cmake .. -DCMAKE_BUILD_TYPE=Release 14 | cmake --build . 15 | sudo cmake --build . --target install 16 | sudo ldconfig 17 | ``` 18 | 19 | If you don't have a Sweep device yet you can build a dummy `libsweep.so` always returning static point cloud data: 20 | 21 | ```bash 22 | cmake .. -DCMAKE_BUILD_TYPE=Release -DDUMMY=On 23 | ``` 24 | 25 | This dummy library is API and ABI compatible. Once your device arrives switch out the `libsweep.so` shared library and you're good to go. 26 | 27 | 28 | #### Windows 29 | 30 | For Windows users open a command prompt with administrative access: 31 | 32 | ```bash 33 | mkdir build 34 | cd build 35 | cmake .. -G "Visual Studio 14 2015 Win64" 36 | cmake --build . --config Release 37 | cmake --build . --target install --config Release 38 | ``` 39 | 40 | The above command assumes Visual Studio 2015. If you have a different version installed, change the value. ie: 41 | 42 | Visual Studio 11 2012 Win64 = Generates Visual Studio 11 (VS 2012) project files for x64 architecture 43 | Visual Studio 12 2013 Win64 = Generates Visual Studio 12 (VS 2013) project files for x64 architecture 44 | Visual Studio 14 2015 Win64 = Generates Visual Studio 14 (VS 2015) project files for x64 architecture 45 | Visual Studio 15 2017 Win64 = Generates Visual Studio 15 (VS 2017) project files for x64 architecture 46 | 47 | Additionally, the above commands assume you want to build a x64 (64bit) verison of the library. To build a x86 (32 bit) version, simply drop the `Win64`. i.e.: 48 | 49 | Visual Studio 14 2015 = Generates Visual Studio 14 (VS 2015) project files for x86 architecture 50 | 51 | To build the dummy library add the dummy flag to the command: 52 | 53 | ```bash 54 | cmake .. -DDUMMY=On -G "Visual Studio 14 2015 Win64" 55 | ``` 56 | 57 | Then be sure to add the installation directories for the library and the header files to the environment `PATH` variable. For the above installation you'd have to add something like the following: 58 | - `C:\Program Files\sweep\lib` for the library 59 | - `C:\Program Files\sweep\include`for the headers 60 | 61 | You may have to restart the computer before the changes take effect. 62 | 63 | Lastly, if you are on windows you will have to adjust the settings for the COM port you are using in order to communicate properly with the sweep sensor. Follow [this guide](https://support.scanse.io/hc/en-us/articles/115000793208-Changing-the-USB-Adapter-Latency-Timer-and-Byte-Size-Setting-on-Windows). Adjusting settings only has to be done once for a given COM port, and windows will remember the settings. 64 | 65 | 66 | #### FreeBSD 67 | 68 | On FreeBSD, using `pkg(8)` is the easiest and fastest way to get libsweep on your machine: 69 | 70 | ```sh 71 | # Requires ports-mgmt/pkg and security/sudo to be pre-installed. 72 | # Or, just issue these commands as root without sudo (NOT recommended!) 73 | sudo pkg update 74 | sudo pkg install libsweep-lidar 75 | ``` 76 | 77 | In case you prefer to build everything from the source or do not have the actual device yet, building from `ports(7)` tree is another way: 78 | 79 | ```sh 80 | # Requires security/sudo to be pre-installed. 81 | # Or, just issue these commands as root without sudo (NOT recommended!) 82 | sudo portsnap auto 83 | cd /usr/ports/misc/libsweep-lidar 84 | sudo make config # Enable NO_DEVICE build option if you don't have the actual device yet. 85 | sudo make install clean 86 | ``` 87 | 88 | 89 | ### Usage 90 | 91 | - Include `` for the C interface or `` for the C++ interface. 92 | - Link `libsweep.so` with `-lsweep`. 93 | - On FreeBSD, be sure to specify library and header location: `-I/usr/local/include -L/usr/local/lib` 94 | 95 | For example: 96 | 97 | ```sh 98 | # Linux 99 | gcc -Wall -Wextra -pedantic -std=c99 examples/example.c -lsweep 100 | g++ -Wall -Wextra -pedantic -std=c++11 examples/example.cc -lsweep 101 | # FreeBSD 102 | cc -Weverything -std=c99 -I/usr/local/include -L/usr/local/lib -lsweep examples/example.c 103 | c++ -Weverything -std=c++11 -I/usr/local/include -L/usr/local/lib -lsweep examples/example.cc 104 | ``` 105 | 106 | In addition, we provide CMake integration. In your `CMakeLists.txt`: 107 | 108 | find_package(Sweep REQUIRED) 109 | target_link_libraries(.. ${LIBSWEEP_LIBRARY}) 110 | target_include_directories(.. ${LIBSWEEP_INCLUDE_DIR}) 111 | 112 | See [example.c](examples/example.c) and [example.cc](examples/example.cc) for a C and C++ example, respectively. 113 | 114 | 115 | ### libsweep 116 | 117 | Before jumping into details, here are the library's main design ideas: 118 | 119 | - Only opaque pointers (think typed `void *`) and plain C types in the API. This is how we accomplish ABI compatibility. 120 | - No global state: `_s` context objects (`_s` since POSIX reserves `_t`) hold state and have to be passed explicitly. 121 | - Make ownership and lifetimes explicit: all objects have to be `_construct()`ed for creation and successfully created objects have to be `_destruct()`ed for cleanup. 122 | - Sane defaults and user-friendly API: when possible we provide `_simple()` functions e.g. doing serial port auto-detection. 123 | - Fixed-size and signed integers for portability and runtime checking (think sanitizers). 124 | 125 | Table of Contents: 126 | - [Firmware Compatibility](#firmware-compatibility) 127 | - [Version and ABI Management](#version-and-abi-management) 128 | - [Error Handling](#error-handling) 129 | - [Device Interaction](#device-interaction) 130 | - [Full 360 Degree Scan](#full-360-degree-scan) 131 | - [Additional Information](#additional-information) 132 | 133 | #### Firmware Compatibility 134 | | libsweep | sweep firmware | 135 | | -------- | :------------: | 136 | | v1.2.0+ | v1.4 | 137 | | v1.1.1 | v1.2 | 138 | | v1.1.0 | v1.1 | 139 | | v0.x.x | v1.0 | 140 | 141 | You can check the firmware version installed on your sweep device by using a serial terminal (see [manual](https://s3.amazonaws.com/scanse/Sweep_user_manual.pdf)) or more easily using the sweep visualizer (see [instructions](https://support.scanse.io/hc/en-us/articles/224557908-Upgrading-Firmware)). 142 | 143 | 144 | #### Version And ABI Management 145 | 146 | ```c++ 147 | SWEEP_VERSION_MAJOR 148 | ``` 149 | 150 | Interface major version number. 151 | 152 | ```c++ 153 | SWEEP_VERSION_MINOR 154 | ``` 155 | 156 | Interface minor version number. 157 | 158 | ```c++ 159 | SWEEP_VERSION 160 | ``` 161 | 162 | Combined interface major and minor version number. 163 | 164 | ```c++ 165 | int32_t sweep_get_version(void) 166 | ``` 167 | 168 | Returns the library's version (see `SWEEP_VERSION`). 169 | Used for ABI compatibility checks. 170 | 171 | ```c++ 172 | bool sweep_is_abi_compatible(void) 173 | ``` 174 | 175 | Returns true if the library is ABI compatible. 176 | This check is done by comparing the interface header with the installed library's version. 177 | 178 | 179 | #### Error Handling 180 | 181 | ```c++ 182 | SWEEP_ASSERT 183 | ``` 184 | 185 | Optionally define this for custom assertion handling. 186 | Uses `assert` from `` by default. 187 | 188 | ```c++ 189 | sweep_error_s 190 | ``` 191 | 192 | Opaque type representing an error. 193 | You can get a human readable representation using the `sweep_error_message` function. 194 | You have to destruct an error object with `sweep_error_destruct`. 195 | 196 | ```c++ 197 | const char* sweep_error_message(sweep_error_s error) 198 | ``` 199 | 200 | Human readable representation for an error. 201 | 202 | ```c++ 203 | void sweep_error_destruct(sweep_error_s error) 204 | ``` 205 | 206 | Destructs an `sweep_error_s` object. 207 | 208 | 209 | #### Device Interaction 210 | 211 | ```c++ 212 | sweep_device_s 213 | ``` 214 | 215 | Opaque type representing a Sweep device. 216 | All direct device interaction happens on this type. 217 | 218 | ```c++ 219 | sweep_device_s sweep_device_construct_simple(const char* port, sweep_error_s* error) 220 | ``` 221 | 222 | Constructs a `sweep_device_s` based on a serial device port (e.g. `/dev/ttyUSB0` on Linux or `COM5` on Windows). 223 | In case of error a `sweep_error_s` will be written into `error`. 224 | 225 | ```c++ 226 | sweep_device_s sweep_device_construct(const char* port, int32_t bitrate, sweep_error_s* error) 227 | ``` 228 | 229 | Constructs a `sweep_device_s` with explicit hardware configuration. 230 | In case of error a `sweep_error_s` will be written into `error`. 231 | 232 | ```c++ 233 | void sweep_device_destruct(sweep_device_s device) 234 | ``` 235 | 236 | Destructs an `sweep_device_s` object. 237 | 238 | ```c++ 239 | void sweep_device_start_scanning(sweep_device_s device, sweep_error_s* error) 240 | ``` 241 | 242 | Signals the `sweep_device_s` to start scanning. 243 | If the motor is stationary (0Hz), will automatically set motor speed to default 5Hz. 244 | Will block until the device is ready (calibration routine complete + motor speed is stable). 245 | Starts internal background thread to accumulate and queue up scans. Scans can then be retrieved using `sweep_device_get_scan`. 246 | In case of error a `sweep_error_s` will be written into `error`. 247 | 248 | 249 | ```c++ 250 | void sweep_device_stop_scanning(sweep_device_s device, sweep_error_s* error) 251 | ``` 252 | 253 | Signals the `sweep_device_s` to stop scanning. 254 | Blocks for ~35ms to allow time for the trailing data stream to collect and flush internally, before sending a second stop command and validate the response. 255 | In case of error a `sweep_error_s` will be written into `error`. 256 | 257 | ```c++ 258 | bool sweep_device_get_motor_ready(sweep_device_s device, sweep_error_s* error) 259 | ``` 260 | 261 | Returns `true` if the device is ready. A device is ready when the motor speed has stabilized to the current setting, and the calibration routine is complete. For visual reference, the blue LED on the device will blink unil the device is ready. This method is useful when the device is powered on, or when adjusting motor speed. If the device is NOT ready, it will respond to certain commands (`DS` or `MS`) with a status code indicating a failure to execute the command. 262 | In case of error a `sweep_error_s` will be written into `error`. 263 | 264 | ```c++ 265 | int32_t sweep_device_get_motor_speed(sweep_device_s device, sweep_error_s* error) 266 | ``` 267 | 268 | Returns the `sweep_device_s`'s motor speed setting in Hz. 269 | If the motor speed is currently changing, the returned motor speed is the target speed at which the device will stabilize. 270 | In case of error a `sweep_error_s` will be written into `error`. 271 | 272 | ```c++ 273 | void sweep_device_set_motor_speed(sweep_device_s device, int32_t hz, sweep_error_s* error) 274 | ``` 275 | 276 | Sets the `sweep_device_s`'s motor speed in Hz. 277 | Blocks until prior motor speed has stabilized. 278 | The device supports speeds of 0 Hz to 10 Hz, but be careful that the device is not set at 0Hz before calling `sweep_device_start_scanning`. 279 | In case of error a `sweep_error_s` will be written into `error`. 280 | 281 | 282 | ```c++ 283 | int32_t sweep_device_get_sample_rate(sweep_device_s device, sweep_error_s* error) 284 | ``` 285 | 286 | Returns the `sweep_device_s`'s sample rate in Hz. 287 | In case of error a `sweep_error_s` will be written into `error`. 288 | 289 | ```c++ 290 | void sweep_device_set_sample_rate(sweep_device_s device, int32_t hz, sweep_error_s* error) 291 | ``` 292 | 293 | Sets the `sweep_device_s`'s sample rate in Hz. 294 | The device supports setting sample rate to the following values: 500 Hz, 750 Hz and 1000 Hz. 295 | These sample rates are not exact. They are general ballpark values. The actual sample rate may differ slightly. 296 | In case of error a `sweep_error_s` will be written into `error`. 297 | 298 | ```c++ 299 | void sweep_device_reset(sweep_device_s device, sweep_error_s* error) 300 | ``` 301 | 302 | Resets the `sweep_device_s` hardware. 303 | In case of error a `sweep_error_s` will be written into `error`. 304 | 305 | 306 | #### Full 360 Degree Scan 307 | 308 | ```c++ 309 | sweep_scan_s 310 | ``` 311 | 312 | Opaque type representing a single full 360 degree scan from a `sweep_device_s`. 313 | 314 | ```c++ 315 | sweep_scan_s sweep_device_get_scan(sweep_device_s device, sweep_error_s* error) 316 | ``` 317 | 318 | Returns the ordered readings (1st to last) from a single scan. 319 | Retrieves the oldest scan from a queue of scans accumulated in a background thread. Blocks until a scan is available. To be used after calling `sweep_device_start_scanning`. 320 | In case of error a `sweep_error_s` will be written into `error`. 321 | 322 | 323 | ```c++ 324 | void sweep_scan_destruct(sweep_scan_s scan) 325 | ``` 326 | 327 | Destructs a `sweep_scan_s` object. 328 | 329 | ```c++ 330 | int32_t sweep_scan_get_number_of_samples(sweep_scan_s scan) 331 | ``` 332 | 333 | Returns the number of samples in a full 360 degree `sweep_scan_s`. 334 | 335 | ```c++ 336 | int32_t sweep_scan_get_angle(sweep_scan_s scan, int32_t sample) 337 | ``` 338 | 339 | Returns the angle in milli-degree for the `sample`th sample in the `sweep_scan_s`. 340 | 341 | ```c++ 342 | int32_t sweep_scan_get_distance(sweep_scan_s scan, int32_t sample) 343 | ``` 344 | 345 | Returns the distance in centi-meter for the `sample`th sample in the `sweep_scan_s`. 346 | 347 | ```c++ 348 | int32_t sweep_scan_get_signal_strength(sweep_scan_s scan, int32_t sample) 349 | ``` 350 | 351 | Returns the signal strength (0 low -- 255 high) for the `sample`th sample in the `sweep_scan_s`. 352 | 353 | 354 | #### Additional Information 355 | It is recommended that you read through the sweep [Theory of Operation](https://support.scanse.io/hc/en-us/articles/115006333327-Theory-of-Operation) and [Best Practices](https://support.scanse.io/hc/en-us/articles/115006055388-Best-Practices). 356 | 357 | 358 | ### License 359 | 360 | Copyright © 2016 Daniel J. Hofmann 361 | 362 | Copyright © 2016 Scanse LLC 363 | 364 | Distributed under the MIT License (MIT). 365 | -------------------------------------------------------------------------------- /libsweep/doc/serial_protocol_spec.md: -------------------------------------------------------------------------------- 1 | # Communication Protocol Spec: 2 | This document outlines the communication protocol for the Scanse Sweep scanning LiDAR. For more information visit [scanse.io](http://scanse.io/). 3 | 4 | --- 5 | ### Interface 6 | 7 | **UART Settings**: 8 | - Bit Rate :115.2 Kbps (115200) 9 | - Parity :None 10 | - Data Bit :8 11 | - Stop Bit :1 12 | - Flow Control :None 13 | 14 | --- 15 | ### Measurement Direction and Data Points 16 | 17 | `Rotation Direction`: Counterclockwise 18 | `Detection Range`: 360 degrees 19 | 20 | --- 21 | ### Naming Convention 22 | `Sensor`: Sweep Device 23 | `Host`:Controller (computer, microcontroller etc) 24 | 25 | In general, Sweep's communication protocol can be thought of as a set of `commands` and `receipts`. 26 | `Command:` the term given to messages sent from the host to the sensor (host->sensor) 27 | `Receipt:` the term given to messages sent from the sensor to the host (sensor -> host). 28 | 29 | --- 30 | ### Data Encoding and Decoding 31 | 32 | The protocol design specifies that all transmitted bytes (both commands and receipts) are legible ASCII characters, in addition to CR and LF. This means that numeric codes (ex: motor speed code '05') are also transmitted as ASCII characters, and not decimal values. This design was chosen so that commands and receipts could be read directly from the terminal. 33 | 34 | **Note:** The only exception is the `Data Block` receipt, which must permit arbitrary byte values. See the Data Block receipt section for encoding. 35 | 36 | --- 37 | ### General Communication Format 38 | 39 | #### (HOST -> SENSOR) 40 | Command with no parameter 41 | 42 | Command Symbol (2 bytes) | Line Feed(LF) 43 | | --- | --- | 44 | 45 | Command with parameter 46 | 47 | Command Symbol (2 bytes) | Parameter (2 bytes) | Line Feed(LF) 48 | | --- | --- | ---| 49 | 50 | 51 | #### (SENSOR -> HOST) 52 | 53 | Response with no parameter echoed 54 | 55 | Command Symbol (2 bytes) | Status (2 bytes) | Sum of Status | Line Feed(LF) 56 | | --- | --- | ---| --- | 57 | 58 | Response with parameter echoed 59 | 60 | Command Symbol (2 bytes) | Parameter (2 bytes) | Line Feed(LF) | Status (2 bytes) | Sum of Status | Line Feed(LF) 61 | | --- | --- | ---| --- | --- | --- | 62 | 63 | - `Command Symbol`: 2 byte code at the beginning of every command (ASCII) 64 | - `Parameter`: Information that is needed to change sensor settings (ASCII).. ex: a motor speed code 05 transmitted as ASCII parameter '05', which has byte values [48, 53] in decimal. 65 | - `Line Feed (LF) or Carriage Return (CR)`: Terminating code. Command can have LF or CR or both as termination code but receipt will always have LF as its termination code. 66 | - `Status`: 2 bytes of data used to convey the normal/abnormal processing of a command. ASCII byte values of `'00'` or `'99'` indicate that the sensor received and processed the command normally. Value of `'11'` specifies an invalid parameter was included in the command. Any other byte values are reserved for errors, which will convey that an undefined, invalid or incomplete command was received by the sensor. 67 | - `Sum of Status`: 1 byte of data used to check for corrupted transmission. See Appendix for instructions for authenticating receipts. 68 | 69 | --- 70 | ## Sensor Commands 71 | 72 | **DS** - Start data acquisition 73 | **DX** - Stop data acquisition 74 | **MS** - Adjust Motor Speed 75 | **LR** - Adjust LiDAR Sample Rate 76 | **LI** - LiDAR Info 77 | **MI** - Motor Information 78 | **MZ** - Motor Ready 79 | **IV** - Version Info 80 | **ID** - Device Info 81 | **RR** - Reset Device 82 | 83 | --- 84 | 85 | ## DS - Start data acquisition 86 | * Initiates scanning 87 | * Sensor responds with header containing status. 88 | * Sensor begins sending constant stream of Data Blocks, each containing a single sensor readings. The stream continues indenfinitely until the host sends a DX command. 89 | 90 | ### (HOST -> SENSOR) 91 | 92 | D | S | LF 93 | | --- | --- | ---| 94 | 95 | ### (SENSOR -> HOST) 96 | Header response 97 | 98 | D | S | Status | SUM | LF 99 | | --- | --- | ---| --- | --- | 100 | 101 | DS command is not guaranteed to succeed. There are a few conditions where it will fail. In the event of a failure, the two status bytes are used to communicate the failure. 102 | 103 | Status Code (2 byte ASCII code): 104 | - `'00'`: Successfully processed command. Data acquisition effectively initiated. 105 | - `'12'`: Failed to process command. Motor speed has not yet stabilized. Data acquisition NOT initiated. Wait until motor speed has stabilized before trying again. 106 | - `'13'`: Failed to process command. Motor is currently stationary (0Hz). Data acquisition NOT initiated. Adjust motor speed before trying again. 107 | 108 | ### (SENSOR -> HOST) 109 | Data Block (7 bytes) - repeat indefinitely 110 | 111 | sync/error (1byte) | Azimuth - degrees(float) (2bytes) | Distance - cm(int) (2bytes) | Signal Strength (1byte) | Checksum (1byte) 112 | | ------ | ------ | ------ | ------ | ------ | 113 | 114 | ### Data Block Structure: 115 | The `Data Block` receipt is 7 bytes long and contains all the information about a single sensor reading. 116 | 117 | - **Sync/Error Byte**: The sync/error byte is multi-purpose, and encodes information about the rotation of the Sweep sensor, as well as any error information. Consider the individuals bits: 118 | 119 | e6 | e5 | e4 | e3 | e2 | e1 | e0 | sync 120 | | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | 121 | 122 | - **Sync bit**: least significant bit (LSB) which carries the sync value. A value of `1` indicates that this Data Block is the first acquired sensor reading since the sensor passed the 0 degree mark. Value of `0` indicates all other measurement packets. 123 | - **Error bits**: 7 most significant bits (e0-6) are reserved for error encoding. The bit `e0` indicates a communication error with the LiDAR module with the value `1`. Bits `e1:6` are reserved for future use. 124 | 125 | - **Azimuth**: Angle that ranging was recorded at (in degrees). Azimuth is transmitted as a 16 bit fixed point value with a scaling factor of 16 (4bit after radix). Note: the lower order byte is received first, higher order byte is received second. Use instructions in the Appendix. 126 | - **Distance**: Distance of range measurement (in cm). Distance is a 16 bit integer value. Note: the lower order byte is received first, higher order byte is received second. Use instructions in the Appendix. 127 | - **Signal strength** : Signal strength of current ranging measurement. Larger is better. 8-bit unsigned int, range: 0-255 128 | - **Checksum**: Calculated by adding the 6 bytes of data then dividing by 255 and keeping the remainder. (Sum of bytes 0-5) % 255 ... Use the instructions in the Appendix. 129 | 130 | 131 | 132 | --- 133 | #### DX - Stop data acquisition 134 | * Stops outputting measurement data. 135 | 136 | #### (HOST -> SENSOR) 137 | 138 | D | X | LF 139 | | --- | --- | ---| 140 | 141 | #### (SENSOR -> HOST) 142 | 143 | D | X | Status | SUM | LF 144 | | --- | --- | ---| --- | --- | 145 | 146 | --- 147 | #### MS - Adjust Motor Speed 148 | Adjusts motor speed setting to the specified code indicating a motor speed between 0Hz and 10Hz. This sets the target speed setting, but the motor will take time (~6 seconds) to adjust and stabilize to the new setting. The blue LED on the device will flash while the speed is stabilizing. 149 | 150 | #### (HOST -> SENSOR) 151 | 152 | M | S | Speed Code (2 bytes) | LF 153 | | --- | --- | ---| --- | 154 | 155 | #### (SENSOR -> HOST) 156 | 157 | M | S | Speed Code (2 bytes) | LF | Status | Sum | LF 158 | | --- | --- | ---| --- | --- | ---| --- | 159 | 160 | Speed Code (2 byte ASCII code): 161 | - `'00'` = 0Hz 162 | - `'01'` = 1Hz 163 | - `'02'` = 2Hz 164 | - `'03'` = 3Hz 165 | - `'04'` = 4Hz 166 | - `'05'` = 5Hz 167 | - `'06'` = 6Hz 168 | - `'07'` = 7Hz 169 | - `'08'` = 8Hz 170 | - `'09'` = 9Hz 171 | - `'10'`= 10Hz 172 | 173 | (Note: codes are ASCII encoded, ie: in '05' = 0x3035) 174 | 175 | MS command is not guaranteed to succeed. There are a few conditions where it will fail. In the event of a failure, the two status bytes are used to communicate the failure. 176 | 177 | Status Code (2 byte ASCII code): 178 | - `'00'`: Successfully processed command. Motor speed setting effectively changed to new value. 179 | - `'11'`: Failed to process command. The command was sent with an invalid parameter. Use a valid parameter when trying again. 180 | - `'12'`: Failed to process command. Motor speed has not yet stabilized to the previous setting. Motor speed setting NOT changed to new value. Wait until motor speed has stabilized before trying to adjust it again. 181 | 182 | --- 183 | #### LR - Adjust LiDAR Sample Rate 184 | Default Sample Rate - 500-600Hz 185 | 186 | #### (HOST -> SENSOR) 187 | 188 | L | R | Sample Rate Code (2 bytes) | LF 189 | | --- | --- | ---| --- | 190 | 191 | 192 | #### (SENSOR -> HOST) 193 | 194 | L | R | Sample Rate Code (2 bytes) | LF | Status | Sum | LF 195 | | --- | --- | ---| --- | ---| --- | --- | 196 | 197 | Sample Rate Code (2 byte ASCII code): 198 | - `'01'` = 500-600Hz 199 | - `'02'` = 750-800Hz 200 | - `'03'` = 1000-1050Hz 201 | 202 | (Note: codes are ASCII encoded, ie: '02' = 0x3032) 203 | 204 | LR command is not guaranteed to succeed. There are a few conditions where it will fail. In the event of a failure, the two status bytes are used to communicate the failure. 205 | 206 | Status Code (2 byte ASCII code): 207 | - `'00'`: Successfully processed command. Sample Rate setting effectively changed to new value. 208 | - `'11'`: Failed to process command. The command was sent with an invalid parameter. Use a valid parameter when trying again. 209 | 210 | (Note: codes are ASCII encoded, ie: '11' = 0x3131) 211 | 212 | --- 213 | #### LI - LiDAR Information 214 | Returns the current LiDAR Sample Rate Code. 215 | 216 | #### (HOST -> SENSOR) 217 | 218 | L | I | LF 219 | | --- | --- | ---| 220 | 221 | #### (SENSOR -> HOST) 222 | 223 | L | I | Sample Rate Code (2 bytes) | LF | 224 | | --- | --- | --- | --- | 225 | 226 | Sample Rate Code (2 byte ASCII code): 227 | - `'01'` = 500-600Hz 228 | - `'02'` = 750-800Hz 229 | - `'03'` = 1000-1050Hz 230 | 231 | (Note: codes are ASCII encoded, ie: '02' = 0x3032) 232 | 233 | --- 234 | #### MI - Motor Information 235 | Returns current motor speed code representing the rotation frequency (in Hz) of the current target motor speed setting. This does not mean that the motor speed is stabilized yet. 236 | 237 | #### (HOST -> SENSOR) 238 | 239 | M | I | LF 240 | | --- | --- | ---| 241 | 242 | #### (SENSOR -> HOST) 243 | 244 | M | I | Speed Code (Hz) (2 bytes) | LF | 245 | | --- | --- | ---| --- | 246 | 247 | Speed Code (2 byte ASCII code): 248 | - `'00'` = 0Hz 249 | - `'01'` = 1Hz 250 | - `'02'` = 2Hz 251 | - `'03'` = 3Hz 252 | - `'04'` = 4Hz 253 | - `'05'` = 5Hz 254 | - `'06'` = 6Hz 255 | - `'07'` = 7Hz 256 | - `'08'` = 8Hz 257 | - `'09'` = 9Hz 258 | - `'10'`= 10Hz 259 | 260 | (Note: codes are ASCII encoded, ie: in '05' = 0x3035) 261 | 262 | --- 263 | #### MZ - Motor Ready/Stabilized 264 | Returns a ready code representing whether or not the motor speed has stabilized. 265 | 266 | #### (HOST -> SENSOR) 267 | 268 | M | Z | LF 269 | | --- | --- | ---| 270 | 271 | #### (SENSOR -> HOST) 272 | 273 | M | Z | Ready Code (2 bytes) | LF | 274 | | --- | --- | --- | --- | 275 | 276 | Ready Code (2 byte ASCII code): 277 | 278 | - `'00'` = motor speed has stabilized. 279 | - `'01'` = motor speed has not yet stabilized. 280 | 281 | (Note: codes are ASCII encoded, ie: '01' = 0x3031) 282 | 283 | While adjusting motor speed, the sensor will NOT be able to accomplish certain actions such as `DS` or `MS`. After powering on the device or adjusting motor speed, the device will allow ~6 seconds for the motor speed to stabilize. The `MZ` command allows the user to repeatedly query the motor speed state until the return code indicates the motor speed has stabilized. After the motor speed is noted as stable, the user can safely send commands like `DS` or `MS`. 284 | 285 | --- 286 | #### IV - Version Details 287 | Returns details about the device's version information. 288 | * Model 289 | * Protocol Version 290 | * Firmware Version 291 | * Hardware Version 292 | * Serial Number 293 | 294 | #### (HOST -> SENSOR) 295 | 296 | I | V | LF 297 | | --- | --- | ---| 298 | 299 | 300 | #### (SENSOR -> HOST) 301 | 302 | I | V | Model (5 bytes) | Protocol (2 bytes) | Firmware V (2 bytes) | Hardware V | Serial Number (8 bytes) | LF 303 | | --- | --- | ---| --- | --- | --- | --- | --- | 304 | 305 | Example: 306 | IVSWEEP01011100000001 307 | 308 | --- 309 | 310 | #### ID - Device Info 311 | Returns details about the device's current state/settings. 312 | * Bit Rate 313 | * Laser State 314 | * Mode 315 | * Diagnostic 316 | * Motor Speed 317 | * Sample Rate 318 | 319 | #### (HOST -> SENSOR) 320 | 321 | I | D | LF 322 | | --- | --- | ---| 323 | 324 | #### (SENSOR -> HOST) 325 | 326 | I | D | Bit Rate (6 bytes) | Laser state | Mode | Diagnostic | Motor Speed (2 bytes) | Sample Rate (4 bytes) | LF 327 | | --- | --- | ---| --- | --- | --- | --- | --- | --- | 328 | 329 | Example: 330 | IV115200110050500 331 | 332 | --- 333 | 334 | #### RR - Reset Device 335 | Resets the device. Green LED indicates the device is resetting and cannot receive commands. When the LED turns blue, the device has successfully reset. 336 | 337 | #### (HOST -> SENSOR) 338 | 339 | R | R | LF 340 | | --- | --- | ---| 341 | 342 | #### (SENSOR -> HOST) 343 | 344 | No response 345 | 346 | --- 347 | ## Appendix: 348 | #### Authenticating Receipts (Does not apply to Data Block) 349 | Authentification of a valid receipt is accomplished by a checksum, which uses the `Status`(2 bytes) and `Sum of Status` (1 byte) from the receipt. To perform the checksum, the received `Sum of Status` bytes is checked against a calculated sum of the 2 `Status` bytes. The protocol design allows for performing the checksum visually in a terminal, which requires all bytes in a receipt to be legible ASCII values (ex: `'00P'`). Therefore performing the checksum in code is not intuitive. It works like this: 350 | 351 | - The status bytes are summed 352 | - The lower 6 bits (Least Significant) of that sum are then added to 30Hex 353 | - The resultant value is compared with the checksum byte 354 | ```javascript 355 | //statusByte#1 + statusByte#2 356 | let sumOfStatusBytes = status1_byteValue + status2_byteValue; 357 | //grab the lower (least significant) 6 bits by performing a bit-wise AND with 0x3F (ie: 00111111) 358 | let lowerSixBits = sumOfStatusBytes & 0x3F; 359 | //add 30Hex to it 360 | let sum = lowerSixBits + 0x30; 361 | return ( sum === checkSumByteValue ); 362 | ``` 363 | ex: Consider the common case of `'00P'` ( decimal -> `[48, 48, 80]`, hex -> `[0x30, 0x30, 0x50]`) 364 | ``` 365 | 0x30 + 0x30 = 0x60 // sum of the status bytes 366 | 0x60 & 0x3F = 0x20 // retrieve only the lower 6 bits 367 | 0x20 + 0x30 = 0x50 // calculate the ASCII legible sum 368 | 0x50 = 'P' // translate to ASCII 369 | ``` 370 | 371 | #### Parsing Data Block 16-bit integers and fixed-point values 372 | The `Data Block` receipt includes int-16 and fixed-point values (distance & azimuth). In the case of distance, the value is a 16-bit integer. In the case of the azimuth, the value is a fixed-point with a scaling factor of 16. 373 | 374 | A 16-bit int is sent as two individual bytes. The lower order byte is received first, and the higher order byte is received second. For example, parsing the distance: 375 | ```javascript 376 | //assume dataBlock holds the DATA_BLOCK byte array 377 | //such that indices 3 & 4 correspond to the two distance bytes 378 | let distance = (dataBlock[4] << 8) + (dataBlock[3]); 379 | ``` 380 | For fixed-point values (azimuth), start by using the same technique to acquire a 16-bit int. Once you have it, you can perform the conversion to fixed-point value like so: 381 | ```javascript 382 | //assume dataBlock holds the DATA_BLOCK byte array, 383 | //such that indices 1 & 2 correspond to the two azimuth bytes 384 | let angle_int = (dataBlock[2] << 8) + (dataBlock[1]); 385 | let degree = angle_int/16.0; 386 | ``` 387 | 388 | #### Performing Data Block Checksum 389 | The last byte of a `Data Block` receipt is a checksum byte. It represents the sum of all bytes up until the checksum byte (ie: the first 6 bytes). Obviously a single byte caps at 255, so the checksum can't reliably hold the sum of 6 other bytes. Instead, the checksum byte uses the modulo operation and effectively contains the remainder after dividing the sum by 255 (this is the same as saying sum % 255). 390 | Validating the checksum looks like: 391 | ```javascript 392 | //ONLY applies to receipts of type ReceiptEnum.DATA_BLOCK 393 | //calculate the sum of the specified status or msg bytes 394 | let calculatedSum = 0; 395 | for(let i = 0; i < 6; i++){ 396 | //add each status byte to the running sum 397 | calculatedSum += dataBlock[i]; 398 | } 399 | calculatedSum = calculatedSum % 255; 400 | let expectedSumVal = dataBlock[6]; 401 | return calculatedSum === expectedSumVal; 402 | ``` 403 | -------------------------------------------------------------------------------- /libsweep/src/sweep.cc: -------------------------------------------------------------------------------- 1 | #include "error.hpp" 2 | #include "protocol.hpp" 3 | #include "queue.hpp" 4 | #include "serial.hpp" 5 | 6 | #include "sweep.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | int32_t sweep_get_version(void) { return SWEEP_VERSION; } 15 | bool sweep_is_abi_compatible(void) { return sweep_get_version() >> 16u == SWEEP_VERSION_MAJOR; } 16 | 17 | struct sweep_error { 18 | std::string what; 19 | }; 20 | 21 | #define SWEEP_MAX_SAMPLES 4096 22 | 23 | struct sample { 24 | int32_t angle; // in millidegrees 25 | int32_t distance; // in cm 26 | int32_t signal_strength; // range 0:255 27 | }; 28 | 29 | struct sweep_scan { 30 | sample samples[SWEEP_MAX_SAMPLES]; 31 | int32_t count; 32 | }; 33 | 34 | static sample parse_payload(const sweep::protocol::response_scan_packet_s& msg) { 35 | sample ret; 36 | ret.angle = msg.get_angle_millideg(); 37 | ret.distance = msg.distance; 38 | ret.signal_strength = msg.signal_strength; 39 | return ret; 40 | } 41 | 42 | struct sweep_device { 43 | sweep::serial::device_s serial; // serial port communication 44 | bool is_scanning; 45 | std::atomic stop_thread; 46 | 47 | struct Element { 48 | std::unique_ptr scan; 49 | std::exception_ptr error; 50 | }; 51 | 52 | sweep::queue::queue scan_queue; 53 | }; 54 | 55 | // Constructor hidden from users 56 | static sweep_error_s sweep_error_construct(const char* what) { 57 | SWEEP_ASSERT(what); 58 | 59 | auto out = new sweep_error{what}; 60 | return out; 61 | } 62 | 63 | const char* sweep_error_message(sweep_error_s error) { 64 | SWEEP_ASSERT(error); 65 | 66 | return error->what.c_str(); 67 | } 68 | 69 | void sweep_error_destruct(sweep_error_s error) { 70 | SWEEP_ASSERT(error); 71 | 72 | delete error; 73 | } 74 | 75 | // Blocks until the device is ready. 76 | // Device is ready when the calibration completes and motor speed stabilizes 77 | static void sweep_device_wait_until_motor_ready(sweep_device_s device, sweep_error_s* error) try { 78 | SWEEP_ASSERT(device); 79 | SWEEP_ASSERT(error); 80 | SWEEP_ASSERT(!device->is_scanning); 81 | 82 | // Motor adjustments can take 7-9 seconds, so timeout after 10 seconds to be safe 83 | // (20 iterations with 500ms pause) 84 | for (int32_t i = 0; i < 20; ++i) { 85 | if (i > 0) { 86 | // only check every 500ms, to avoid unecessary processing if this is executing in a dedicated thread 87 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 88 | } 89 | 90 | if (sweep_device_get_motor_ready(device, error)) 91 | return; 92 | } 93 | 94 | *error = sweep_error_construct("timed out waiting for motor to stabilize"); 95 | } catch (const std::exception& e) { 96 | *error = sweep_error_construct(e.what()); 97 | } 98 | 99 | // Accumulates scans in a queue. Used by background thread 100 | static void sweep_device_accumulate_scans(sweep_device_s device) try { 101 | SWEEP_ASSERT(device); 102 | SWEEP_ASSERT(device->is_scanning); 103 | 104 | sample buffer[SWEEP_MAX_SAMPLES]; 105 | int32_t received = 0; 106 | 107 | while (!device->stop_thread && received < SWEEP_MAX_SAMPLES) { 108 | 109 | const auto response = sweep::protocol::read_response_scan(device->serial); 110 | 111 | if (!response.has_error()) { 112 | buffer[received] = parse_payload(response); 113 | received++; 114 | } 115 | 116 | if (response.is_sync() && received > 1) { 117 | 118 | // package the previous scan without the sync reading from the subsequent scan 119 | auto out = std::unique_ptr(new sweep_scan); 120 | out->count = received - 1; // minus 1 to exclude sync reading 121 | 122 | std::copy_n(std::begin(buffer), received - 1, std::begin(out->samples)); 123 | 124 | // place the scan in the queue 125 | device->scan_queue.enqueue({std::move(out), nullptr}); 126 | 127 | // place the sync reading at the start for the next scan 128 | buffer[0] = buffer[received - 1]; 129 | 130 | // reset received 131 | received = 1; 132 | } 133 | } 134 | } catch (...) { 135 | // worker thread is dead at this point 136 | device->scan_queue.enqueue({nullptr, std::current_exception()}); 137 | } 138 | 139 | // Attempts to start scanning without waiting for motor ready. Can error on failure. 140 | // Does NOT start background thread to accumulate scans. 141 | static void sweep_device_attempt_start_scanning(sweep_device_s device, sweep_error_s* error) try { 142 | SWEEP_ASSERT(device); 143 | SWEEP_ASSERT(error); 144 | SWEEP_ASSERT(!device->is_scanning); 145 | 146 | if (device->is_scanning) 147 | return; 148 | 149 | sweep::protocol::write_command(device->serial, sweep::protocol::DATA_ACQUISITION_START); 150 | 151 | auto response = sweep::protocol::read_response_header(device->serial, sweep::protocol::DATA_ACQUISITION_START); 152 | 153 | // Check the status bytes do not indicate failure 154 | const uint8_t status_bytes[2] = {response.cmdStatusByte1, response.cmdStatusByte2}; 155 | int32_t status_code = sweep::protocol::ascii_bytes_to_integral(status_bytes); 156 | switch (status_code) { 157 | case 12: 158 | *error = sweep_error_construct("Failed to start scanning because motor speed has not stabilized."); 159 | return; 160 | case 13: 161 | *error = sweep_error_construct("Failed to start scanning because motor is stationary."); 162 | return; 163 | default: 164 | break; 165 | } 166 | } catch (const std::exception& e) { 167 | *error = sweep_error_construct(e.what()); 168 | } 169 | 170 | // Attempts to set motor speed without waiting for motor ready. Can error on failure. 171 | static void sweep_device_attempt_set_motor_speed(sweep_device_s device, int32_t hz, sweep_error_s* error) try { 172 | SWEEP_ASSERT(device); 173 | SWEEP_ASSERT(hz >= 0 && hz <= 10); 174 | SWEEP_ASSERT(error); 175 | SWEEP_ASSERT(!device->is_scanning); 176 | 177 | uint8_t args[2] = {0}; 178 | sweep::protocol::integral_to_ascii_bytes(hz, args); 179 | 180 | sweep::protocol::write_command_with_arguments(device->serial, sweep::protocol::MOTOR_SPEED_ADJUST, args); 181 | 182 | const auto response = sweep::protocol::read_response_param(device->serial, sweep::protocol::MOTOR_SPEED_ADJUST); 183 | 184 | // Check the status bytes do not indicate failure 185 | const uint8_t status_bytes[2] = {response.cmdStatusByte1, response.cmdStatusByte2}; 186 | int32_t status_code = sweep::protocol::ascii_bytes_to_integral(status_bytes); 187 | switch (status_code) { 188 | case 11: 189 | *error = sweep_error_construct("Failed to set motor speed because provided parameter was invalid."); 190 | return; 191 | case 12: 192 | *error = sweep_error_construct("Failed to set motor speed because prior speed has not yet stabilized."); 193 | return; 194 | default: 195 | break; 196 | } 197 | } catch (const std::exception& e) { 198 | *error = sweep_error_construct(e.what()); 199 | } 200 | 201 | sweep_device_s sweep_device_construct_simple(const char* port, sweep_error_s* error) try { 202 | SWEEP_ASSERT(error); 203 | return sweep_device_construct(port, 115200, error); 204 | } catch (const std::exception& e) { 205 | *error = sweep_error_construct(e.what()); 206 | return nullptr; 207 | } 208 | 209 | sweep_device_s sweep_device_construct(const char* port, int32_t bitrate, sweep_error_s* error) try { 210 | SWEEP_ASSERT(port); 211 | SWEEP_ASSERT(bitrate > 0); 212 | SWEEP_ASSERT(error); 213 | 214 | sweep::serial::device_s serial = sweep::serial::device_construct(port, bitrate); 215 | 216 | // initialize assuming the device is scanning 217 | auto out = new sweep_device{serial, /*is_scanning=*/true, /*stop_thread=*/{false}, /*scan_queue=*/{20}}; 218 | 219 | // send a stop scanning command in case the scanner was powered on and scanning 220 | sweep_device_stop_scanning(out, error); 221 | 222 | return out; 223 | } catch (const std::exception& e) { 224 | *error = sweep_error_construct(e.what()); 225 | return nullptr; 226 | } 227 | 228 | void sweep_device_destruct(sweep_device_s device) { 229 | SWEEP_ASSERT(device); 230 | 231 | try { 232 | sweep_error_s ignore = nullptr; 233 | sweep_device_stop_scanning(device, &ignore); 234 | } catch (...) { 235 | // nothing we can do here 236 | } 237 | 238 | sweep::serial::device_destruct(device->serial); 239 | 240 | delete device; 241 | } 242 | 243 | void sweep_device_start_scanning(sweep_device_s device, sweep_error_s* error) try { 244 | SWEEP_ASSERT(device); 245 | SWEEP_ASSERT(error); 246 | SWEEP_ASSERT(!device->is_scanning); 247 | 248 | if (device->is_scanning) 249 | return; 250 | 251 | // Get the current motor speed setting 252 | int32_t speed = sweep_device_get_motor_speed(device, error); 253 | 254 | // Check that the motor is not stationary 255 | if (speed == 0 /*Hz*/) { 256 | // If the motor is stationary, adjust it to default 5Hz 257 | sweep_device_set_motor_speed(device, 5 /*Hz*/, error); 258 | } 259 | 260 | // Make sure the motor is stabilized so the DS command doesn't fail 261 | sweep_device_wait_until_motor_ready(device, error); 262 | 263 | // Attempt to start scanning 264 | sweep_device_attempt_start_scanning(device, error); 265 | 266 | // Start SCAN WORKER 267 | device->scan_queue.clear(); 268 | device->is_scanning = true; 269 | // START background worker thread 270 | device->stop_thread = false; 271 | // create a thread 272 | std::thread th = std::thread(sweep_device_accumulate_scans, device); 273 | // detach the thread so that it runs in the background and cleans itself up 274 | th.detach(); 275 | } catch (const std::exception& e) { 276 | *error = sweep_error_construct(e.what()); 277 | } 278 | 279 | void sweep_device_stop_scanning(sweep_device_s device, sweep_error_s* error) try { 280 | SWEEP_ASSERT(device); 281 | SWEEP_ASSERT(error); 282 | 283 | // STOP the background thread from accumulating scans 284 | device->stop_thread = true; 285 | 286 | sweep::protocol::write_command(device->serial, sweep::protocol::DATA_ACQUISITION_STOP); 287 | 288 | // Wait some time for a few reasons: 289 | // 1. allow time for the device to register the stop cmd and stop sending data blocks 290 | // 2. allow time for slower performing devices (RaspberryPi) to buffer the stop response 291 | // 3. allow a grace period for the device to perform its logging routine before sending a second stop command 292 | std::this_thread::sleep_for(std::chrono::milliseconds(35)); 293 | 294 | // Read the response from the first stop command 295 | // It is possible this will contain garbage bytes (leftover from data stream), and will error 296 | // But we are guaranteed to read at least as many bytes as the length of a stop response 297 | try { 298 | sweep::protocol::read_response_header(device->serial, sweep::protocol::DATA_ACQUISITION_STOP); 299 | } catch (const std::exception& ignore) { 300 | // Catch and ignore the error in the case of the stop response containing garbage bytes 301 | // Occurs if the device was actively streaming data before the stop cmd 302 | (void)ignore; 303 | } 304 | 305 | // Flush any bytes left over, in case device was actively streaming data before the stop cmd 306 | sweep::serial::device_flush(device->serial); 307 | 308 | // Write another stop cmd so we can read a valid response 309 | sweep::protocol::write_command(device->serial, sweep::protocol::DATA_ACQUISITION_STOP); 310 | 311 | // read the response 312 | sweep::protocol::read_response_header(device->serial, sweep::protocol::DATA_ACQUISITION_STOP); 313 | 314 | device->is_scanning = false; 315 | } catch (const std::exception& e) { 316 | *error = sweep_error_construct(e.what()); 317 | } 318 | 319 | // Retrieves a scan from the queue (will block until scan is available) 320 | sweep_scan_s sweep_device_get_scan(sweep_device_s device, sweep_error_s* error) try { 321 | SWEEP_ASSERT(device); 322 | SWEEP_ASSERT(error); 323 | SWEEP_ASSERT(device->is_scanning); 324 | 325 | auto out = device->scan_queue.dequeue(); 326 | 327 | if (out.error != nullptr) { 328 | std::rethrow_exception(out.error); 329 | } 330 | 331 | return out.scan.release(); 332 | 333 | } catch (const std::exception& e) { 334 | *error = sweep_error_construct(e.what()); 335 | return nullptr; 336 | } 337 | 338 | bool sweep_device_get_motor_ready(sweep_device_s device, sweep_error_s* error) try { 339 | SWEEP_ASSERT(device); 340 | SWEEP_ASSERT(error); 341 | SWEEP_ASSERT(!device->is_scanning); 342 | 343 | sweep::protocol::write_command(device->serial, sweep::protocol::MOTOR_READY); 344 | 345 | const auto response = sweep::protocol::read_response_info_motor_ready(device->serial); 346 | 347 | int32_t ready_code = sweep::protocol::ascii_bytes_to_integral(response.motor_ready); 348 | SWEEP_ASSERT(ready_code >= 0); 349 | 350 | return ready_code == 0; 351 | } catch (const std::exception& e) { 352 | *error = sweep_error_construct(e.what()); 353 | return false; 354 | } 355 | 356 | int32_t sweep_device_get_motor_speed(sweep_device_s device, sweep_error_s* error) try { 357 | SWEEP_ASSERT(device); 358 | SWEEP_ASSERT(error); 359 | SWEEP_ASSERT(!device->is_scanning); 360 | 361 | sweep::protocol::write_command(device->serial, sweep::protocol::MOTOR_INFORMATION); 362 | 363 | const auto response = sweep::protocol::read_response_info_motor_speed(device->serial); 364 | 365 | int32_t speed = sweep::protocol::ascii_bytes_to_integral(response.motor_speed); 366 | SWEEP_ASSERT(speed >= 0); 367 | 368 | return speed; 369 | } catch (const std::exception& e) { 370 | *error = sweep_error_construct(e.what()); 371 | return -1; 372 | } 373 | 374 | void sweep_device_set_motor_speed(sweep_device_s device, int32_t hz, sweep_error_s* error) try { 375 | SWEEP_ASSERT(device); 376 | SWEEP_ASSERT(hz >= 0 && hz <= 10); 377 | SWEEP_ASSERT(error); 378 | SWEEP_ASSERT(!device->is_scanning); 379 | 380 | // Make sure the motor is stabilized so the MS command doesn't fail 381 | sweep_device_wait_until_motor_ready(device, error); 382 | 383 | // Attempt to set motor speed 384 | sweep_device_attempt_set_motor_speed(device, hz, error); 385 | } catch (const std::exception& e) { 386 | *error = sweep_error_construct(e.what()); 387 | } 388 | 389 | int32_t sweep_device_get_sample_rate(sweep_device_s device, sweep_error_s* error) try { 390 | SWEEP_ASSERT(device); 391 | SWEEP_ASSERT(error); 392 | SWEEP_ASSERT(!device->is_scanning); 393 | 394 | sweep::protocol::write_command(device->serial, sweep::protocol::SAMPLE_RATE_INFORMATION); 395 | 396 | const auto response = sweep::protocol::read_response_info_sample_rate(device->serial); 397 | 398 | // 01: 500-600Hz, 02: 750-800Hz, 03: 1000-1050Hz 399 | int32_t code = sweep::protocol::ascii_bytes_to_integral(response.sample_rate); 400 | int32_t rate = 0; 401 | 402 | switch (code) { 403 | case 1: 404 | rate = 500; 405 | break; 406 | case 2: 407 | rate = 750; 408 | break; 409 | case 3: 410 | rate = 1000; 411 | break; 412 | default: 413 | SWEEP_ASSERT(false && "sample rate code unknown"); 414 | } 415 | 416 | return rate; 417 | } catch (const std::exception& e) { 418 | *error = sweep_error_construct(e.what()); 419 | return -1; 420 | } 421 | 422 | void sweep_device_set_sample_rate(sweep_device_s device, int32_t hz, sweep_error_s* error) try { 423 | SWEEP_ASSERT(device); 424 | SWEEP_ASSERT(hz == 500 || hz == 750 || hz == 1000); 425 | SWEEP_ASSERT(error); 426 | SWEEP_ASSERT(!device->is_scanning); 427 | 428 | // 01: 500-600Hz, 02: 750-800Hz, 03: 1000-1050Hz 429 | int32_t code = 1; 430 | 431 | switch (hz) { 432 | case 500: 433 | code = 1; 434 | break; 435 | case 750: 436 | code = 2; 437 | break; 438 | case 1000: 439 | code = 3; 440 | break; 441 | default: 442 | SWEEP_ASSERT(false && "sample rate unknown"); 443 | } 444 | 445 | uint8_t args[2] = {0}; 446 | sweep::protocol::integral_to_ascii_bytes(code, args); 447 | 448 | sweep::protocol::write_command_with_arguments(device->serial, sweep::protocol::SAMPLE_RATE_ADJUST, args); 449 | 450 | const auto response = sweep::protocol::read_response_param(device->serial, sweep::protocol::SAMPLE_RATE_ADJUST); 451 | 452 | // Check the status bytes do not indicate failure 453 | const uint8_t status_bytes[2] = {response.cmdStatusByte1, response.cmdStatusByte2}; 454 | int32_t status_code = sweep::protocol::ascii_bytes_to_integral(status_bytes); 455 | switch (status_code) { 456 | case 11: 457 | *error = sweep_error_construct("Failed to set motor speed because provided parameter was invalid."); 458 | return; 459 | default: 460 | break; 461 | } 462 | } catch (const std::exception& e) { 463 | *error = sweep_error_construct(e.what()); 464 | } 465 | 466 | int32_t sweep_scan_get_number_of_samples(sweep_scan_s scan) { 467 | SWEEP_ASSERT(scan); 468 | SWEEP_ASSERT(scan->count >= 0); 469 | 470 | return scan->count; 471 | } 472 | 473 | int32_t sweep_scan_get_angle(sweep_scan_s scan, int32_t sample) { 474 | SWEEP_ASSERT(scan); 475 | SWEEP_ASSERT(sample >= 0 && sample < scan->count && "sample index out of bounds"); 476 | 477 | return scan->samples[sample].angle; 478 | } 479 | 480 | int32_t sweep_scan_get_distance(sweep_scan_s scan, int32_t sample) { 481 | SWEEP_ASSERT(scan); 482 | SWEEP_ASSERT(sample >= 0 && sample < scan->count && "sample index out of bounds"); 483 | 484 | return scan->samples[sample].distance; 485 | } 486 | 487 | int32_t sweep_scan_get_signal_strength(sweep_scan_s scan, int32_t sample) { 488 | SWEEP_ASSERT(scan); 489 | SWEEP_ASSERT(sample >= 0 && sample < scan->count && "sample index out of bounds"); 490 | 491 | return scan->samples[sample].signal_strength; 492 | } 493 | 494 | void sweep_scan_destruct(sweep_scan_s scan) { 495 | SWEEP_ASSERT(scan); 496 | 497 | delete scan; 498 | } 499 | 500 | void sweep_device_reset(sweep_device_s device, sweep_error_s* error) try { 501 | SWEEP_ASSERT(device); 502 | SWEEP_ASSERT(error); 503 | SWEEP_ASSERT(!device->is_scanning); 504 | 505 | sweep::protocol::write_command(device->serial, sweep::protocol::RESET_DEVICE); 506 | } catch (const std::exception& e) { 507 | *error = sweep_error_construct(e.what()); 508 | } 509 | --------------------------------------------------------------------------------