diff --git a/.gitignore b/.gitignore index e23ac99..bfb4ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ -build* -test_*dump* -.cproject -.project -.externalToolBuilders -.settings -.pydevproject -*.idea* -*__pycache__* -# NPM Packages -node_modules -package-lock.json \ No newline at end of file +build* +test_*dump* +.cproject +.project +.externalToolBuilders +.settings +.pydevproject +*.idea* +*__pycache__* +.vscode +node_modules +*.bfpc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1df0063..820f958 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,10 @@ endif() string(REGEX REPLACE "^v([0-9.]+).*" "\\1" PROJECT_VERSION "${PROJECT_VERSION}") string(REGEX REPLACE "^v" "" PROJECT_GIT_DESCRIBE "${PROJECT_GIT_DESCRIBE}") +string(REPLACE "." ";" VERSION_LIST ${PROJECT_VERSION}) +list(GET VERSION_LIST 0 BSL_VERSION_MAJOR) +list(GET VERSION_LIST 1 BSL_VERSION_MINOR) +list(GET VERSION_LIST 2 BSL_VERSION_PATCH) message("BSL Version ${PROJECT_GIT_DESCRIBE}") project (blickfeld-scanner-lib @@ -204,6 +208,7 @@ set(BLICKFELD_SCANNER_SOURCES src/scanner_connection.cpp src/connection.cpp src/discover.cpp + src/point_cloud_record.cpp ) set(BLICKFELD_SCANNER_INCLUDES ${Protobuf_INCLUDE_DIRS} @@ -250,8 +255,8 @@ add_dependencies(blickfeld-scanner blickfeld-scanner-protocol) add_dependencies(blickfeld-scanner-static blickfeld-scanner-protocol) if(NOT BSL_STANDALONE) - target_link_libraries(blickfeld-scanner PUBLIC ${Protobuf_LIBRARY}) - target_include_directories(blickfeld-scanner PUBLIC ${Protobuf_INCLUDE_DIRS}) + target_link_libraries( blickfeld-scanner PUBLIC $) + target_include_directories( blickfeld-scanner PUBLIC $) else() target_link_libraries(blickfeld-scanner PRIVATE ${Protobuf_LIBRARY}) target_include_directories(blickfeld-scanner PRIVATE ${Protobuf_INCLUDE_DIRS}) @@ -269,12 +274,9 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(blickfeld-scanner INTERFACE ${CMAKE_THREAD_LIBS_INIT}) -get_directory_property(HAS_PARENT PARENT_DIRECTORY) -if(HAS_PARENT) - set(Protobuf_PROTOC_EXECUTABLE ${Protobuf_PROTOC_EXECUTABLE} PARENT_SCOPE) - set(Protobuf_LIBRARY ${Protobuf_LIBRARY} PARENT_SCOPE) - set(BF_BSL_VERSION ${PROJECT_GIT_DESCRIBE} PARENT_SCOPE) -endif() +# Set version to lib +set_target_properties(blickfeld-scanner + PROPERTIES VERSION ${BSL_VERSION_MAJOR}.${BSL_VERSION_MINOR}) target_include_directories(blickfeld-scanner INTERFACE $ @@ -304,6 +306,12 @@ install( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h" ) +# Protocol files +install( + DIRECTORY ${PROJECT_SOURCE_DIR}/protocol/blickfeld/ + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/blickfeld-scanner/protocol/blickfeld + FILES_MATCHING PATTERN "*.proto" +) # Misc if(BF_BUILD_TESTS) @@ -316,21 +324,32 @@ if(BF_BUILD_PYTHON) add_subdirectory(python) endif() +get_directory_property(HAS_PARENT PARENT_DIRECTORY) +if(HAS_PARENT) + set(Protobuf_PROTOC_EXECUTABLE ${Protobuf_PROTOC_EXECUTABLE} PARENT_SCOPE) + set(Protobuf_LIBRARY ${Protobuf_LIBRARY} PARENT_SCOPE) + set(BF_BSL_PYTHON_VERSION ${BF_BSL_PYTHON_VERSION} PARENT_SCOPE) + + set(blickfeld-scanner_INCLUDE_DIRS ${BLICKFELD_SCANNER_INCLUDES} PARENT_SCOPE) + set(blickfeld-scanner_PROTOCOL "${PROJECT_SOURCE_DIR}/protocol" PARENT_SCOPE) +endif() + # CMake Package include(CMakePackageConfigHelpers) write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/blickfeld-scanner/blickfeld-scanner-config-version.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/blickfeld-scanner/blickfeld-scanner-config-version.cmake" VERSION ${blickfeld-scanner-lib_VERSION} COMPATIBILITY AnyNewerVersion ) export(EXPORT blickfeld-scanner - FILE "${CMAKE_CURRENT_BINARY_DIR}/blickfeld-scanner/blickfeld-scanner.cmake" + FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/blickfeld-scanner/blickfeld-scanner.cmake" ) configure_file(cmake/blickfeld-scanner-config.cmake - "${CMAKE_CURRENT_BINARY_DIR}/blickfeld-scanner/blickfeld-scanner-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/blickfeld-scanner/blickfeld-scanner-config.cmake" @ONLY ) +file(COPY "${PROJECT_SOURCE_DIR}/protocol/blickfeld" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/cmake/blickfeld-scanner/protocol") set(ConfigPackageLocation lib/cmake/blickfeld-scanner) install(EXPORT blickfeld-scanner @@ -341,8 +360,8 @@ install(EXPORT blickfeld-scanner ) install( FILES - "${CMAKE_CURRENT_BINARY_DIR}/blickfeld-scanner/blickfeld-scanner-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/blickfeld-scanner/blickfeld-scanner-config-version.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/blickfeld-scanner/blickfeld-scanner-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/blickfeld-scanner/blickfeld-scanner-config-version.cmake" DESTINATION ${ConfigPackageLocation} COMPONENT diff --git a/cmake/blickfeld-scanner-config.cmake b/cmake/blickfeld-scanner-config.cmake index 71ccc1f..71f4bdf 100644 --- a/cmake/blickfeld-scanner-config.cmake +++ b/cmake/blickfeld-scanner-config.cmake @@ -5,7 +5,7 @@ set(BSL_STANDALONE @BSL_STANDALONE@) set(HAVE_OPENSSL @HAVE_OPENSSL@) if (NOT BSL_STANDALONE) - find_dependency(Protobuf REQUIRED) + find_dependency(Protobuf @Protobuf_VERSION@ REQUIRED) if(NOT PROTOBUF_FOUND) set(blickfeld-scanner_FOUND False) set(blickfeld-scanner_NOT_FOUND_MESSAGE "BSL is configured with Protobuf support. Could not find suitable Protobuf on this system (libprotobuf-dev).") @@ -38,3 +38,8 @@ foreach(_comp ${blickfeld-scanner_FIND_COMPONENTS}) endforeach() include("${CMAKE_CURRENT_LIST_DIR}/blickfeld-scanner.cmake") +set_property(TARGET blickfeld-scanner + APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${PROTOBUF_INCLUDE_DIR}" + APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${Protobuf_LIBRARIES}" +) +set(blickfeld-scanner_PROTOCOL "${CMAKE_CURRENT_LIST_DIR}/protocol") diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 1807a25..4b3f29b 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -13,6 +13,23 @@ influence the resulting point cloud. ### Removed +## [2.13.0] - 2020.09.25 + +### Added +* [Introduced in firmware v1.13] Separate API request for raw file streams. This allows file recordings with Python on low-performance clients. +* [Introduced in firmware v1.13] Server section to status message. Reports connected clients and network statistics. +* Add protocol files to install target +* [Introduced in firmware v1.13] Extend advanced configuration with `default_point_cloud_subscription` +* [Introduced in firmware v1.13] Request to attempt error recovery + +### Changed +* Python: Improved examples and refactored arguments +* CPP: Refactored point cloud recording class +* CMake: Improved the approach of linking dependent library to make the install package relocatable + +### Removed +* [Removed in firmware v1.13] Drop deprecated legacy scan patterns + ## [2.12.1] - 2020.09.08 ### Changed diff --git a/doc/protobuf-frame-visualisation.png b/doc/protobuf-frame-visualisation.png index 581f545..fa6264f 100644 Binary files a/doc/protobuf-frame-visualisation.png and b/doc/protobuf-frame-visualisation.png differ diff --git a/doc/protobuf_protocol.md b/doc/protobuf_protocol.md index 20081d8..414b7ee 100644 --- a/doc/protobuf_protocol.md +++ b/doc/protobuf_protocol.md @@ -21,6 +21,7 @@ The data, such as a point cloud, are also packed in protobuf messages. - [blickfeld/connection.proto](#blickfeld/connection.proto) - [Request](#blickfeld.protocol.Request) + - [Request.AttemptErrorRecovery](#blickfeld.protocol.Request.AttemptErrorRecovery) - [Request.Developer](#blickfeld.protocol.Request.Developer) - [Request.FillScanPattern](#blickfeld.protocol.Request.FillScanPattern) - [Request.GetAdvancedConfig](#blickfeld.protocol.Request.GetAdvancedConfig) @@ -31,6 +32,7 @@ The data, such as a point cloud, are also packed in protobuf messages. - [Request.SetScanPattern](#blickfeld.protocol.Request.SetScanPattern) - [Request.Status](#blickfeld.protocol.Request.Status) - [Response](#blickfeld.protocol.Response) + - [Response.AttemptErrorRecovery](#blickfeld.protocol.Response.AttemptErrorRecovery) - [Response.Developer](#blickfeld.protocol.Response.Developer) - [Response.FillScanPattern](#blickfeld.protocol.Response.FillScanPattern) - [Response.GetAdvancedConfig](#blickfeld.protocol.Response.GetAdvancedConfig) @@ -96,6 +98,7 @@ The data, such as a point cloud, are also packed in protobuf messages. - [Advanced](#blickfeld.protocol.config.Advanced) - [Advanced.Detector](#blickfeld.protocol.config.Advanced.Detector) - [Advanced.Processing](#blickfeld.protocol.config.Advanced.Processing) + - [Advanced.Server](#blickfeld.protocol.config.Advanced.Server) @@ -202,6 +205,16 @@ The data, such as a point cloud, are also packed in protobuf messages. +- [blickfeld/status/server.proto](#blickfeld/status/server.proto) + - [Server](#blickfeld.protocol.status.Server) + - [Server.Client](#blickfeld.protocol.status.Server.Client) + - [Server.NetworkStats](#blickfeld.protocol.status.Server.NetworkStats) + - [Server.NetworkStats.Channel](#blickfeld.protocol.status.Server.NetworkStats.Channel) + + + + + - [blickfeld/status/temperature.proto](#blickfeld/status/temperature.proto) - [Temperature](#blickfeld.protocol.status.Temperature) @@ -211,17 +224,25 @@ The data, such as a point cloud, are also packed in protobuf messages. - [blickfeld/stream/connection.proto](#blickfeld/stream/connection.proto) - - [Event](#blickfeld.protocol.stream.Event) - - [Event.Developer](#blickfeld.protocol.stream.Event.Developer) - [Subscribe](#blickfeld.protocol.stream.Subscribe) - [Subscribe.Developer](#blickfeld.protocol.stream.Subscribe.Developer) - [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) + - [Subscribe.RawFile](#blickfeld.protocol.stream.Subscribe.RawFile) - [Subscribe.Status](#blickfeld.protocol.stream.Subscribe.Status) +- [blickfeld/stream/event.proto](#blickfeld/stream/event.proto) + - [Event](#blickfeld.protocol.stream.Event) + - [Event.Developer](#blickfeld.protocol.stream.Event.Developer) + - [Event.EndOfStream](#blickfeld.protocol.stream.Event.EndOfStream) + + + + + - [blickfeld/update/hardware.proto](#blickfeld/update/hardware.proto) - [partial_module_eeprom_msg](#blickfeld.protocol.update.partial_module_eeprom_msg) - [partial_trenz_eeprom_msg](#blickfeld.protocol.update.partial_trenz_eeprom_msg) @@ -318,6 +339,8 @@ A request is always answered with a response. For every response, there is a req | run_self_test | [Request.RunSelfTest](#blickfeld.protocol.Request.RunSelfTest) | optional | Refer to [Request.RunSelfTest](#blickfeld.protocol.Request.RunSelfTest) | | set_advanced_config | [Request.SetAdvancedConfig](#blickfeld.protocol.Request.SetAdvancedConfig) | optional |
Introduced in BSL v2.11 and firmware v1.11
Refer to [Request.SetAdvancedConfig](#blickfeld.protocol.Request.SetAdvancedConfig) | | get_advanced_config | [Request.GetAdvancedConfig](#blickfeld.protocol.Request.GetAdvancedConfig) | optional |
Introduced in BSL v2.11 and firmware v1.11
Refer to [Request.GetAdvancedConfig](#blickfeld.protocol.Request.GetAdvancedConfig) | +| unsubscribe | [stream.Subscribe](#blickfeld.protocol.stream.Subscribe) | optional |
Introduced in BSL v2.13 and firmware v1.13
Unsubscribe a stream started with a [Subscribe](#blickfeld.protocol.stream.Subscribe) request. | +| attempt_error_recovery | [Request.AttemptErrorRecovery](#blickfeld.protocol.Request.AttemptErrorRecovery) | optional |
Introduced in BSL v2.13 and firmware v1.13
Refer to [Request.AttemptErrorRecovery](#blickfeld.protocol.Request.AttemptErrorRecovery) | | _asJSON | [string](#string) | optional | Internal use only | | accept_format | [Format](#blickfeld.protocol.Format) | optional | Internal use only Default: PROTOBUF | @@ -326,6 +349,19 @@ A request is always answered with a response. For every response, there is a req + + +### Request.AttemptErrorRecovery +> Introduced in BSL v2.13 and firmware v1.13 + +This request can be used to attempt a re-initialization of the device if it is errored. +A self test is automatically triggered after a successful re-initialization. + + + + + + ### Request.Developer @@ -347,7 +383,6 @@ The filled scan pattern can then be set as input for [Request.SetScanPattern](# | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) | -| legacy_config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Deprecated | @@ -433,7 +468,6 @@ This request is used for configuring a Scan Pattern. | ----- | ---- | ----- | ----------- | | config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) | | persist | [bool](#bool) | optional | Persists the scan pattern and sets it after a power cycle. Default: False Default: false | -| legacy_config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Deprecated old 'config' due to horizontal field of view definition | @@ -472,6 +506,7 @@ Each response has the same name as the request. | run_self_test | [Response.RunSelfTest](#blickfeld.protocol.Response.RunSelfTest) | optional | Refer to [Response.RunSelfTest](#blickfeld.protocol.Response.RunSelfTest) | | set_advanced_config | [Response.SetAdvancedConfig](#blickfeld.protocol.Response.SetAdvancedConfig) | optional |
Introduced in BSL v2.11 and firmware v1.11
Refer to [Response.SetAdvanced](#blickfeld.protocol.Response.SetAdvancedConfig) | | get_advanced_config | [Response.GetAdvancedConfig](#blickfeld.protocol.Response.GetAdvancedConfig) | optional |
Introduced in BSL v2.11 and firmware v1.11
Refer to [Response.GetAdvanced](#blickfeld.protocol.Response.GetAdvancedConfig) | +| attempt_error_recovery | [Response.AttemptErrorRecovery](#blickfeld.protocol.Response.AttemptErrorRecovery) | optional |
Introduced in BSL v2.13 and firmware v1.13
Refer to [Response.AttemptErrorRecovery](#blickfeld.protocol.Response.AttemptErrorRecovery) | | _asJSON | [string](#string) | optional | Internal use only | @@ -479,6 +514,18 @@ Each response has the same name as the request. + + +### Response.AttemptErrorRecovery +> Introduced in BSL v2.13 and firmware v1.13 + +This response is sent out after sending AttemptErrorRecovery. + + + + + + ### Response.Developer @@ -499,7 +546,6 @@ It returns a scan pattern, the unset fields are filled with default values. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) | -| legacy_config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Deprecated | @@ -532,7 +578,6 @@ This response is returned after a request to get the current [ScanPattern](#blic | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) | -| legacy_config | [config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Deprecated | @@ -944,6 +989,7 @@ The current set of parameters is preliminary, additional parameters may be added | ----- | ---- | ----- | ----------- | | detector | [Advanced.Detector](#blickfeld.protocol.config.Advanced.Detector) | optional | Refer to [Detector](#blickfeld.protocol.config.Advanced.Detector) | | processing | [Advanced.Processing](#blickfeld.protocol.config.Advanced.Processing) | optional |
Introduced in BSL v2.12 and firmware v1.12
Refer to [Processing](#blickfeld.protocol.config.Advanced.Processing) | +| server | [Advanced.Server](#blickfeld.protocol.config.Advanced.Server) | optional |
Introduced in BSL v2.13 and firmware v1.13
Refer to [Server](#blickfeld.protocol.config.Advanced.Server) | @@ -981,6 +1027,23 @@ Processing parameters are set at the factory after calibration. Changing these v + + + +### Advanced.Server +> Introduced in BSL v2.13 and firmware v1.13 + +Parameters, which control the server behavior. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| default_point_cloud_subscription | [blickfeld.protocol.stream.Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) | optional | Overwrite default point cloud subscription. Can still be overridden with client requests. | + + + + + @@ -1705,6 +1768,7 @@ This section contains the status messages of the two deflection mirrors and the | ----- | ---- | ----- | ----------- | | scanner | [status.Scanner](#blickfeld.protocol.status.Scanner) | optional | Refer to [Scanner](#blickfeld.protocol.status.scanner.Scanner) | | temperatures | [status.Temperature](#blickfeld.protocol.status.Temperature) | repeated | Refer to [Temperature](#blickfeld.protocol.status.temperature.Temperature) | +| server | [status.Server](#blickfeld.protocol.status.Server) | optional | Refer to [Client](#blickfeld.protocol.status.server.Server) | @@ -1738,7 +1802,6 @@ This section defines the status of the device. | state | [Scanner.State](#blickfeld.protocol.status.Scanner.State) | optional | Refer to [Scanner.State](#blickfeld.protocol.status.Scanner.State) | | scan_pattern | [blickfeld.protocol.config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) | | error | [blickfeld.protocol.Error](#blickfeld.protocol.Error) | optional | Refer to [Error](#blickfeld.protocol.Error) | -| legacy_scan_pattern | [blickfeld.protocol.config.ScanPattern](#blickfeld.protocol.config.ScanPattern) | optional | Deprecated old ´scan_pattern´ definition | @@ -1771,6 +1834,89 @@ This section defines the status of the device. + +

Top

+ +## blickfeld/status/server.proto + + + + + +### Server +This section defines the status of the network server. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| clients | [Server.Client](#blickfeld.protocol.status.Server.Client) | repeated | | +| network_stats | [Server.NetworkStats](#blickfeld.protocol.status.Server.NetworkStats) | optional | | + + + + + + + + +### Server.Client + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| subscriptions | [blickfeld.protocol.stream.Subscribe](#blickfeld.protocol.stream.Subscribe) | repeated | | +| network_stats | [Server.NetworkStats](#blickfeld.protocol.status.Server.NetworkStats) | optional | | +| identifier | [string](#string) | optional | | + + + + + + + + +### Server.NetworkStats + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| sent | [Server.NetworkStats.Channel](#blickfeld.protocol.status.Server.NetworkStats.Channel) | optional | | +| received | [Server.NetworkStats.Channel](#blickfeld.protocol.status.Server.NetworkStats.Channel) | optional | | +| dropped_messages | [uint64](#uint64) | optional | Default: 0 | + + + + + + + + +### Server.NetworkStats.Channel + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| total_byte_count | [uint64](#uint64) | optional | Default: 0 | +| bytes_per_second | [float](#float) | optional | | +| maximum_bytes_per_second | [float](#float) | optional | | + + + + + + + + + + + + + + +

Top

@@ -1829,26 +1975,28 @@ This section describes the hardware modules in the device. - + -### Event -This section describes the events of streams. +### Subscribe +This section describes the different streams to which it is possible to subscribe. +A stream regularly provides data or status updates for the user. The events will not be pushed automatically to the BSL; the client has to retrieve them. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| point_cloud | [blickfeld.protocol.data.PointCloud](#blickfeld.protocol.data.PointCloud) | optional | Refer to [PointCloud](#blickfeld.protocol.data.PointCloud) | -| status | [blickfeld.protocol.Status](#blickfeld.protocol.Status) | optional | Refer to [Status](#blickfeld.protocol.status.Status) | -| developer | [Event.Developer](#blickfeld.protocol.stream.Event.Developer) | optional | Refer to [Event.Developer](#blickfeld.protocol.stream.Event.Developer) | +| point_cloud | [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) | optional | Refer to [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) | +| status | [Subscribe.Status](#blickfeld.protocol.stream.Subscribe.Status) | optional | Refer to [Subscribe.Status](#blickfeld.protocol.stream.Subscribe.Status) | +| developer | [Subscribe.Developer](#blickfeld.protocol.stream.Subscribe.Developer) | optional | Refer to [Subscribe.Developer](#blickfeld.protocol.stream.Subscribe.Developer) | +| raw_file | [Subscribe.RawFile](#blickfeld.protocol.stream.Subscribe.RawFile) | optional | Refer to [Subscribe.RawFile](#blickfeld.protocol.stream.Subscribe.RawFile) | - + -### Event.Developer +### Subscribe.Developer Internal use only @@ -1856,54 +2004,110 @@ Internal use only - + -### Subscribe -This section describes the different streams to which it is possible to subscribe. -A stream regularly provides data or status updates for the user. The events will not be pushed automatically to the BSL; the client has to retrieve them. +### Subscribe.PointCloud +This request is used for subscribing to a point cloud stream. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| point_cloud | [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) | optional | Refer to [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) | -| status | [Subscribe.Status](#blickfeld.protocol.stream.Subscribe.Status) | optional | Refer to [Subscribe.Status](#blickfeld.protocol.stream.Subscribe.Status) | -| developer | [Subscribe.Developer](#blickfeld.protocol.stream.Subscribe.Developer) | optional | Refer to [Subscribe.Developer](#blickfeld.protocol.stream.Subscribe.Developer) | +| reference_frame | [blickfeld.protocol.data.Frame](#blickfeld.protocol.data.Frame) | optional |
Introduced in BSL v2.10 and firmware v1.9
If present, only fields that are set in this message and submessages will be present in the point cloud. If less fields are requested, the Protobuf encoding and network transport time can reduce significantly. | +| filter | [blickfeld.protocol.config.ScanPattern.Filter](#blickfeld.protocol.config.ScanPattern.Filter) | optional |
Introduced in BSL v2.10 and firmware v1.9
Refer to [ScanPattern.Filter](#blickfeld.protocol.config.ScanPattern.Filter). Overrides parameters of scan pattern. | - + -### Subscribe.Developer -Internal use only +### Subscribe.RawFile +> Introduced in BSL v2.13 and firmware v1.13 +This request is used for subscribing to a raw file stream. +The requested stream is directly packed in the Blickfeld data format and only raw bytes are sent to the client, which it should write sequentially in a file. +An [Unsubscribe](#blickfeld.protocol.stream.Unsubscribe) request with the same request data must be sent to properly end the file stream. +The request raw file stream is ended with an [EndOfStream](#blickfeld.protocol.stream.Event.EndOfStream) event. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| point_cloud | [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) | optional | Subscribe to a raw point cloud stream. Refer to [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud). | - -### Subscribe.PointCloud -This request is used for subscribing to a point cloud stream. + + + + +### Subscribe.Status +This request is used for subscribing to a status stream. + + + + + + + + + + + + + + + + +

Top

+ +## blickfeld/stream/event.proto + + + + + +### Event +This section describes the events of streams. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| reference_frame | [blickfeld.protocol.data.Frame](#blickfeld.protocol.data.Frame) | optional |
Introduced in BSL v2.10 and firmware v1.9
If present, only fields that are set in this message and submessages will be present in the point cloud. If less fields are requested, the Protobuf encoding and network transport time can reduce significantly. | -| filter | [blickfeld.protocol.config.ScanPattern.Filter](#blickfeld.protocol.config.ScanPattern.Filter) | optional |
Introduced in BSL v2.10 and firmware v1.9
Refer to [ScanPattern.Filter](#blickfeld.protocol.config.ScanPattern.Filter). Overrides parameters of scan pattern. | +| point_cloud | [blickfeld.protocol.data.PointCloud](#blickfeld.protocol.data.PointCloud) | optional | Refer to [PointCloud](#blickfeld.protocol.data.PointCloud) | +| status | [blickfeld.protocol.Status](#blickfeld.protocol.Status) | optional | Refer to [Status](#blickfeld.protocol.status.Status) | +| developer | [Event.Developer](#blickfeld.protocol.stream.Event.Developer) | optional | Refer to [Event.Developer](#blickfeld.protocol.stream.Event.Developer) | +| raw_file | [bytes](#bytes) | optional |
Introduced in BSL v2.13 and firmware v1.13
Raw bytes, which should be written sequentially in a file. Refer to [RawFile](#blickfeld.protocol.stream.Subscribe.RawFile). | +| end_of_stream | [Event.EndOfStream](#blickfeld.protocol.stream.Event.EndOfStream) | optional |
Introduced in BSL v2.13 and firmware v1.13
Refer to [EndOfStream](#blickfeld.protocol.stream.Event.EndOfStream) | - + -### Subscribe.Status -This request is used for subscribing to a status stream. +### Event.Developer +Internal use only + + + + + + + + +### Event.EndOfStream +> Introduced in BSL v2.13 and firmware v1.13 + +Event to indicate the end of stream. +This is called after an [Unsubscribe](#blickfeld.protocol.stream.Unsubscribe) request. +No further events will arrive for the subscribed stream after this event. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| subscription | [Subscribe](#blickfeld.protocol.stream.Subscribe) | optional | Ended subscription. Refer to [Subscribe](#blickfeld.protocol.stream.Subscribe). | diff --git a/doc/python_blickfeld_scanner.rst b/doc/python_blickfeld_scanner.rst index 93f5a37..ba6dcf9 100644 --- a/doc/python_blickfeld_scanner.rst +++ b/doc/python_blickfeld_scanner.rst @@ -27,3 +27,4 @@ The blickfeld_scanner python library is used for connecting to a device (:ref:`p python_scanner python_discover python_point_cloud_stream + python_raw_stream diff --git a/doc/python_examples.rst b/doc/python_examples.rst index 2964758..980278f 100644 --- a/doc/python_examples.rst +++ b/doc/python_examples.rst @@ -14,3 +14,5 @@ To fully understand the code, please refer to the source code as well. autoapi/getting_started_example/index.rst autoapi/configure_point_cloud_stream/index.rst autoapi/export_mat/index.rst + autoapi/record_point_cloud/index.rst + autoapi/read_point_cloud_file/index.rst diff --git a/doc/python_raw_stream.rst b/doc/python_raw_stream.rst new file mode 100644 index 0000000..d022b9e --- /dev/null +++ b/doc/python_raw_stream.rst @@ -0,0 +1,5 @@ +stream.raw +========== + +.. automodule:: blickfeld_scanner.stream.raw + :members: \ No newline at end of file diff --git a/examples/python/configure_point_cloud_stream.py b/examples/python/configure_point_cloud_stream.py index 234f80c..c8cf244 100644 --- a/examples/python/configure_point_cloud_stream.py +++ b/examples/python/configure_point_cloud_stream.py @@ -45,7 +45,7 @@ def configure_point_cloud_stream(args): if __name__ == "__main__": parser = argparse.ArgumentParser() # Command line argument parser - parser.add_argument("--target", "-t", help="hostname or IP of scanner to connect to") # host name or IP address of the device + parser.add_argument("target", help="hostname or IP of scanner to connect to") # host name or IP address of the device args = parser.parse_args() # Parse command line arguments configure_point_cloud_stream(args) # Start example diff --git a/examples/python/fetch_point_cloud.py b/examples/python/fetch_point_cloud.py index 7947bfa..14ed8f9 100644 --- a/examples/python/fetch_point_cloud.py +++ b/examples/python/fetch_point_cloud.py @@ -11,32 +11,28 @@ import blickfeld_scanner -def fetch_point_cloud(args): +def fetch_point_cloud(target): """Fetch the point cloud of a device and print the frame ID and number of returns in the received frame. - If the record_to_file parameter is set with a path and a filename, the stream will be recorded to this file. + This example will stop after 10 received frames. - Stop this example with CTRL+C. The file should be closed in a correct way. - - :param args: arguments to parse out the hostname or IP address of the device and an optional file path to record to. + :param target: hostname or IP address of the device """ - device = blickfeld_scanner.scanner(args.target) # Connect to the device - + device = blickfeld_scanner.scanner(target) # Connect to the device + stream = device.get_point_cloud_stream() # Create a point cloud stream object - - if args.record_to_file: - stream.record_to_file(args.record_to_file + ".bfpc") # Record the frames to a file - while True: + + for i in range(10): frame = stream.recv_frame() # Receive a frame. This frame will also be dumped into the file # Format of frame is described in protocol/blickfeld/data/frame.proto or doc/protocol.md # Protobuf API is described in https://developers.google.com/protocol-buffers/docs/pythontutorial print(f"Got {frame}") + stream.stop() + if __name__ == "__main__": parser = argparse.ArgumentParser() # Command line argument parser - parser.add_argument("--target", "-t", help="hostname or IP of scanner to connect to") # host name or IP address of the device - parser.add_argument("--record_to_file", "-r", default=None, help="Record point cloud to file") # optional filename/path to record to - + parser.add_argument("target", help="hostname or IP of scanner to connect to") # host name or IP address of the device + args = parser.parse_args() # Parse command line arguments - fetch_point_cloud(args) # Start example - + fetch_point_cloud(args.target) # Start example diff --git a/examples/python/getting_started_example.py b/examples/python/getting_started_example.py index 309104a..550f4fb 100644 --- a/examples/python/getting_started_example.py +++ b/examples/python/getting_started_example.py @@ -43,7 +43,7 @@ def getting_started_example(args): if __name__ == "__main__": parser = argparse.ArgumentParser() # Command line argument parser - parser.add_argument("--host", default="localhost", required=True, help="hostname or IP of device") # host name or IP address of the device + parser.add_argument("host", help="hostname or IP of device") # host name or IP address of the device args = parser.parse_args() # Parse command line arguments getting_started_example(args) # Start example \ No newline at end of file diff --git a/examples/python/read_point_cloud_file.py b/examples/python/read_point_cloud_file.py new file mode 100644 index 0000000..a3fc261 --- /dev/null +++ b/examples/python/read_point_cloud_file.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2020 Blickfeld GmbH. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE.md file in the root directory of this source tree. +# +from __future__ import print_function + +import argparse +import blickfeld_scanner + + +def read_point_cloud_file(file_path): + """Read bfpc dump and print the frame ID and number of returns in the received frame. + + This example will stop, when the dump is completly read out. + + :param file_path: Path to dump file + """ + file_stream = blickfeld_scanner.stream.point_cloud(from_file=file_path) # Connect to the device + + print(file_stream.get_metadata()) # print meta data of the dump (footer and header) + + while not file_stream.end_of_stream(): + frame = file_stream.recv_frame() # Receive a frame. + # Format of frame is described in protocol/blickfeld/data/frame.proto or doc/protocol.md + # Protobuf API is described in https://developers.google.com/protocol-buffers/docs/pythontutorial + print(f"Got {frame}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() # Command line argument parser + parser.add_argument("file_path", help="Path to dump file.") # path to dump file + + args = parser.parse_args() # Parse command line arguments + read_point_cloud_file(args.file_path) # Start example diff --git a/examples/python/record_point_cloud.py b/examples/python/record_point_cloud.py new file mode 100644 index 0000000..8de7670 --- /dev/null +++ b/examples/python/record_point_cloud.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2020 Blickfeld GmbH. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE.md file in the root directory of this source tree. +# +from __future__ import print_function + +import argparse +import blickfeld_scanner + + +def record_point_cloud(target, file_name): + """Record a raw point cloud and print the number of received bytes. + + If the record_to_file parameter is set with a path and a filename, the stream will be recorded to this file. + + :param target: hostname or IP address of the device + :param file_name: file path to record to + """ + device = blickfeld_scanner.scanner(args.target) # Connect to the device + + raw_stream = device.record_point_cloud_stream(file_name) # Create a raw point cloud stream object + + while True: + raw_file = raw_stream.recv_bytes() # Receive a raw file bytes + print(f"Got {len(raw_file)} bytes") + + raw_file = raw_stream.stop() + print(f"Got {len(raw_file)} bytes and stopped") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() # Command line argument parser + parser.add_argument("target", help="hostname or IP of scanner to connect to") # host name or IP address of the device + parser.add_argument("file_name", help="Record point cloud to file") # optional filename/path to record to + + args = parser.parse_args() # Parse command line arguments + record_point_cloud(args.target, args.file_name) # Start example diff --git a/include/blickfeld/scanner.h b/include/blickfeld/scanner.h index bdbaa7b..aabfffc 100644 --- a/include/blickfeld/scanner.h +++ b/include/blickfeld/scanner.h @@ -38,11 +38,8 @@ namespace google { namespace protobuf { namespace io { class IstreamInputStream; -class OstreamOutputStream; class GzipInputStream; -class GzipOutputStream; class CodedInputStream; -class CodedOutputStream; } } } @@ -64,6 +61,7 @@ class ScanPattern_Filter; } class connection; +class point_cloud_record; /** * Blickfeld Scanner class for point cloud streams, status, and configuration requests. @@ -102,9 +100,7 @@ class scanner : public logged_object { google::protobuf::io::GzipInputStream* pb_izstream = nullptr; google::protobuf::io::CodedInputStream* pb_icstream = nullptr; std::ostream* ostream = nullptr; - google::protobuf::io::OstreamOutputStream* pb_ostream = nullptr; - google::protobuf::io::GzipOutputStream* pb_ozstream = nullptr; - google::protobuf::io::CodedOutputStream* pb_ocstream = nullptr; + point_cloud_record* record = nullptr; #endif public: @@ -133,7 +129,7 @@ class scanner : public logged_object { * * @param ostream Output stream for point cloud file. The file format is described in the technical documentation: "Blickfeld Scanner Library : File Format". */ - void record_to_stream(std::ostream* ostream); + void record_to_stream(std::ostream* ostream, int compression_level = 1); /** * Stops the recording, which was started with record_to_stream. @@ -270,6 +266,14 @@ class scanner : public logged_object { static std::shared_ptr > simple_file_point_cloud_stream(std::istream* istream); #endif + /** + * > Introduced in BSL v2.13 and firmware v1.3 + * + * Can be used to attempt a re-initialization of the device if it is errored. + * A self test is automatically triggered after a successful re-initialization. + */ + void attempt_error_recovery(); + #ifndef BSL_STANDALONE /** * Fetches point cloud frames from the device. diff --git a/protocol/CMakeLists.txt b/protocol/CMakeLists.txt index f96ee93..4f01992 100644 --- a/protocol/CMakeLists.txt +++ b/protocol/CMakeLists.txt @@ -89,6 +89,7 @@ set(PROTOBUF_SOURCES blickfeld/status/main.proto blickfeld/status/scanner.proto blickfeld/status/temperature.proto + blickfeld/status/server.proto blickfeld/data/frame.proto blickfeld/data/point_cloud.proto @@ -99,6 +100,7 @@ set(PROTOBUF_SOURCES blickfeld/file/point_cloud.proto blickfeld/stream/connection.proto + blickfeld/stream/event.proto blickfeld/update/hardware.proto blickfeld/update/manifest.proto diff --git a/protocol/blickfeld/config/advanced.proto b/protocol/blickfeld/config/advanced.proto index 52db904..2e42339 100644 --- a/protocol/blickfeld/config/advanced.proto +++ b/protocol/blickfeld/config/advanced.proto @@ -1,6 +1,7 @@ syntax = "proto2"; import "blickfeld/options.proto"; +import "blickfeld/stream/connection.proto"; package blickfeld.protocol.config; @@ -29,6 +30,16 @@ message Advanced { optional float range_offset = 1 [default = 0, (d_min) = -2, (d_max) = 2]; // in [m] } + /** + * > Introduced in BSL v2.13 and firmware v1.13 + * + * Parameters, which control the server behavior. + */ + message Server { + optional stream.Subscribe.PointCloud default_point_cloud_subscription = 1; // Overwrite default point cloud subscription. Can still be overridden with client requests. + } + optional Detector detector = 1; // Refer to [Detector](#blickfeld.protocol.config.Advanced.Detector) optional Processing processing = 2; //
Introduced in BSL v2.12 and firmware v1.12
Refer to [Processing](#blickfeld.protocol.config.Advanced.Processing) + optional Server server = 3; //
Introduced in BSL v2.13 and firmware v1.13
Refer to [Server](#blickfeld.protocol.config.Advanced.Server) } diff --git a/protocol/blickfeld/connection.proto b/protocol/blickfeld/connection.proto index 6195b19..b2e6999 100644 --- a/protocol/blickfeld/connection.proto +++ b/protocol/blickfeld/connection.proto @@ -6,8 +6,9 @@ import "blickfeld/error.proto"; import "blickfeld/config/advanced.proto"; import "blickfeld/config/scan_pattern.proto"; import "blickfeld/config/secure.proto"; -import "blickfeld/stream/connection.proto"; import "blickfeld/status/main.proto"; +import "blickfeld/stream/connection.proto"; +import "blickfeld/stream/event.proto"; package blickfeld.protocol; @@ -37,10 +38,11 @@ message Request { * This request is used for configuring a Scan Pattern. */ message SetScanPattern { - optional config.ScanPattern config = 3 [(legacy_field_id) = 1]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) - optional bool persist = 2 [default = false]; // Persists the scan pattern and sets it after a power cycle. Default: False + reserved "legacy_config"; + reserved 1; - optional config.ScanPattern legacy_config = 1 [(optional) = true]; // Deprecated old 'config' due to horizontal field of view definition + optional config.ScanPattern config = 3; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) + optional bool persist = 2 [default = false]; // Persists the scan pattern and sets it after a power cycle. Default: False } /** @@ -49,9 +51,10 @@ message Request { * The filled scan pattern can then be set as input for [Request.SetScanPattern](#blickfeld.protocol.Request.SetScanPattern). */ message FillScanPattern { - optional config.ScanPattern config = 2 [(allow_sparse)=true, (legacy_field_id) = 1]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) + reserved "legacy_config"; + reserved 1; - optional config.ScanPattern legacy_config = 1 [(allow_sparse)=true, (optional) = true]; // Deprecated + optional config.ScanPattern config = 2 [(allow_sparse)=true]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) } /** @@ -94,6 +97,15 @@ message Request { message GetAdvancedConfig { } + /** + * > Introduced in BSL v2.13 and firmware v1.13 + * + * This request can be used to attempt a re-initialization of the device if it is errored. + * A self test is automatically triggered after a successful re-initialization. + */ + message AttemptErrorRecovery { + } + oneof data { Hello hello = 11; // Refer to [Request.Hello](#blickfeld.protocol.Request.Hello) Developer developer = 13; // Refer to [Request.Developer](#blickfeld.protocol.Request.Developer) @@ -105,6 +117,8 @@ message Request { RunSelfTest run_self_test = 20; // Refer to [Request.RunSelfTest](#blickfeld.protocol.Request.RunSelfTest) SetAdvancedConfig set_advanced_config = 21; //
Introduced in BSL v2.11 and firmware v1.11
Refer to [Request.SetAdvancedConfig](#blickfeld.protocol.Request.SetAdvancedConfig) GetAdvancedConfig get_advanced_config = 22; //
Introduced in BSL v2.11 and firmware v1.11
Refer to [Request.GetAdvancedConfig](#blickfeld.protocol.Request.GetAdvancedConfig) + stream.Subscribe unsubscribe = 23; //
Introduced in BSL v2.13 and firmware v1.13
Unsubscribe a stream started with a [Subscribe](#blickfeld.protocol.stream.Subscribe) request. + AttemptErrorRecovery attempt_error_recovery = 24; //
Introduced in BSL v2.13 and firmware v1.13
Refer to [Request.AttemptErrorRecovery](#blickfeld.protocol.Request.AttemptErrorRecovery) string _asJSON = 100; // Internal use only } @@ -146,20 +160,20 @@ message Response { * It returns a scan pattern, the unset fields are filled with default values. */ message FillScanPattern { - option (help) = ".."; - - optional config.ScanPattern config = 2 [(legacy_field_id) = 1]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) + reserved "legacy_config"; + reserved 1; - optional config.ScanPattern legacy_config = 1 [(optional) = true]; // Deprecated + optional config.ScanPattern config = 2; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) } /** * This response is returned after a request to get the current [ScanPattern](#blickfeld.protocol.config.ScanPattern). */ message GetScanPattern { - optional config.ScanPattern config = 2 [(legacy_field_id) = 1]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) + reserved "legacy_config"; + reserved 1; - optional config.ScanPattern legacy_config = 1 [(optional) = true]; // Deprecated + optional config.ScanPattern config = 2; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) } /** @@ -191,6 +205,14 @@ message Response { optional config.Advanced config = 1; // Refer to [Advanced](#blickfeld.protocol.config.Advanced) } + /** + * > Introduced in BSL v2.13 and firmware v1.13 + * + * This response is sent out after sending AttemptErrorRecovery. + */ + message AttemptErrorRecovery { + } + oneof data { Error error = 10; // Refer to [Error](#blickfeld.protocol.Error) Hello hello = 11; // Refer to [Response.Hello](#blickfeld.protocol.Response.Hello) @@ -203,6 +225,7 @@ message Response { RunSelfTest run_self_test = 20; // Refer to [Response.RunSelfTest](#blickfeld.protocol.Response.RunSelfTest) SetAdvancedConfig set_advanced_config = 21; //
Introduced in BSL v2.11 and firmware v1.11
Refer to [Response.SetAdvanced](#blickfeld.protocol.Response.SetAdvancedConfig) GetAdvancedConfig get_advanced_config = 22; //
Introduced in BSL v2.11 and firmware v1.11
Refer to [Response.GetAdvanced](#blickfeld.protocol.Response.GetAdvancedConfig) + AttemptErrorRecovery attempt_error_recovery = 24; //
Introduced in BSL v2.13 and firmware v1.13
Refer to [Response.AttemptErrorRecovery](#blickfeld.protocol.Response.AttemptErrorRecovery) string _asJSON = 100; // Internal use only } diff --git a/protocol/blickfeld/file/point_cloud.proto b/protocol/blickfeld/file/point_cloud.proto index d05943d..e65fbfb 100644 --- a/protocol/blickfeld/file/point_cloud.proto +++ b/protocol/blickfeld/file/point_cloud.proto @@ -1,4 +1,5 @@ syntax = "proto2"; +option cc_enable_arenas = true; import "blickfeld/options.proto"; import "blickfeld/data/point_cloud.proto"; diff --git a/protocol/blickfeld/status/main.proto b/protocol/blickfeld/status/main.proto index f315ad6..714a4e5 100644 --- a/protocol/blickfeld/status/main.proto +++ b/protocol/blickfeld/status/main.proto @@ -2,6 +2,7 @@ syntax = "proto2"; import "blickfeld/status/scanner.proto"; import "blickfeld/status/temperature.proto"; +import "blickfeld/status/server.proto"; package blickfeld.protocol; @@ -12,4 +13,5 @@ message Status { optional status.Scanner scanner = 1; // Refer to [Scanner](#blickfeld.protocol.status.scanner.Scanner) repeated status.Temperature temperatures = 2; // Refer to [Temperature](#blickfeld.protocol.status.temperature.Temperature) extensions 3, 4; // Internal use only + optional status.Server server = 5; // Refer to [Client](#blickfeld.protocol.status.server.Server) } \ No newline at end of file diff --git a/protocol/blickfeld/status/scanner.proto b/protocol/blickfeld/status/scanner.proto index 5fc4208..743a473 100644 --- a/protocol/blickfeld/status/scanner.proto +++ b/protocol/blickfeld/status/scanner.proto @@ -10,6 +10,9 @@ package blickfeld.protocol.status; * This section defines the status of the device. */ message Scanner { + reserved "legacy_scan_pattern"; + reserved 2; + enum State { INITIALIZING = 1; // Device is initializing the hardware. READY = 2; // Device is ready to start and no error occurred. @@ -21,8 +24,6 @@ message Scanner { } optional State state = 1; // Refer to [Scanner.State](#blickfeld.protocol.status.Scanner.State) - optional config.ScanPattern scan_pattern = 4 [(allow_sparse)=true, (legacy_field_id)=2]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) + optional config.ScanPattern scan_pattern = 4 [(allow_sparse)=true]; // Refer to [ScanPattern](#blickfeld.protocol.config.ScanPattern) optional Error error = 3 [(optional)=true]; // Refer to [Error](#blickfeld.protocol.Error) - - optional config.ScanPattern legacy_scan_pattern = 2 [(allow_sparse)=true, (optional)=true]; // Deprecated old ´scan_pattern´ definition } \ No newline at end of file diff --git a/protocol/blickfeld/status/server.proto b/protocol/blickfeld/status/server.proto new file mode 100644 index 0000000..b54cd06 --- /dev/null +++ b/protocol/blickfeld/status/server.proto @@ -0,0 +1,32 @@ +syntax = "proto2"; + +import "blickfeld/options.proto"; +import "blickfeld/stream/connection.proto"; + +package blickfeld.protocol.status; + +/** + * This section defines the status of the network server. + */ +message Server { + message NetworkStats { + message Channel { + optional uint64 total_byte_count = 1 [ default = 0 ]; + optional float bytes_per_second = 2 [ (optional)=true ]; + optional float maximum_bytes_per_second = 3 [ (optional)=true ]; + } + + optional Channel sent = 1; + optional Channel received = 2; + optional uint64 dropped_messages = 3 [ default = 0]; + } + + message Client { + repeated stream.Subscribe subscriptions = 1; + optional NetworkStats network_stats = 2; + optional string identifier = 3; + } + + repeated Client clients = 1; + optional NetworkStats network_stats = 2; +} \ No newline at end of file diff --git a/protocol/blickfeld/stream/connection.proto b/protocol/blickfeld/stream/connection.proto index a3a616f..9ccaeae 100644 --- a/protocol/blickfeld/stream/connection.proto +++ b/protocol/blickfeld/stream/connection.proto @@ -2,9 +2,7 @@ syntax = "proto2"; option cc_enable_arenas = true; import "blickfeld/options.proto"; -import "blickfeld/data/point_cloud.proto"; import "blickfeld/data/frame.proto"; -import "blickfeld/status/main.proto"; import "blickfeld/config/scan_pattern.proto"; package blickfeld.protocol.stream; @@ -18,9 +16,9 @@ message Subscribe { * This request is used for subscribing to a point cloud stream. */ message PointCloud { - optional data.Frame reference_frame = 1 [(optional)=true, (allow_sparse)=true]; //
Introduced in BSL v2.10 and firmware v1.9
If present, only fields that are set in this message and submessages will be present in the point cloud. If less fields are requested, the Protobuf encoding and network transport time can reduce significantly. optional config.ScanPattern.Filter filter = 2; //
Introduced in BSL v2.10 and firmware v1.9
Refer to [ScanPattern.Filter](#blickfeld.protocol.config.ScanPattern.Filter). Overrides parameters of scan pattern. + extensions 3; // Internal developer configuration } /** @@ -29,6 +27,20 @@ message Subscribe { message Status { } + /** + * > Introduced in BSL v2.13 and firmware v1.13 + * + * This request is used for subscribing to a raw file stream. + * The requested stream is directly packed in the Blickfeld data format and only raw bytes are sent to the client, which it should write sequentially in a file. + * An [Unsubscribe](#blickfeld.protocol.stream.Unsubscribe) request with the same request data must be sent to properly end the file stream. + * The request raw file stream is ended with an [EndOfStream](#blickfeld.protocol.stream.Event.EndOfStream) event. + */ + message RawFile { + oneof data { + PointCloud point_cloud = 1; // Subscribe to a raw point cloud stream. Refer to [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud). + } + } + /** * Internal use only */ @@ -44,25 +56,6 @@ message Subscribe { PointCloud point_cloud = 11; // Refer to [Subscribe.PointCloud](#blickfeld.protocol.stream.Subscribe.PointCloud) Status status = 12; // Refer to [Subscribe.Status](#blickfeld.protocol.stream.Subscribe.Status) Developer developer = 13; // Refer to [Subscribe.Developer](#blickfeld.protocol.stream.Subscribe.Developer) - } -} - -/** - * This section describes the events of streams. - */ -message Event { - - /** - * Internal use only - */ - message Developer { - extensions 1 to max; // Internal developer events - } - - oneof data { - option (optional_one_of) = true; - data.PointCloud point_cloud = 11; // Refer to [PointCloud](#blickfeld.protocol.data.PointCloud) - Status status = 12; // Refer to [Status](#blickfeld.protocol.status.Status) - Developer developer = 13; // Refer to [Event.Developer](#blickfeld.protocol.stream.Event.Developer) + RawFile raw_file = 14; // Refer to [Subscribe.RawFile](#blickfeld.protocol.stream.Subscribe.RawFile) } } diff --git a/protocol/blickfeld/stream/event.proto b/protocol/blickfeld/stream/event.proto new file mode 100644 index 0000000..0b2cce3 --- /dev/null +++ b/protocol/blickfeld/stream/event.proto @@ -0,0 +1,42 @@ +syntax = "proto2"; +option cc_enable_arenas = true; + +import "blickfeld/options.proto"; +import "blickfeld/data/point_cloud.proto"; +import "blickfeld/status/main.proto"; +import "blickfeld/stream/connection.proto"; + +package blickfeld.protocol.stream; + +/** + * This section describes the events of streams. + */ +message Event { + + /** + * Internal use only + */ + message Developer { + extensions 1 to max; // Internal developer events + } + + /** + * > Introduced in BSL v2.13 and firmware v1.13 + * + * Event to indicate the end of stream. + * This is called after an [Unsubscribe](#blickfeld.protocol.stream.Unsubscribe) request. + * No further events will arrive for the subscribed stream after this event. + */ + message EndOfStream { + optional stream.Subscribe subscription = 1; // Ended subscription. Refer to [Subscribe](#blickfeld.protocol.stream.Subscribe). + } + + oneof data { + option (optional_one_of) = true; + data.PointCloud point_cloud = 11; // Refer to [PointCloud](#blickfeld.protocol.data.PointCloud) + Status status = 12; // Refer to [Status](#blickfeld.protocol.status.Status) + Developer developer = 13; // Refer to [Event.Developer](#blickfeld.protocol.stream.Event.Developer) + bytes raw_file = 14; //
Introduced in BSL v2.13 and firmware v1.13
Raw bytes, which should be written sequentially in a file. Refer to [RawFile](#blickfeld.protocol.stream.Subscribe.RawFile). + EndOfStream end_of_stream = 15; //
Introduced in BSL v2.13 and firmware v1.13
Refer to [EndOfStream](#blickfeld.protocol.stream.Event.EndOfStream) + } +} diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7b8ca52..65b8d20 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -9,8 +9,13 @@ find_package(Python COMPONENTS Interpreter) # Note that PYTHON_VERSION is not the python version but the version of the package blickfeld_scanner -string(REGEX REPLACE "([0-9.]*)-" "\\1\+" CONDA_RELEASE_VERSION_TMP "${PROJECT_GIT_DESCRIBE}") -string(REGEX REPLACE "\\+([0-9]*)\\+" ".dev0\+\\1\." CONDA_RELEASE_VERSION "${CONDA_RELEASE_VERSION_TMP}") +string(REGEX REPLACE "([0-9.]*)-" "\\1\+" BF_BSL_PYTHON_VERSION_TMP "${PROJECT_GIT_DESCRIBE}") +string(REGEX REPLACE "\\+([0-9]*)\\+" ".dev0\+\\1\." BF_BSL_PYTHON_VERSION "${BF_BSL_PYTHON_VERSION_TMP}") + +get_directory_property(HAS_PARENT PARENT_DIRECTORY) +if(HAS_PARENT) + set(BF_BSL_PYTHON_VERSION ${BF_BSL_PYTHON_VERSION} PARENT_SCOPE) +endif() if(Python_Interpreter_FOUND) set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") @@ -41,7 +46,7 @@ if(Python_Interpreter_FOUND) COMMAND ${Python_EXECUTABLE} ${SETUP_PY} build COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/dist COMMAND ${Python_EXECUTABLE} ${SETUP_PY} sdist - COMMAND ${CMAKE_COMMAND} -E rename ${CMAKE_CURRENT_BINARY_DIR}/dist/blickfeld_scanner-${CONDA_RELEASE_VERSION}.tar.gz ${OUTPUT} + COMMAND ${CMAKE_COMMAND} -E rename ${CMAKE_CURRENT_BINARY_DIR}/dist/blickfeld_scanner-${BF_BSL_PYTHON_VERSION}.tar.gz ${OUTPUT} DEPENDS ${DEPS} ) diff --git a/python/blickfeld_scanner/scanner.py b/python/blickfeld_scanner/scanner.py index 8d53143..9e9eed6 100644 --- a/python/blickfeld_scanner/scanner.py +++ b/python/blickfeld_scanner/scanner.py @@ -16,8 +16,10 @@ import ssl import tempfile import os +import warnings from .protocol import connection_pb2 +from .protocol.stream import connection_pb2 as stream_connection_pb2 from .protocol import options_pb2 from . import stream @@ -92,20 +94,80 @@ def get_status_stream(self): """ return status_stream(self.create_connection()) - def get_point_cloud_stream(self, filter=None, reference_frame=None): + def get_point_cloud_stream(self, filter=None, reference_frame=None, point_filter=None): """ Request point cloud stream of device - :param filter: + :param filter: DEPRECATED + > Introduced in BSL v2.10 and firmware v1.9 - - Filter points and returns by point attributes during the post-processing on the device. - :param reference_frame: + + Filter points and returns by point attributes during the post-processing on the device. Is replaced by 'point_filter'. + :param reference_frame: > Introduced in BSL v2.10 and firmware v1.9 - + Frame representing the desired data. To request a field, set it to any value (also in submessages). For a repeated field, add at least one element. + :param point_filter: + > Introduced in BSL v2.13 and firmware v1.9 + + Filter points and returns by point attributes during the post-processing on the device. This replaces the 'filter' parameter :returns: :py:class:`blickfeld_scanner.scanner.stream.point_cloud` object """ - return stream.point_cloud(self.create_connection(), filter=filter, reference_frame=reference_frame) + if filter and point_filter: + raise Exception("Either provide 'filter' or 'point_filter'.\nPlease use 'point_filter' instead of 'filter'. 'filter' is deprecated.") + if filter: + warnings.warn("Please use 'point_filter' instead of 'filter'. 'filter' is deprecated.", DeprecationWarning, stacklevel=2) + point_filter = filter + return stream.point_cloud(self.create_connection(), point_filter=point_filter, reference_frame=reference_frame) + + def get_raw_point_cloud_stream(self, point_filter=None, reference_frame=None): + """ Request raw point cloud stream of device + + > Introduced in BSL v2.13 and firmware v1.13 + + :param point_filter: + > Introduced in BSL v2.10 and firmware v1.9 + + Filter points and returns by point attributes during the post-processing on the device. + :param reference_frame: + > Introduced in BSL v2.10 and firmware v1.9 + + Frame representing the desired data. To request a field, set it to any value (also in submessages). For a repeated field, add at least one element. + :returns: :py:class:`blickfeld_scanner.scanner.stream.raw` object + """ + req = stream_connection_pb2.Subscribe.RawFile() + req.point_cloud.SetInParent() + if point_filter: + req.point_cloud.filter.CopyFrom(point_filter) + if reference_frame: + req.point_cloud.reference_frame.CopyFrom(reference_frame) + + return stream.raw(self.create_connection(), req) + + def record_point_cloud_stream(self, file_name, point_filter=None, reference_frame=None): + """ Record point cloud stream to file + + > Introduced in BSL v2.13 and firmware v1.13 + + :param file_name: Path to the file where it should be dumped + + :param point_filter: + > Introduced in BSL v2.10 and firmware v1.9 + + Filter points and returns by point attributes during the post-processing on the device. + :param reference_frame: + > Introduced in BSL v2.10 and firmware v1.9 + + Frame representing the desired data. To request a field, set it to any value (also in submessages). For a repeated field, add at least one element. + :returns: :py:class:`blickfeld_scanner.scanner.stream.raw` object + """ + req = stream_connection_pb2.Subscribe.RawFile() + req.point_cloud.SetInParent() + if point_filter: + req.point_cloud.filter.CopyFrom(point_filter) + if reference_frame: + req.point_cloud.reference_frame.CopyFrom(reference_frame) + + return stream.raw(self.create_connection(), req, file_name) @staticmethod def file_point_cloud_stream(dump_filename): @@ -151,11 +213,11 @@ def get_scan_pattern(self): return self._connection.send_request(req).get_scan_pattern.config def set_advanced_config(self, config, persist = False): - """> Introduced in BSL v2.11 and firmware v1.11 - - Function to set advanced config, see: :any:`protobuf_protocol`. + """ Function to set advanced config, see: :any:`protobuf_protocol`. Expert parameters: It is not recommended to adapt this calibrated configuration without understanding the influences on the resulting point cloud quality. + > Introduced in BSL v2.11 and firmware v1.11 + :param config: advanced config to be set :param persist: Persist advanced config on device and reload it after a power-cycle :return: response advanced config, see :any:`protobuf_protocol` advanced config @@ -166,9 +228,9 @@ def set_advanced_config(self, config, persist = False): return self._connection.send_request(req).set_advanced_config def get_advanced_config(self): - """> Introduced in BSL v2.11 and firmware v1.11 + """ Returns the currently set advanced config, see: :any:`protobuf_protocol`. - Returns the currently set advanced config, see: :any:`protobuf_protocol`. + > Introduced in BSL v2.11 and firmware v1.11 :return: Currently set advanced config, see :any:`protobuf_protocol` advanced config """ @@ -233,6 +295,16 @@ def create_connection(self): :return: Newly created :py:class:`blickfeld_scanner.scanner.connection` """ return connection(self.hostname_or_ip, self.port, self._key_and_cert) + + def attempt_error_recovery(self): + """> Introduced in BSL v2.13 and firmware v1.13 + + This request can be used to attempt a re-initialization of the device if it is errored. + A self test is automatically triggered after a successful re-initialization. + """ + req = connection_pb2.Request() + req.attempt_error_recovery.SetInParent() + return self._connection.send_request(req).attempt_error_recovery @staticmethod def sync(devices, scan_pattern = None, target_frame_rate = None, max_time_difference = 0.1): diff --git a/python/blickfeld_scanner/stream/__init__.py b/python/blickfeld_scanner/stream/__init__.py index 2f88994..667f3d8 100644 --- a/python/blickfeld_scanner/stream/__init__.py +++ b/python/blickfeld_scanner/stream/__init__.py @@ -11,6 +11,8 @@ __all__ = [ "point_cloud", + "raw", ] -from .point_cloud import point_cloud \ No newline at end of file +from .point_cloud import point_cloud +from .raw import raw diff --git a/python/blickfeld_scanner/stream/point_cloud.py b/python/blickfeld_scanner/stream/point_cloud.py index 28a7c86..2582f03 100644 --- a/python/blickfeld_scanner/stream/point_cloud.py +++ b/python/blickfeld_scanner/stream/point_cloud.py @@ -12,6 +12,7 @@ import math import gzip import time +import warnings from google.protobuf.internal.decoder import _DecodeVarint32 from google.protobuf.internal.encoder import _EncodeVarint @@ -30,7 +31,6 @@ def signal_handler(sig, frame): global terminate terminate = True - # Catch CTRL-C and terminate and close the point cloud stream signal.signal(signal.SIGINT, signal_handler) @@ -90,20 +90,34 @@ class point_cloud(object): :param connection: connection to the device :type connection: :py:class:`blickfeld_scanner.scanner.connection` + :param from_file: path to file, of which to stream a point cloud, if this is set, no connection should be given + :param filter: DEPRECATED + + > Introduced in BSL v2.10 and firmware v1.9 + + Filter points and returns by point attributes during the post-processing on the device. Is replaced by 'point_filter'. + :param reference_frame: + > Introduced in BSL v2.10 and firmware v1.9 + + Frame representing the desired data. To request a field, set it to any value (also in submessages). For a repeated field, add at least one element. + :param point_filter: + > Introduced in BSL v2.10 and firmware v1.9 + + Filter points and returns by point attributes during the post-processing on the device. This replaces the 'filter' parameter + + :param extend_subscribe_request: + > Introduced in BSL v2.13 and firmware v1.13 + + Extend point cloud subscription request with additional parameters. This is mainly used internally. """ - """ Reference frame: XYZ coordinates """ - REF_FRAME_XYZ = REF_FRAME_XYZ - """ Reference frame: XYZ coordinates, intensity """ - REF_FRAME_XYZ_I = REF_FRAME_XYZ_I - """ Reference frame: XYZ coordinates, intensity, frame id, scanline id, point id, return id """ - REF_FRAME_XYZ_I_ID = REF_FRAME_XYZ_I_ID - """ Reference frame: XYZ coordinates, intensity, frame id, scanline id, point id, return id, timestamps """ - REF_FRAME_XYZ_I_ID_TS = REF_FRAME_XYZ_I_ID_TS - """ Reference frame: ambient_light_level, intensity, range, frame id, scanline id, point id """ - REF_FRAME_DEPTH_MAP = REF_FRAME_DEPTH_MAP - - def __init__(self, connection=None, from_file=None, filter=None, reference_frame=None): + REF_FRAME_XYZ = REF_FRAME_XYZ #: Reference frame: XYZ coordinates + REF_FRAME_XYZ_I = REF_FRAME_XYZ_I #: Reference frame: XYZ coordinates, intensity + REF_FRAME_XYZ_I_ID = REF_FRAME_XYZ_I_ID #: Reference frame: XYZ coordinates, intensity, frame id, scanline id, point id, return id + REF_FRAME_XYZ_I_ID_TS = REF_FRAME_XYZ_I_ID_TS #: Reference frame: XYZ coordinates, intensity, frame id, scanline id, point id, return id, timestamps + REF_FRAME_DEPTH_MAP = REF_FRAME_DEPTH_MAP #: Reference frame: ambient_light_level, intensity, range, frame id, scanline id, point id + + def __init__(self, connection=None, from_file=None, filter=None, reference_frame=None, point_filter=None, extend_subscribe_request=None): self._metadata = point_cloud_pb2.PointCloud.Metadata() if connection and from_file: @@ -125,10 +139,17 @@ def __init__(self, connection=None, from_file=None, filter=None, reference_frame req = connection_pb2.Request() req.subscribe.point_cloud.SetInParent() + if filter and point_filter: + raise Exception("Either provide 'filter' or 'point_filter'.\nPlease use 'point_filter' instead of 'filter'. 'filter' is deprecated.") + if point_filter: + req.subscribe.point_cloud.filter.CopyFrom(point_filter) if filter: + warnings.warn("Please use 'point_filter' instead of 'filter'. 'filter' is deprecated.", DeprecationWarning, stacklevel=2) req.subscribe.point_cloud.filter.CopyFrom(filter) if reference_frame: req.subscribe.point_cloud.reference_frame.CopyFrom(reference_frame) + if extend_subscribe_request: + req.subscribe.point_cloud.MergeFrom(extend_subscribe_request) self._metadata.header.device.CopyFrom(self._connection.send_request(req).event.point_cloud.header) @@ -139,8 +160,11 @@ def __init__(self, connection=None, from_file=None, filter=None, reference_frame # Open file self._file_name = from_file self._file = gzip.open(self._file_name, 'rb') - self._file.seek(0, 2) - self._file_size = self._file.tell() + try: + self._file.seek(0, 2) + self._file_size = self._file.tell() + except EOFError as e: + self._file_size = None self._file.seek(0, 0) # Read header @@ -149,16 +173,17 @@ def __init__(self, connection=None, from_file=None, filter=None, reference_frame self._metadata.header.ParseFromString(buf_header) self._stream_buffered = False - # Find footer offset - while self._file_size > self._file.tell(): - offset_footer = self._file.tell() - self._get_file_block() - - # Read footer - self._file.seek(offset_footer) - if self._read_stream(): - self._metadata.footer.CopyFrom(self._stream_data.footer) - self._stream_buffered = False + if self._file_size: + # Find footer offset + while self._file_size > self._file.tell(): + offset_footer = self._file.tell() + self._get_file_block() + + # Read footer + self._file.seek(offset_footer) + if self._read_stream(): + self._metadata.footer.CopyFrom(self._stream_data.footer) + self._stream_buffered = False self._file.seek(offset_data) def __del__(self): @@ -189,13 +214,16 @@ def _get_file_block(self): return buf def _read_stream(self): - if self._file_size == self._file.tell(): + if self._file_size and self._file_size == self._file.tell(): self._stream_buffered = False return False self._stream_data.Clear() - self._stream_data.ParseFromString(self._get_file_block()) - self._stream_buffered = True + try: + self._stream_data.ParseFromString(self._get_file_block()) + self._stream_buffered = True + except EOFError as e: + self._stream_buffered = False return self._stream_buffered def get_metadata(self): @@ -281,7 +309,12 @@ def record_to_file(self, file_name, compresslevel=1): :param file_name: Path to the file where it should be dumped :param compresslevel: The compresslevel argument is an integer from 0 to 9 controlling the level of compression; 1 is fastest and produces the least compression, and 9 is slowest and produces the most compression. 0 is no compression. The default is 1. If frames are lost during the recording decrease the compression level. + + .. deprecated:: 2.13.0 + Since BSL v2.13.0 this function is deprecated, because of performance issues. Please use :py:func:`blickfeld_scanner.scanner.scanner.record_point_cloud_stream` for a performance improved recording. """ + warnings.warn("This function is deprecated, because of performance issues. Please use scanner.record_point_cloud_stream for a performance improved recording.", DeprecationWarning, stacklevel=2) + if self._ofile: raise Exception("The output file has already been opened. To open another file, please call 'stop_recording' first to close the current output file.") self._prev_scan_pattern_str = b"" @@ -300,3 +333,15 @@ def stop_recording(self): self._ofile.close() self._ofile = None + + def stop(self): + """ Stop and unsubscribe of the stream + """ + self.stop_recording() + + if self._connection: + req = connection_pb2.Request() + req.unsubscribe.point_cloud.SetInParent() + self._connection.send(req) + else: + raise NotImplementedError("File stream not supported.") diff --git a/python/blickfeld_scanner/stream/raw.py b/python/blickfeld_scanner/stream/raw.py new file mode 100644 index 0000000..5b37c41 --- /dev/null +++ b/python/blickfeld_scanner/stream/raw.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# +# Copyright (c) 2020 Blickfeld GmbH. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE.md file in the root directory of this source tree. +# + +from __future__ import print_function + +from ..protocol import connection_pb2 + +import signal +import sys + +terminate = False +def signal_handler(sig, frame): + global terminate + terminate = True + +# Catch CTRL-C and terminate and close the raw file stream +signal.signal(signal.SIGINT, signal_handler) + +class raw(object): + """ Class to request a point cloud stream + + > Introduced in BSL v2.13 and firmware v1.13 + + :param connection: connection to the device + :type connection: :py:class:`blickfeld_scanner.scanner.connection` + :param req: request for the raw stream, for example a raw point cloud stream + :param file_name: Path to the file where it should be dumped + """ + + def __init__(self, connection, req, file_name = None): + + if not connection: + raise AttributeError("Connection is not provided.") + + if file_name: + self._ofile = open(file_name, 'wb') + else: + self._ofile = None + + self._req = req + req = connection_pb2.Request() + req.subscribe.raw_file.CopyFrom(self._req) + + self._connection = connection + self._connection.send_request(req) + + def __del__(self): + self.stop() + + def recv_bytes(self): + """ Receive raw bytes + + When a file during creating of the raw object is given, this function will dump the bytes to the file. + + Will throw a `ConnectionError` when the end_of_stream field is set in the received event. + + :return: bytes + """ + + # If stream is closed with CTRL-C it will still write the footer and save the file + global terminate + if terminate: + terminate = False + self.stop() + raise Exception("Detected SIGINT during recv_bytes, stream and file were closed gracefully.") + + resp = self._connection.recv().event + + if resp.HasField("end_of_stream"): + raise ConnectionError("Stream was closed by the server.") + + # Record frame to file + if self._ofile: + self._ofile.write(resp.raw_file) + + return resp.raw_file + + def stop(self): + """ Stop and unsuscribe of the stream. + + Receive all remaining packets and the footer. + Will receive packets until the `ConnectionError` is raised, when the server sends the end_of_stream event. + + :return: all remaining bytes accumalated + """ + req = connection_pb2.Request() + req.unsubscribe.raw_file.CopyFrom(self._req) + self._connection.send(req) + + raw_file = b"" + + while True: + try: + raw_file += self.recv_bytes() + except ConnectionError: + break + + if self._ofile: + self._ofile.close() + self._ofile = None + + return raw_file diff --git a/python/meta.yaml b/python/meta.yaml index 04b56aa..0c7bb17 100755 --- a/python/meta.yaml +++ b/python/meta.yaml @@ -1,6 +1,6 @@ package: name: blickfeld_scanner - version: @CONDA_RELEASE_VERSION@ + version: @BF_BSL_PYTHON_VERSION@ source: path: ./ diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..4d690ca --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,3 @@ +protobuf +packaging +twine \ No newline at end of file diff --git a/python/setup.py.in b/python/setup.py.in index 69e28aa..8b135f9 100644 --- a/python/setup.py.in +++ b/python/setup.py.in @@ -10,7 +10,7 @@ with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: setuptools.setup( name="blickfeld_scanner", - version="${CONDA_RELEASE_VERSION}", + version="${BF_BSL_PYTHON_VERSION}", author="Blickfeld GmbH", author_email="opensource@blickfeld.com", description="Python package to communicate with LiDAR devices of the Blickfeld GmbH.", diff --git a/python/version.py.in b/python/version.py.in index 9f00675..1ed3916 100644 --- a/python/version.py.in +++ b/python/version.py.in @@ -1 +1 @@ -__version__ = "@CONDA_RELEASE_VERSION@" \ No newline at end of file +__version__ = "@BF_BSL_PYTHON_VERSION@" \ No newline at end of file diff --git a/src/point_cloud_record.cpp b/src/point_cloud_record.cpp new file mode 100644 index 0000000..a1b2455 --- /dev/null +++ b/src/point_cloud_record.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020 Blickfeld GmbH. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE.md file in the root directory of this source tree. + */ + +#include "point_cloud_record.h" + +#ifdef BSL_RECORDING +namespace blickfeld { + +point_cloud_record::point_cloud_record(std::ostream* ostream, int compression_level) + : ostream(ostream), pb_ostream(ostream), pb_ozstream(&pb_ostream, [compression_level]() { + struct google::protobuf::io::GzipOutputStream::Options options; + options.compression_level = compression_level; + return options; + } ()) { +} + +point_cloud_record::~point_cloud_record() { + finish(); +} + +void point_cloud_record::start(const protocol::data::PointCloud::Header& header) { + metadata.mutable_header()->mutable_device()->CopyFrom(header); + + // Write client header + auto client_header = metadata.mutable_header()->mutable_client(); + client_header->set_library_version(BSL_VERSION); + client_header->set_file_time_ns(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); + client_header->set_language(protocol::file::Client::CPP); + + // Write header + google::protobuf::io::CodedOutputStream pb_ocstream(&pb_ozstream); + pb_ocstream.WriteVarint32(metadata.header().ByteSizeLong()); + metadata.header().SerializeWithCachedSizes(&pb_ocstream); +} + +void point_cloud_record::record_data(const protocol::file::PointCloud_Data* stream_data) { + google::protobuf::io::CodedOutputStream pb_ocstream(&pb_ozstream); + pb_ocstream.WriteVarint32(stream_data->ByteSizeLong()); + stream_data->SerializeWithCachedSizes(&pb_ocstream); + + // Add events for changed scan pattern to footer + if (!google::protobuf::util::MessageDifferencer::ApproximatelyEquals(scan_pattern, stream_data->frame().scan_pattern())) { + auto event = metadata.mutable_footer()->add_events(); + event->set_from_frame_id(stream_data->frame().id()); + event->mutable_scan_pattern()->CopyFrom(stream_data->frame().scan_pattern()); + + scan_pattern.CopyFrom(stream_data->frame().scan_pattern()); + } + + // Update stats in footer + auto mut_counter = metadata.mutable_footer()->mutable_stats()->mutable_counter(); + mut_counter->set_frames(mut_counter->frames() + 1); + mut_counter->set_points(mut_counter->points() + stream_data->frame().total_number_of_points()); + mut_counter->set_returns(mut_counter->returns() + stream_data->frame().total_number_of_returns()); +} + +void point_cloud_record::finish() { + if (finished) + return; + + finished = true; + + google::protobuf::io::CodedOutputStream pb_ocstream(&pb_ozstream); + protocol::file::PointCloud_Data stream_data; + stream_data.mutable_footer()->CopyFrom(metadata.footer()); + pb_ocstream.WriteVarint32(stream_data.ByteSizeLong()); + stream_data.SerializeWithCachedSizes(&pb_ocstream); +} + +streambuf_point_cloud_record::streambuf_point_cloud_record(int compression_level) + : out_stream(&out_buffer), record(new point_cloud_record(&out_stream, compression_level)) { +} + +streambuf_point_cloud_record::~streambuf_point_cloud_record() { + finish(); +} + +void streambuf_point_cloud_record::finish() { + if (!record || record->finished) + return; + record->finish(); + delete record; + record = nullptr; +} + +size_t streambuf_point_cloud_record::available() { + return out_buffer.data().size(); +} + +void streambuf_point_cloud_record::consume(void* data) { + memcpy(data, out_buffer.data().data(), out_buffer.data().size()); + out_buffer.consume(out_buffer.data().size()); +} + +} // namespace blickfeld +#endif // #ifdef BSL_RECORDING diff --git a/src/point_cloud_record.h b/src/point_cloud_record.h new file mode 100644 index 0000000..b633463 --- /dev/null +++ b/src/point_cloud_record.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Blickfeld GmbH. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE.md file in the root directory of this source tree. + */ + +#pragma once + +#include +#include "blickfeld/scanner-config.h" +#include "blickfeld/scanner.h" +#include "blickfeld/file/point_cloud.pb.h" + +#ifdef BSL_RECORDING +#include +#include +#include + +namespace blickfeld { + +class streambuf_point_cloud_record; + +class point_cloud_record { + friend class streambuf_point_cloud_record; + + protocol::file::PointCloud_Metadata metadata; + protocol::config::ScanPattern scan_pattern; + + std::ostream* ostream = nullptr; + google::protobuf::io::OstreamOutputStream pb_ostream; + google::protobuf::io::GzipOutputStream pb_ozstream; + bool finished = false; + +public: + point_cloud_record(std::ostream* ostream, int compression_level = 0); + virtual ~point_cloud_record(); + + void start(const protocol::data::PointCloud::Header& header); + void record_data(const protocol::file::PointCloud_Data* data); + void finish(); + + bool is_finished() { + return finished; + } + +}; + +class streambuf_point_cloud_record { + std::ostream out_stream; + asio::streambuf out_buffer; + point_cloud_record* record; + +public: + streambuf_point_cloud_record(int compression_level = 0); + virtual ~streambuf_point_cloud_record(); + + void start(const protocol::data::PointCloud::Header& header) { + record->start(header); + } + + void record_data(const protocol::file::PointCloud_Data* data) { + record->record_data(data); + } + + void finish(); + bool is_finished() { + return record->finished; + } + + size_t available(); + void consume(void* data); +}; + +} +#endif // #ifdef BSL_RECORDING diff --git a/src/scanner.cpp b/src/scanner.cpp index d2fd05b..32de122 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -14,6 +14,7 @@ #include "blickfeld/protocol_exception.h" #include "scanner_connection.h" +#include "point_cloud_record.h" #include #include @@ -77,12 +78,6 @@ scanner::point_cloud_stream::point_cloud_stream(std::shared_ptrmutable_point_cloud()->mutable_reference_frame()->CopyFrom(*reference_frame); conn->send_request(req, resp); metadata->mutable_header()->mutable_device()->CopyFrom(resp.event().point_cloud().header()); - - // Write client header - auto client_header = metadata->mutable_header()->mutable_client(); - client_header->set_library_version(BSL_VERSION); - client_header->set_file_time_ns(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); - client_header->set_language(protocol::file::Client::CPP); } #ifdef BSL_RECORDING @@ -158,45 +153,23 @@ scanner::point_cloud_stream::~point_cloud_stream() { #ifdef BSL_RECORDING if (stream_data) delete stream_data; - if (pb_icstream) - delete pb_icstream; - if (pb_izstream) - delete pb_izstream; - if (pb_istream) - delete pb_istream; + if (record) + delete record; #endif } template const frame_t& scanner::point_cloud_stream::recv_frame() { if (conn) { - // Previous frame - auto frame_prev = (static_cast(*resp)).event().point_cloud().frame(); - // Receive and extract frame conn->recv(*resp); auto frame_i = (static_cast(*resp)).event().point_cloud().frame(); #ifdef BSL_RECORDING - // Record frame to stream - if (pb_ocstream) { - auto data = protocol::file::PointCloud_Data(); + if (record) { + protocol::file::PointCloud_Data data; data.mutable_frame()->CopyFrom(frame_i); - pb_ocstream->WriteVarint32(static_cast(data.ByteSizeLong())); - data.SerializeWithCachedSizes(pb_ocstream); - - // Add events for changed scan pattern to footer - if (!google::protobuf::util::MessageDifferencer::ApproximatelyEquals(frame_prev.scan_pattern(), frame_i.scan_pattern())) { - auto event = metadata->mutable_footer()->add_events(); - event->set_from_frame_id(frame_i.id()); - event->mutable_scan_pattern()->CopyFrom(frame_i.scan_pattern()); - } - - // Update stats in footer - auto mut_counter = metadata->mutable_footer()->mutable_stats()->mutable_counter(); - mut_counter->set_frames(mut_counter->frames() + 1); - mut_counter->set_points(mut_counter->points() + frame_i.total_number_of_points()); - mut_counter->set_returns(mut_counter->returns() + frame_i.total_number_of_returns()); + record->record_data(&data); } #endif @@ -226,35 +199,20 @@ void scanner::point_cloud_stream::subscribe(subscribe_callback_t cb) { #ifdef BSL_RECORDING template -void scanner::point_cloud_stream::record_to_stream(std::ostream* ostream) { - if (pb_ocstream) { - delete pb_ocstream; - delete pb_ozstream; - delete pb_ostream; - this->ostream->flush(); - } +void scanner::point_cloud_stream::record_to_stream(std::ostream* ostream, int compression_level) { + stop_recording(); this->ostream = ostream; - pb_ostream = new google::protobuf::io::OstreamOutputStream(ostream); - pb_ozstream = new google::protobuf::io::GzipOutputStream(pb_ostream); - pb_ocstream = new google::protobuf::io::CodedOutputStream(pb_ozstream); - - pb_ocstream->WriteVarint32(metadata->header().ByteSizeLong()); - metadata->header().SerializeWithCachedSizes(pb_ocstream); + record = new point_cloud_record(ostream, compression_level); + record->start(metadata->header().device()); } template void scanner::point_cloud_stream::stop_recording() { - if (pb_ocstream) { - auto data = protocol::file::PointCloud_Data(); - data.mutable_footer()->CopyFrom(metadata->footer()); - pb_ocstream->WriteVarint32(static_cast(data.ByteSizeLong())); - data.SerializeWithCachedSizes(pb_ocstream); - - delete pb_ocstream; - delete pb_ozstream; - delete pb_ostream; - ostream->flush(); + if (record) { + delete record; + record = nullptr; + this->ostream->flush();; metadata->mutable_footer()->Clear(); } } @@ -462,6 +420,13 @@ std::shared_ptr > scanner:: #endif +void scanner::attempt_error_recovery() { + protocol::Request req; + protocol::Response resp; + req.mutable_attempt_error_recovery(); + conn->send_request(req, resp); +} + #ifndef BSL_STANDALONE std::shared_ptr > scanner::get_point_cloud_stream() {