{"id":21854787,"url":"https://github.com/nandite/r2000","last_synced_at":"2025-04-14T18:06:34.917Z","repository":{"id":43921945,"uuid":"497321783","full_name":"Nandite/R2000","owner":"Nandite","description":"Communication interface for the Pepperl+Fuchs R2000 series sensor implemented in C++17 for embedded applications. ","archived":false,"fork":false,"pushed_at":"2024-04-05T13:55:22.000Z","size":8526,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-14T18:06:28.044Z","etag":null,"topics":["cpp17","lidar","lidar-point-cloud","omd30m","omd60m","omdxx","pepperlfuchs","r2000","robotics-programming","sensors","sensors-data-collection"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Nandite.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-05-28T13:11:48.000Z","updated_at":"2024-12-25T21:38:33.000Z","dependencies_parsed_at":"2023-01-22T03:48:08.061Z","dependency_job_id":null,"html_url":"https://github.com/Nandite/R2000","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nandite%2FR2000","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nandite%2FR2000/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nandite%2FR2000/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nandite%2FR2000/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nandite","download_url":"https://codeload.github.com/Nandite/R2000/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248933339,"owners_count":21185460,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp17","lidar","lidar-point-cloud","omd30m","omd60m","omdxx","pepperlfuchs","r2000","robotics-programming","sensors","sensors-data-collection"],"created_at":"2024-11-28T02:11:18.788Z","updated_at":"2025-04-14T18:06:34.897Z","avatar_url":"https://github.com/Nandite.png","language":"C++","readme":"R2000\n===============\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"res/r2000.jpg\" width=\"200\" height=\"200\"/\u003e\u003c/p\u003e\n\n[![][license-image]][license]\n![][platform-image]\n\n[license-image]: https://img.shields.io/badge/license-MIT-green.svg?style=flat-square\n[license]: https://github.com/Nandite/PclWalkerViewer/blob/master/LICENSE\n\n[platform-image]: https://img.shields.io/badge/platorms-linux64%20%7C%20osx%20%7C%20windows-lightgrey?style=flat-square\n\nThis is an implementation of a communication interface for the [OMDxxx - R2000](https://www.pepperl-fuchs.com/global/en/R2000_Detection_laser_scanner.htm) \nsensors series of Pepperl+Fuchs in C++17 for embedded applications. \nThis interface offers to quickly set up the sensor for scan acquisition in TCP or UDP channels. Advanced configuration \nfor the scan acquisition is also available depending on the needs. The sensor can also be parameterized in details using\nthe facilities of the interface. The implementation was done based on the [Protocol Version 1.04](docs/doct3469f.pdf).\nMost of the functionalities offered by this version of the communication protocol are supported by the current implementation.\nHowever, this interface can still be used with sensors having lower protocol version. Some command will just not be available\non the sensor side and will end up in errors on execution attempt.\n\n## Dependencies\n\nThis project depends on:\n- [Boost](https://www.boost.org/) \u003e= 1.64 \n- A [compiler](https://en.cppreference.com/w/cpp/compiler_support) supporting \u003e= C++17\n\n## Quick setup\n\nThe below example show how to set up a data link to receive scan from the sensor synchronously using TCP:   \n```cpp\nconst auto device{Device::R2000::makeShared({\"R2000\", deviceAddress})};\nauto handleParameters{Device::Parameters::ReadWriteParameters::TcpHandle{}\n                              .withWatchdog()\n                              .withWatchdogTimeout(5000)};\nauto [requestResult, dataLink]{Device::DataLinkBuilder(handleParameters).build(device)}; // Blocking build\nif(requestResult != Device::RequestResult::SUCCESS)\n{\n     // There was an error building the channel, check requestResult value\n}\n// At this stage the channel is build and the sensor is sending data\ndataLink-\u003eaddOnNewScanAvailableCallback([](const auto\u0026 newScan) { \n            // A new scan has been delivered, do something with it ! \n}); // Add a new callback to be notified when a new scan is available\n```\n\nYou can also do it asynchronously (advised):\n\n```cpp\nconst auto device{Device::R2000::makeShared({\"R2000\", deviceAddress})}; // Make a new device\nauto handleParameters{Device::Parameters::ReadWriteParameters::TcpHandle{}\n                              .withWatchdog()\n                              .withWatchdogTimeout(5000)}; // Request a Tcp channel with watchdog enabled\nauto future{Device::DataLinkBuilder(handleParameters).build(device, 1s)}; // Build the channel asynchronously with 1s timeout\n// Do some stuff if you want while the channel is being built\nauto [asyncRequestResult, dataLink]{future.get()}; // Get the result and the DataLink\nif (asyncRequestResult != Device::RequestResult::SUCCESS) {\n    // There was an error building the channel, check requestResult value\n}\n// At this stage the channel is build and the sensor is sending data\ndataLink-\u003eaddOnNewScanAvailableCallback([](const auto\u0026 newScan) { \n            // A new scan has been delivered, do something with it ! \n}); // Add a new callback to be notified when a new scan is available\n```\nNote: Doing the operations asynchronously is preferable as you can set a timeout to avoid lengthy freeze in case of\nnetwork failures.\n\n## How does it work ?\n\nThe interface is articulated around three concepts:\n- **Commands and Parameters**, allow to control the sensor and configure it.\n- **DataLinks**, allow to stream scans from the sensor using TCP or UDP\n- **StatusWatcher**, watch for events and queries the device periodically and gather analytics data on the status of the sensor.\n\nAll these concepts revolve around the **R2000** device object. The first thing to do is to always instantiate a device:\n```cpp\nDeviceConfiguration configuration{\"DeviceName\", \"192.168.1.60\"}\nconst auto device{Device::R2000::makeShared(configuration)};\n//or\nconst auto device{Device::R2000::makeShared({\"DeviceName\", \"192.168.1.60\"})};\n```\nA **R2000** cannot be directly constructed. it can only be obtained through the static _**makeShared**_ method. The\nmethod returns a new instance of R2000 through a shared_ptr. The R2000 instance object is necessary to use any of the\nabove three concepts.\n\nFor every sensor on the network, an instance of **R2000** can be constructed to interact with it.\n\n### Parameters and Commands \n\n#### Parameters\n\nTo configure or interact with the sensor, Parameters and Commands objects are used. These objects are a direct materialization \nof the parameters and commands mentioned in the [Protocol Version](docs/doct3469f.pdf) documentation.\n\nThe parameters are divided eight groups over two access type, ReadWrite and ReadOnly:\n- BasicInformation, provides basic information on the sensor (RW,RO)\n- Capabilities, provides information about the sensor capabilities (RO) \n- Ethernet, control the sensor ethernet configuration (RW,RO)\n- Measure, control the sensor measurement configuration (RW,RO)\n- HmiDisplay, control the sensor HMI (RW)\n- SystemStatus, provides information about the status of the sensor (RO)\n- UdpHandle, used to set up a UDP scan stream with custom parameters (RW)\n- TcpHandle, used to set up a TCP scan stream with custom parameters (RW)\n\nThe parameters are basically [builders](https://en.wikipedia.org/wiki/Builder_pattern) using the [fluent interface pattern](https://en.wikipedia.org/wiki/Fluent_interface)\nto easily compose a set of parameters that can be read or modified by a command. Setting up a builder necessitate the specification of its parameters access type, meaning ReadOnly\nor ReadWrite, then specify the parameters we want to act upon or read from.\nExample with RW parameters:\n```cpp\n#include \"Control/Parameters.hpp\"\n\n//...\n\n// Request the HMI parameters as RW then unlock the HMI button, set the display language to english, set the display mode\n// to application text then provides the two lines of text to display.\nconst auto hmiParameters{Device::Parameters::ReadWriteParameters::HmiDisplay()\n                                 .unlockHmiButton()\n                                 .withHmiLanguage(Device::Parameters::Language::ENGLISH)\n                                 .withHmiDisplayMode(Device::Parameters::HMI_DISPLAY_MODE::APPLICATION_TEXT)\n                                 .withHmiApplicationText1(\"Line 1\")\n                                 .withHmiApplicationText2(\"Line 2\")};\n// Pass the parameters to a special command, so they can be written into the sensor.\n// ...\n\n// Set the operating mode to measure (activate the measuring head), the frequency of scan to 35 Hz, the number of points\n// per scan to 7200 and the direction of rotation to counter clock wise.\nconst auto measurementParameters{Device::Parameters::ReadWriteParameters::Measure()\n                                  .withOperatingMode(Device::Parameters::OPERATING_MODE::MEASURE)\n                                  .withScanFrequency(35.0)\n                                  .withSamplesPerScan(7200)\n                                  .withScanDirection(Device::Parameters::SCAN_DIRECTION::CCW)};\n// Pass the parameters to a special command, so they can be written into the sensor.\n// ...\n```\nExample with RO parameters:\n```cpp\n// Request a bunch of parameters from the status of the sensor.\nconst auto systemParameters{Parameters::ReadOnlyParameters::SystemStatus{}.requestLoadIndication()\n                        .requestSystemTimeRaw()\n                        .requestUpTime()\n                        .requestPowerCycles()\n                        .requestOperationTime()\n                        .requestOperationTimeScaled()\n                        .requestCurrentTemperature()\n                        .requestMinimalTemperature()\n                        .requestMaximalTemperature()\n                        .requestStatusFlags()};\n```\nThere are builders for all the eight group of parameters and their access type. \n\n#### Commands\n\nThe Commands object are lightweight proxies that take Parameters objects (or nothing) as input and form a valid expression \ninterpretable by the sensor to modify its internal state or to perform an action.\nThere are sixteen constructible commands available to interact with the sensors:\n```cpp\n#include \u003cControl/Commands.hpp\u003e\n// ...\nDevice::Commands::GetProtocolVersionCommand getProtocolVersionCommand{*device};\nDevice::Commands::GetProtocolInfoCommand getProtocolInfoCommand{*device};\nDevice::Commands::ReleaseHandleCommand releaseHandleCommand{*device};\nDevice::Commands::StartScanCommand startScanCommand{*device};\nDevice::Commands::StopScanCommand stopScanCommand{*device};\nDevice::Commands::FeedWatchdogCommand feedWatchdogCommand{*device};\nDevice::Commands::GetParametersCommand getParametersCommand{*device};\nDevice::Commands::FetchParametersCommand fetchParametersCommand{*device};\nDevice::Commands::SetParametersCommand setParametersCommand{*device};\nDevice::Commands::FactoryResetParametersCommand factoryResetParametersCommand{*device};\nDevice::Commands::FactoryResetDeviceCommand factoryResetDeviceCommand{*device};\nDevice::Commands::RebootDeviceCommand rebootDeviceCommand{*device};\nDevice::Commands::RequestUdpHandleCommand requestUdpHandleCommand{*device};\nDevice::Commands::RequestTcpHandleCommand requestTcpHandleCommand{*device};\nDevice::Commands::GetScanOutputConfigCommand getScanOutputConfigCommand{*device};\nDevice::Commands::SetScanOutputConfigCommand setScanOutputConfigCommand{*device};\n```\nOn construction, a command is given an instance of **R2000** to operate on. Hence, each instance of a Command is attached to a sensor.\nAll commands can be executed in three modes:\n- synchronously \n- asynchronously with future/promise \n- asynchronously using callbacks\n\n```cpp\nDevice::Commands::SetParametersCommand setParametersCommand{*device};\n//...\nauto result{setParametersCommand.execute(hmiParameters)}; // Blocking call until the command is executed or fails\n\n// or    \n\nauto future{setParametersCommand.asyncExecute(2s, hmiParameters)}; // Asynchronous execution with 2 seconds timeout\nif(!future)\n{\n    // The command could not been submitted for execution\n}\n// Do something else ...\nauto result{future-\u003eget()};\n\n// or\n\nconst auto started{setParametersCommand.asyncExecute(hmiParameters, [](const auto result){\n    // Do something with the result of the command execution\n}, 2s)}; // Asynchronous execution with 2 seconds timeout\n\nif(started)\n{\n    // The command has been submitted for execution\n}\n```\n\nThe commands take different number of arguments depending on their action.\n```cpp\n// Rebooting the device takes no arguments\nconst auto requestResult{rebootDeviceCommand.execute()};\n\n// Asynchronously rebooting the device only takes the command timeout.\nauto future{rebootDeviceCommand.asyncExecute(2s)};\nif(!future)\n{\n    // ...\n}\n\n```\nSome even takes a variadic number of arguments:\n```cpp\nconst auto hmiParameters{Device::Parameters::ReadWriteParameters::HmiDisplay()\n                                 .unlockHmiButton()\n                                 .withHmiLanguage(Device::Parameters::Language::ENGLISH)\n                                 .withHmiDisplayMode(Device::Parameters::HMI_DISPLAY_MODE::APPLICATION_TEXT)\n                                 .withHmiApplicationText1(\"Line 1\")\n                                 .withHmiApplicationText2(\"Line 2\")};\nconst auto measurementParameters{Device::Parameters::ReadWriteParameters::Measure()\n                                     .withOperatingMode(Device::Parameters::OPERATING_MODE::MEASURE)\n                                     .withScanFrequency(35.0)\n                                     .withSamplesPerScan(7200)\n                                     .withScanDirection(Device::Parameters::SCAN_DIRECTION::CCW)};\nconst auto ethernetParameters{Device::Parameters::ReadWriteParameters::Ethernet ()\n                                     .withGateway(\"192.168.1.1\")\n                                     .withIpAddress(\"192.168.1.72\")\n                                     .withIpMode(Device::Parameters::IpMode::STATIC)\n                                     .withSubnetMask(\"255.255.255.0\")};\n\nDevice::Commands::SetParametersCommand setParametersCommand{*device};\n// Set parameters command takes a variadic number of parameters builder, allowing to configure \n// multiple section of the sensor.\nauto future{setParametersCommand.asyncExecute(2s, hmiParameters, \n                                              measurementParameters, \n                                              ethernetParameters, ...)};\nif(!future)\n{\n    // The command could not been submitted for execution\n}\n```\n\n### DataLinks\n\nThe DataLinks are the objects that handle the streaming of scan from the sensor. There is two type of link\nimplemented, TCP and UDP links. DataLinks are RAII by design. On construction, they immediately open the channel with\nthe sensor and start the streaming of scan. On destruction, they close the channel and release any resources allocated\nfor the stream of scan.\n\nDataLinks must not be instantiated directly. They are built using a special factory class named **DataLinkBuilder**.\nSetting up a **DataLink** necessitate to send commands to the sensors, get the results and interpret it, extract some\nparameters that are used by the Links. All of this is automatically handled by the **DataLinkBuilder**. Depending on the\ntype of Parameters handle used with the builder, it will set up a TCP or UDP link while abstracting all the configuration\nthat goes underneath:\n```cpp\n// Building a TCP Link:\nauto tcpHandleParameters{Device::Parameters::ReadWriteParameters::TcpHandle{}\n                                .withWatchdog()\n                                .withWatchdogTimeout(5000)\n                                // ... other parameters\n                                };\nauto [requestResult, dataLink]{Device::DataLinkBuilder(handleParameters).build(device)}; // Build a TCP Link\n\n// Building and UDP Link (asynchronously):\nauto udpHandleParameters{Device::Parameters::ReadWriteParameters::UdpHandle{}\n                              .withHostname(\"192.168.1.5\") // In UDP, you must specify the destination of the packet scan\n                              .withPort(62000) // And the destination port\n                              .withPacketType(Device::Parameters::PACKET_TYPE::B)\n                              .withWatchdog()\n                              .withWatchdogTimeout(5000)};\nauto future{Device::DataLinkBuilder(udpHandleParameters).build(device, 1s)};\n// Do something else\nauto [requestResult, dataLink]{future.get()};\n```\n\nThere is four different way to get the received scans from a DataLink depending on the needs:\n```cpp\nusing namespace std::chrono_literals;\n// ...\nconst auto scan {dataLink-\u003egetLastScan()}; // Get the last scan received (wait-free, lock-free)\n// or\nconst auto scan {dataLink-\u003ewaitForNextScan()}; // Block until the next scan is received\n// or\nconst auto scan {dataLink-\u003ewaitForNextScan(750ms)}; // Block until the next scan is received or timeout is reached\n// or\ndataLink-\u003eaddOnNewScanAvailableCallback([](const auto\u0026 newScan){ \n    \n}); // Register a callback to listen for the arrival of every new scan.\n```\n\n### StatusWatcher\n\nThe StatusWatcher is a utility object that monitor the network and health status of the sensor periodically and can raise\nevents. It can be used to listen for device connection/disconnection or to generate reports about the current state of the\ndevice.\n```cpp\nconst auto device{Device::R2000::makeShared({\"DeviceName\", \"192.168.1.60\"})};\n// ...\nDevice::StatusWatcher statusWatcher{device, 2s}; // Watcher with a period of 2s\n\nstatusWatcher.addOnDeviceConnectedCallback([](){ \n    // Device has connected\n}); // Register a callback to listen for device connection\n\nstatusWatcher.addOnDeviceDisconnectedCallback([](){ \n    // Device has disconnected\n}); // Register a callback to listen for device disconnection\n```\nThe watcher generate periodically an object named **DeviceStatus**. This object contains all the status information about\nthe sensor at a given time T. There are two different method of getting the status:\n```cpp\nconst auto deviceStatus{statusWatcher-\u003egetLastReceivedStatus()}; // Get the last received status (lock-free, wait-free)\n// or \nstatusWatcher-\u003eaddOnStatusAvailableCallback([](auto status)\n{\n    // A new status has been received.\n}); // Register a callback to listen for the arrival of every new status.\n```\nThe DeviceStatus object exposes a list of getter on the internal state of the sensor:\n\n```cpp\nconst auto deviceStatus{statusWatcher-\u003egetLastReceivedStatus()};\nconst auto rawSystemTime{deviceStatus-\u003egetRawSystemTime()};\nconst auto cpuLoad{deviceStatus-\u003egetCpuLoad()};\nconst auto currentTemperature{deviceStatus-\u003egetCurrentTemperature()};\nconst auto statusFlags{deviceStatus-\u003egetStatusFlags()};\n// ...\nconst auto error{statusFlags.deviceHasError()};\nconst auto deviceIsInitializing{statusFlags.isInitializing()};\n// ...\n```\n\n## Building the examples\n\nThere is a set of executables in the [examples](examples) to test the interface. You can build them using the following commands:\n```sh\nmkdir build \u0026\u0026 cd build\ncmake -DCMAKE_BUILD_TYPE=Release ..\nmake\n```\nIt will generate 3 executables: \n- DeviceEventWatch: shows how to use the StatusWatcher to listen for connection/disconnection.\n- DeviceStatusWatch: shows how to use the StatusWatcher to monitor the device.\n- AcquireScan: show how to quickly set up a DataLink to acquire scans.\n\n```sh\n./DeviceEventWatch 192.168.2.30\n# or\n./DeviceStatusWatch 192.168.2.30\n# or\n./AcquireScan 192.168.2.30\n```\n\n## Troubleshooting\n\n- Using the UDP mode, mind that the address given to the builder with the UdpHandle is not the device address, but the address where the sensor must send the datagrams.\nIt is usually the address of the machine running this communication interface.\n- Be careful of the firewall. It can restrict both the configuration packet on TCP and the scans on TCP and UDP.\nIn some cases, the firewall allows the configuration packet to flow through, but the DataLinks doesn't receive the\nscans in UDP. Make sure that the firewall accepts both the configuration packets on TCP and the scans on UDP.\n- Using the DataLinks without watchdog enabled and killing the application without destroying the DataLinks \ncan cause \"zombie\" links within the sensors. Current software revisions of the R2000 only allows at most 3 streams of scans.\nTrying to set up more DataLink when this maximum number is reached (due to zombie links or because the device is already\nstreaming scans to 3 clients) will result in failure.\n**In any case, it is strongly advised to set up the DataLinks with watchdog enabled, so that the sensor can automatically kill \"zombie\" links.**\n\n## Feedback\n\nDon't hesitate if you have any suggestions for improving this project, or if you find any error. I will be glad to\nhear from you. There are traits that have been implemented yet. You can submit implementations suggestions.\nContributions are welcomed :)\n\n## License\n\nDistributed under the MIT Software License (X11 license).\nSee accompanying file LICENSE.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnandite%2Fr2000","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnandite%2Fr2000","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnandite%2Fr2000/lists"}