├── .gitignore ├── library.properties ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── Feedback-request.md │ └── BUG_REPORT_EC.md └── workflows │ ├── arduino-lint.yml │ └── BuildLibrary.yml ├── CMakeLists.txt ├── idf_component.yml ├── test ├── catch2 │ ├── CMakeLists.txt │ ├── LppMessageTest.cpp │ └── LppPolylineTest.cpp └── aunit │ ├── platformio.ini │ └── main.cpp ├── library.json ├── LICENSE.md ├── keywords.txt ├── src ├── CayenneLPPMessage.h ├── CayenneLPPPolyline.h ├── CayenneLPP.h ├── CayenneLPPPolyline.cpp └── CayenneLPP.cpp ├── examples ├── Decode │ └── Decode.ino ├── DecodeTTN │ └── DecodeTTN.ino └── basic_lorawan_beelan │ └── basic_lorawan_beelan.ino ├── decoders ├── decoder.min.js └── decoder.js ├── README.md └── API.md /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CayenneLPP 2 | version=1.6.1 3 | author=Electronic Cats 4 | maintainer=Electronic Cats 5 | sentence=CayenneLPP Arduino Library. 6 | paragraph=Compatible with Cayenne Low Power Payload. 7 | category=Communication 8 | url=https://github.com/ElectronicCats/CayenneLPP 9 | architectures=* 10 | includes=CayenneLPP.h 11 | depends=ArduinoJson 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: See if your issue is solved before creating a new one. 4 | about: Try to see if ther is an answer for yoru questions before you create a new issue. 5 | url: https://github.com/ElectronicCats/CayenneLPP/issues?q=is%3Aissue+is%3Aclosed 6 | - name: ElectronicCats 7 | about: Contact us through our website 8 | url: https://electroniccats.com/contact/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(ESP_PLATFORM) 4 | # Build CayenneLPP as an ESP-IDF component 5 | # required because ESP-IDF runs cmake in script mode 6 | # and needs idf_component_register() 7 | file(GLOB_RECURSE CAYENNELPP_ESP_SOURCES 8 | "src/*.*" 9 | ) 10 | 11 | idf_component_register( 12 | SRCS ${CAYENNELPP_ESP_SOURCES} 13 | INCLUDE_DIRS src 14 | REQUIRES bblanchon__arduinojson 15 | ) 16 | 17 | return() 18 | endif() 19 | -------------------------------------------------------------------------------- /.github/workflows/arduino-lint.yml: -------------------------------------------------------------------------------- 1 | name: arduino-lint 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: arduino/arduino-lint-action@v1 9 | with: 10 | # path: ./ 11 | version: 1.x 12 | compliance: strict 13 | library-manager: update 14 | project-type: library 15 | recursive: true 16 | # report-file: 17 | verbose: false 18 | token: GITHUB_TOKEN -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | version: "1.6.1" 2 | description: "CayenneLPP library for Arduino and ESP-IDF. Compatible with Cayenne Low Power Payload." 3 | tags: "communication, LoRa, LoRaWAN" 4 | url: "https://github.com/ElectronicCats/CayenneLPP" 5 | issues: https://github.com/ElectronicCats/CayenneLPP/issues 6 | documentation: https://github.com/ElectronicCats/CayenneLPP/blob/master/API.md 7 | repository: "https://github.com/ElectronicCats/CayenneLPP.git" 8 | license: "MIT" 9 | dependencies: 10 | bblanchon/arduinojson: 11 | version: "*" 12 | maintainers: 13 | "Electronic Cats " -------------------------------------------------------------------------------- /test/catch2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | Include(FetchContent) 4 | 5 | FetchContent_Declare( 6 | Catch2 7 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 8 | GIT_TAG v3.0.0-preview3 9 | ) 10 | 11 | FetchContent_MakeAvailable(Catch2) 12 | 13 | add_executable(clpp_test 14 | LppMessageTest.cpp 15 | LppPolylineTest.cpp 16 | ../../src/CayenneLPP.cpp 17 | ../../src/CayenneLPPPolyline.cpp 18 | ) 19 | 20 | target_include_directories(clpp_test 21 | PRIVATE 22 | ../../src 23 | ) 24 | 25 | target_link_libraries(clpp_test 26 | PRIVATE 27 | Catch2::Catch2WithMain 28 | ) 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feedback-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F408 Feddback or requests" 3 | about: Suggest an idea or improvement for this project 4 | title: 'ElectronicCats' 5 | labels: 'enhancement' 6 | --- 7 | 8 | **What idea or improvement for the library has occurred to you?** 9 | 10 | **If you found an error:** 11 | 12 | Please describe clearly and concisely the problem you are aware of. 13 | 14 | 15 | **Have you already found a solution?** 16 | 17 | If you found a way to fix the problem please let us know. 18 | It would be very helpful if you put the part of the code that needs to be corrected and how it needs to be corrected. 19 | 20 | ***Thanks a lot*** -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CayenneLPP", 3 | "version": "1.6.1", 4 | "description": "CayenneLPP library for Arduino and ESP-IDF. Compatible with Cayenne Low Power Payload.", 5 | "keywords": "communication, LoRa, LoRaWAN", 6 | "homepage": "https://github.com/ElectronicCats/CayenneLPP", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/ElectronicCats/CayenneLPP.git" 10 | }, 11 | "authors": { 12 | "name": "Electronic Cats", 13 | "email": "support@electroniccats.com", 14 | "maintainer": true 15 | }, 16 | "license": "MIT", 17 | "frameworks":[ 18 | "arduino", 19 | "espidf" 20 | ], 21 | "platforms": "*", 22 | "headers": "CayenneLPP.h", 23 | "dependencies": 24 | { 25 | "bblanchon/ArduinoJson": "*" 26 | }, 27 | "build": 28 | { 29 | "libLDFMode": "chain+" 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /test/aunit/platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | src_dir = . 13 | default_envs = leonardo 14 | 15 | [env] 16 | framework = arduino 17 | lib_deps = 18 | https://github.com/bxparks/AUnit 19 | ArduinoJson 20 | monitor_speed = 115200 21 | build_flags = -DPC_SERIAL=Serial 22 | 23 | [env:leonardo] 24 | platform = atmelavr 25 | board = leonardo 26 | lib_extra_dirs = 27 | .pio/libdeps/leonardo 28 | ../.. 29 | 30 | [env:esp8266] 31 | platform = espressif8266 32 | board = esp12e 33 | lib_extra_dirs = 34 | .pio/libdeps/esp8266 35 | ../.. 36 | upload_speed = 460800 37 | 38 | [env:m0pro] 39 | platform = atmelsam 40 | board = mzeroproUSB 41 | build_flags = -DPC_SERIAL=SerialUSB 42 | lib_extra_dirs = 43 | .pio/libdeps/m0pro 44 | ../.. 45 | 46 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For CayenneLPP 3 | ####################################### 4 | 5 | ####################################### 6 | # Class (KEYWORD1) 7 | ####################################### 8 | 9 | CayenneLPP KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | reset KEYWORD2 16 | getSize KEYWORD2 17 | *getBuffer KEYWORD2 18 | copy KEYWORD2 19 | 20 | addDigitalInput KEYWORD2 21 | addDigitalOutput KEYWORD2 22 | addAnalogInput KEYWORD2 23 | addAnalogOutput KEYWORD2 24 | addLuminosity KEYWORD2 25 | addPresence KEYWORD2 26 | addTemperature KEYWORD2 27 | addRelativeHumidity KEYWORD2 28 | addAccelerometer KEYWORD2 29 | addBarometricPressure KEYWORD2 30 | addGyrometer KEYWORD2 31 | addGPS KEYWORD2 32 | 33 | addUnixTime KEYWORD2 34 | 35 | addGenericSensor KEYWORD2 36 | addVoltage KEYWORD2 37 | addCurrent KEYWORD2 38 | addFrequency KEYWORD2 39 | addPercentage KEYWORD2 40 | addAltitude KEYWORD2 41 | addPower KEYWORD2 42 | addDistance KEYWORD2 43 | addEnergy KEYWORD2 44 | addDirection KEYWORD2 45 | addSwitch KEYWORD2 46 | 47 | addConcentration KEYWORD2 48 | addColour KEYWORD2 49 | addPolyline KEYWORD2 50 | 51 | getTypeName KEYWORD2 52 | decode KEYWORD2 53 | decodeTTN KEYWORD2 54 | 55 | ####################################### 56 | # Constants (LITERAL1) 57 | ####################################### 58 | -------------------------------------------------------------------------------- /src/CayenneLPPMessage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CayenneLPP - CayenneLPP Message Struct 3 | * 4 | * Copyright (C) 2021 by Manuel Weichselbaumer 5 | * 6 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 7 | * 8 | */ 9 | 10 | #ifndef CAYENNE_LPP_MESSAGE_H 11 | #define CAYENNE_LPP_MESSAGE_H 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | struct CayenneLPPMessage { 18 | 19 | // Original LPPv1 data types 20 | uint32_t digitalInput = 0; 21 | uint32_t digitalOutput = 0; 22 | float analogInput = 0.0f; 23 | float analogOutput = 0.0f; 24 | uint32_t luminosity = 0; 25 | uint32_t presence = 0; 26 | float temperature = 0.0f; 27 | float relativeHumidity = 0.0f; 28 | std::array accelerometer; 29 | float barometricPressure = 0.0f; 30 | std::array gyrometer; 31 | std::array gps; 32 | 33 | // Additional data types 34 | uint32_t unixTime = 0; 35 | float genericSensor = 0.0f; 36 | float voltage = 0.0f; 37 | float current = 0.0f; 38 | uint32_t frequency = 0; 39 | uint32_t percentage = 0; 40 | float altitude = 0.0f; 41 | float power = 0.0f; 42 | float distance = 0.0f; 43 | float energy = 0.0f; 44 | float direction = 0.0f; 45 | uint32_t onOffSwitch = 0; 46 | uint32_t concentration = 0; 47 | std::array colour; 48 | 49 | // Non-IPSO data types 50 | std::vector> polyline; 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /examples/Decode/Decode.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Decode.ino 3 | 4 | This code create a CayenneLPP package and decode to Json. 5 | 6 | created 22 October 2019 7 | by Luiz H. Cassettari 8 | */ 9 | 10 | #include 11 | 12 | void setup() 13 | { 14 | DynamicJsonDocument jsonBuffer(4096); 15 | CayenneLPP lpp(160); 16 | 17 | JsonArray root = jsonBuffer.to(); 18 | 19 | Serial.begin(115200); 20 | Serial.println(); 21 | 22 | lpp.reset(); 23 | lpp.addDigitalInput(1, 0); 24 | lpp.addDigitalOutput(2, 1); 25 | lpp.addAnalogInput(3, 1.23f); 26 | lpp.addAnalogOutput(4, 3.45f); 27 | lpp.addLuminosity(5, 20304); 28 | lpp.addPresence(6, 1); 29 | lpp.addTemperature(7, 26.5f); 30 | lpp.addRelativeHumidity(8, 86.6f); 31 | lpp.addAccelerometer(9, 1.234f, -1.234f, 0.567f); 32 | lpp.addBarometricPressure(10, 1023.4f); 33 | lpp.addGyrometer(1, -12.34f, 45.56f, 89.01f); 34 | lpp.addGPS(1, -12.34f, 45.56f, 9.01f); 35 | 36 | lpp.addUnixTime(1, 135005160); 37 | 38 | lpp.addGenericSensor(1, 4294967295); 39 | lpp.addVoltage(1, 3.35); 40 | lpp.addCurrent(1, 0.321); 41 | lpp.addFrequency(1, 50); 42 | lpp.addPercentage(1, 100); 43 | lpp.addAltitude(1 , 50); 44 | lpp.addPower(1 , 50000); 45 | lpp.addDistance(1 , 10.555); 46 | lpp.addEnergy(1 , 19.055); 47 | lpp.addDirection(1 , 90); 48 | lpp.addSwitch(1 , 0); 49 | 50 | lpp.addConcentration(1 , 512); 51 | lpp.addColour(1 , 64, 128, 255); 52 | 53 | 54 | lpp.decode(lpp.getBuffer(), lpp.getSize(), root); 55 | 56 | serializeJsonPretty(root, Serial); 57 | Serial.println(); 58 | 59 | } 60 | 61 | void loop() 62 | { 63 | } -------------------------------------------------------------------------------- /examples/DecodeTTN/DecodeTTN.ino: -------------------------------------------------------------------------------- 1 | /* 2 | DecodeTTN.ino 3 | 4 | This code create a CayenneLPP package and decode to Json. 5 | 6 | created 22 October 2019 7 | by Luiz H. Cassettari 8 | */ 9 | 10 | #include 11 | 12 | void setup() 13 | { 14 | DynamicJsonDocument jsonBuffer(1024); 15 | CayenneLPP lpp(160); 16 | 17 | JsonObject root = jsonBuffer.to(); 18 | 19 | Serial.begin(115200); 20 | Serial.println(); 21 | 22 | lpp.reset(); 23 | lpp.addDigitalInput(1, 0); 24 | lpp.addDigitalOutput(2, 1); 25 | lpp.addAnalogInput(3, 1.23f); 26 | lpp.addAnalogOutput(4, 3.45f); 27 | lpp.addLuminosity(5, 20304); 28 | lpp.addPresence(6, 1); 29 | lpp.addTemperature(7, 26.5f); 30 | lpp.addRelativeHumidity(8, 86.6f); 31 | lpp.addAccelerometer(9, 1.234f, -1.234f, 0.567f); 32 | lpp.addBarometricPressure(10, 1023.4f); 33 | lpp.addGyrometer(1, -12.34f, 45.56f, 89.01f); 34 | lpp.addGPS(1, -12.34f, 45.56f, 9.01f); 35 | 36 | lpp.addUnixTime(1, 135005160); 37 | 38 | lpp.addGenericSensor(1, 4294967295); 39 | lpp.addVoltage(1, 3.35); 40 | lpp.addCurrent(1, 0.321); 41 | lpp.addFrequency(1, 50); 42 | lpp.addPercentage(1, 100); 43 | lpp.addAltitude(1 , 50); 44 | lpp.addPower(1 , 50000); 45 | lpp.addDistance(1 , 10.555); 46 | lpp.addEnergy(1 , 19.055); 47 | lpp.addDirection(1 , 90); 48 | lpp.addSwitch(1 , 0); 49 | 50 | lpp.addConcentration(1 , 512); 51 | lpp.addColour(1 , 64, 128, 255); 52 | 53 | 54 | lpp.decodeTTN(lpp.getBuffer(), lpp.getSize(), root); 55 | 56 | serializeJsonPretty(root, Serial); 57 | Serial.println(); 58 | 59 | } 60 | 61 | void loop() 62 | { 63 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT_EC.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F640 Bug report " 3 | about: Report a bug or unexpected behavior while using the CayenneLPP library 4 | title: 'ElectronicCats' 5 | labels: bug 6 | --- 7 | 8 | **Please, before reporting any issue** 9 | - Make sure your board it's in good condition. 10 | - Verify that it really is a library problem and not a hardware problem. 11 | - **Avoid** to submit a GitHub issue for project troubleshooting. 12 | 13 | Any feedback/suggestions should be discussed on the [feedback section](https://github.com/ElectronicCats/CayenneLPP/issues): 14 | * Just click on New Issue and select the correct category. 15 | 16 | When reporting any issue, please try to provide all relevant information to help on its resolution. 17 | 18 | 19 | **Describe the bug** 20 | A clear and concise description of what the bug is. 21 | The more detailed this is, the easier we can come up with a solution. 22 | 23 | 24 | **To Reproduce** 25 | Complete source code which can be used to reproduce the issue. Please try to be as generic as possible. 26 | No extra code, extra hardware, etc. 27 | If you think it is absolutely necessary to add this, mention in detail the connections in case it is necessary hardware and also indicate the part of code that was added and how it was added. 28 | 29 | 30 | **Expected behavior** 31 | A clear and concise description of what you expected to happen. 32 | 33 | **Screenshots** 34 | If applicable, add screenshots to help explain your problem. 35 | 36 | **To help us to understand your situation, we would like to ask you for the following additional information::** 37 | - Which board are you using? 38 | - What Operating System and version are you using (e.g. Windows 11, macOS 12.0, Linux)? 39 | - In case you are using the Arduino IDE, What version of the Arduino IDE? 40 | - Did it work before?, If it worked before, what were the parameters that you modify? 41 | 42 | 43 | **Additional context** 44 | Add any other context about the problem here that you think will help us to solve your problem. -------------------------------------------------------------------------------- /decoders/decoder.min.js: -------------------------------------------------------------------------------- 1 | function lppDecode(e){var i={0:{size:1,name:"digital_in",signed:!1,divisor:1},1:{size:1,name:"digital_out",signed:!1,divisor:1},2:{size:2,name:"analog_in",signed:!0,divisor:100},3:{size:2,name:"analog_out",signed:!0,divisor:100},100:{size:4,name:"generic",signed:!1,divisor:1},101:{size:2,name:"illuminance",signed:!1,divisor:1},102:{size:1,name:"presence",signed:!1,divisor:1},103:{size:2,name:"temperature",signed:!0,divisor:10},104:{size:1,name:"humidity",signed:!1,divisor:2},113:{size:6,name:"accelerometer",signed:!0,divisor:1e3},115:{size:2,name:"barometer",signed:!1,divisor:10},116:{size:2,name:"voltage",signed:!1,divisor:100},117:{size:2,name:"current",signed:!1,divisor:1e3},118:{size:4,name:"frequency",signed:!1,divisor:1},120:{size:1,name:"percentage",signed:!1,divisor:1},121:{size:2,name:"altitude",signed:!0,divisor:1},125:{size:2,name:"concentration",signed:!1,divisor:1},128:{size:2,name:"power",signed:!1,divisor:1},130:{size:4,name:"distance",signed:!1,divisor:1e3},131:{size:4,name:"energy",signed:!1,divisor:1e3},132:{size:2,name:"direction",signed:!1,divisor:1},133:{size:4,name:"time",signed:!1,divisor:1},134:{size:6,name:"gyrometer",signed:!0,divisor:100},135:{size:3,name:"colour",signed:!1,divisor:1},136:{size:9,name:"gps",signed:!0,divisor:[1e4,1e4,100]},142:{size:1,name:"switch",signed:!1,divisor:1}};function s(e,i,s){for(var n=0,d=0;d255)throw"Byte value overflow!";n=n<<8|e[d]}if(i){var r=1<<8*e.length;n=n>r-1>>1?n-r:n}return n/=s}for(var n=[],d=0;d 14 | #include 15 | 16 | CayenneLPP lpp(51); 17 | 18 | //ABP Credentials 19 | const char *devAddr = "00000000"; 20 | const char *nwkSKey = "00000000000000000000000000000000"; 21 | const char *appSKey = "00000000000000000000000000000000"; 22 | 23 | const unsigned long interval = 10000; // 10 s interval to send message 24 | unsigned long previousMillis = 0; // will store last time message sent 25 | unsigned int counter = 0; // message counter 26 | 27 | char myStr[50]; 28 | char outStr[255]; 29 | byte recvStatus = 0; 30 | 31 | const sRFM_pins RFM_pins = { 32 | .CS = 20, 33 | .RST = 9, 34 | .DIO0 = 0, 35 | .DIO1 = 1, 36 | .DIO2 = 2, 37 | .DIO5 = 15, 38 | }; 39 | 40 | void setup() { 41 | // Setup loraid access 42 | Serial.begin(115200); 43 | delay(2000); 44 | if(!lora.init()){ 45 | Serial.println("RFM95 not detected"); 46 | delay(5000); 47 | return; 48 | } 49 | 50 | // Set LoRaWAN Class change CLASS_A or CLASS_C 51 | lora.setDeviceClass(CLASS_A); 52 | 53 | // Set Data Rate 54 | lora.setDataRate(SF8BW125); 55 | 56 | // set channel to random 57 | lora.setChannel(MULTI); 58 | 59 | // Put ABP Key and DevAddress here 60 | lora.setNwkSKey(nwkSKey); 61 | lora.setAppSKey(appSKey); 62 | lora.setDevAddr(devAddr); 63 | } 64 | 65 | void loop() { 66 | // Check interval overflow 67 | if(millis() - previousMillis > interval) { 68 | previousMillis = millis(); 69 | 70 | printVariables(); 71 | 72 | Serial.print("Sending: "); 73 | lora.sendUplink((char *)lpp.getBuffer(), lpp.getSize(), 0, 1); 74 | } 75 | 76 | recvStatus = lora.readData(outStr); 77 | if(recvStatus) { 78 | Serial.println(outStr); 79 | } 80 | 81 | // Check Lora RX 82 | lora.update(); 83 | } 84 | 85 | void printVariables() 86 | { 87 | lpp.reset(); 88 | 89 | int humidity = random(0,300); 90 | Serial.print(F(",humidity=")); 91 | Serial.print(humidity, 1); 92 | lpp.addRelativeHumidity(3, humidity); 93 | 94 | int temp = random(0,200); 95 | Serial.print(F(",tempf=")); 96 | Serial.print(temp, 1); 97 | lpp.addTemperature(4, temp); 98 | 99 | int pressure = random(0,2000); 100 | Serial.print(F(",pressure=")); 101 | Serial.print((pressure/100.0), 2); 102 | lpp.addBarometricPressure(7,(pressure/100.0)); 103 | 104 | int batt_lvl = random(0,3.3); 105 | Serial.print(F(",batt_lvl=")); 106 | Serial.print(batt_lvl, 2); 107 | lpp.addAnalogInput(8, batt_lvl); 108 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CayenneLPP _by Electronic Cats_ - Library for Arduino and ESP-IDF 2 | 3 | ![LibraryBuild](https://github.com/ElectronicCats/CayenneLPP/workflows/LibraryBuild/badge.svg?branch=master) 4 | 5 | This is an Library for Arduino and ESP-IDF Compatible with Cayenne Low Power Payload with Extended Data Types. 6 | 7 | CayenneLPP is a format designed by [myDevices](https://mydevices.com) to integrate LoRaWan nodes into their [IoT Platform](https://mydevices.com/capabilities). It is used to send sensor data in a packed way to [The Things Network platform](https://www.thethingsnetwork.org). You can read more on [myDevices CayenneLPP](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) 8 | 9 | ## Description 10 | 11 | CayenneLPP format is a quite well-optimized way to send sensor data over low bit rate connection, like LoRa. You may find, probably, a better way for your specific project but CayenneLPP is a standardized and proven format that packs data in a sufficient way. It implements basic sensor types specified by [OMA SpecWorks](https://www.omaspecworks.org), formerly IPSO Alliance. 12 | 13 |

14 | 15 | 16 | 17 |

18 | 19 | It supports multichannel data, which means that you can use it on multisensor devices. 20 | 21 | > [!Warning] 22 | > This version of the library includes several IPSO data types not included in the original work by [Johan Stokking](https://github.com/TheThingsNetwork/arduino-device-lib) or most of the forks and side works by other people, **these additional data types are not supported by My Devices Cayenne**. In addition, it includes a fully backward-compatible decoder in JavaScript, suitable for implementations with NodeRED or TTN, for instance. 23 | 24 | When using the decoder, you must install the [ArduinoJson 6.X](https://arduinojson.org/) library. You can find it in both the Arduino IDE and Platform IO library managers. 25 | 26 | ## How to contribute 27 | Contributions are welcome! 28 | 29 | Please read the document [**Contribution Manual**](https://github.com/ElectronicCats/electroniccats-cla/blob/main/electroniccats-contribution-manual.md) which will show you how to contribute your changes to the project. 30 | 31 | ✨ Thanks to all our [contributors](https://github.com/ElectronicCats/CayenneLPP/graphs/contributors)! ✨ 32 | 33 | See [**_Electronic Cats CLA_**](https://github.com/ElectronicCats/electroniccats-cla/blob/main/electroniccats-cla.md) for more information. 34 | 35 | See the [**community code of conduct**](https://github.com/ElectronicCats/electroniccats-cla/blob/main/electroniccats-community-code-of-conduct.md) for a vision of the community we want to build and what we expect from it. 36 | 37 | ## References 38 | 39 | * [Cayenne Low Power Payload](https://mydevices.com/cayenne/docs/#lora-cayenne-low-power-payload) 40 | * [IPSO data types](http://openmobilealliance.org/wp/OMNA/LwM2M/LwM2MRegistry.html#extlabel) 41 | 42 | ### Maintainer 43 | 44 | Electronic Cats invests time and resources in providing this open-source design, please support Electronic Cats and open-source hardware by purchasing products from Electronic Cats! 45 | 46 | 47 | 48 | 49 | 50 | ## License 51 | 52 | Based on the work of [Johan Stokking](https://github.com/TheThingsNetwork/arduino-device-lib). 53 | 54 | The MIT License (MIT) 55 | -------------------------------------------------------------------------------- /.github/workflows/BuildLibrary.yml: -------------------------------------------------------------------------------- 1 | # LibraryBuild.yml 2 | # Github workflow script to test compile all examples of an Arduino library repository. 3 | # 4 | # Copyright (C) 2020 Armin Joachimsmeyer 5 | # https://github.com/ArminJo/Github-Actions 6 | # 7 | 8 | # This is the name of the workflow, visible on GitHub UI. 9 | name: LibraryBuild 10 | on: [push, pull_request] # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request 11 | 12 | jobs: 13 | build: 14 | name: ${{ matrix.arduino-boards-fqbn }} - test compiling examples 15 | 16 | runs-on: ubuntu-latest # I picked Ubuntu to use shell scripts. 17 | 18 | env: 19 | REQUIRED_LIBRARIES: ArduinoJson,Beelan LoRaWAN 20 | 21 | strategy: 22 | matrix: 23 | # The matrix will produce one job for each configuration parameter of type `arduino-boards-fqbn` 24 | # In the Arduino IDE, the fqbn is printed in the first line of the verbose output for compilation as parameter -fqbn=... for the "arduino-builder -dump-prefs" command 25 | # 26 | # Examples: arduino:avr:uno, arduino:avr:leonardo, arduino:avr:nano, arduino:avr:mega 27 | # arduino:sam:arduino_due_x, arduino:samd:arduino_zero_native" 28 | # ATTinyCore:avr:attinyx5:chip=85,clock=1internal, digistump:avr:digispark-tiny, digistump:avr:digispark-pro 29 | # STM32:stm32:GenF1:pnum=BLUEPILL_F103C8 30 | # esp8266:esp8266:huzzah:eesz=4M3M,xtal=80, esp32:esp32:featheresp32:FlashFreq=80 31 | # You may add a suffix behind the fqbn with "|" to specify one board for e.g. different compile options like arduino:avr:uno|trace 32 | ############################################################################################################# 33 | arduino-boards-fqbn: 34 | - arduino:avr:uno|All-US_915 35 | - arduino:avr:leonardo|All-US_915 36 | - arduino:samd:nano_33_iot|All-US_915 37 | - arduino:mbed:nano33ble|All-US_915 38 | - esp8266:esp8266:huzzah:eesz=4M3M,xtal=80|All-US_915 39 | - esp32:esp32:featheresp32:FlashFreq=80 40 | 41 | # Specify parameters for each board. 42 | # Parameters can be: platform-url, examples-exclude and examples-build-properties 43 | # With examples-exclude you may exclude specific examples for a board. Use a space separated list. 44 | ############################################################################################################# 45 | include: 46 | - arduino-boards-fqbn: arduino:avr:uno|All-US_915 47 | build-properties: 48 | All: 49 | -DUS_915 50 | -DDEBUG 51 | - arduino-boards-fqbn: arduino:avr:leonardo|All-US_915 52 | build-properties: 53 | All: 54 | -DUS_915 55 | -DDEBUG 56 | - arduino-boards-fqbn: arduino:samd:nano_33_iot|All-US_915 57 | build-properties: 58 | All: 59 | -DUS_915 60 | -DDEBUG 61 | - arduino-boards-fqbn: arduino:mbed:nano33ble|All-US_915 62 | build-properties: 63 | All: 64 | -DUS_915 65 | -DDEBUG 66 | - arduino-boards-fqbn: esp8266:esp8266:huzzah:eesz=4M3M,xtal=80|All-US_915 67 | platform-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json 68 | build-properties: 69 | All: 70 | -DUS_915 71 | -DDEBUG 72 | - arduino-boards-fqbn: esp32:esp32:featheresp32:FlashFreq=80 73 | platform-url: https://dl.espressif.com/dl/package_esp32_index.json 74 | sketches-exclude: basic_lorawan_beelan 75 | 76 | # Do not cancel all jobs / architectures if one job fails 77 | fail-fast: false 78 | 79 | # This is the list of steps this job will run. 80 | steps: 81 | 82 | # First of all, we clone the repo using the `checkout` action. 83 | - name: Checkout 84 | uses: actions/checkout@master 85 | 86 | - name: Compile all examples 87 | uses: ArminJo/arduino-test-compile@master 88 | with: 89 | arduino-board-fqbn: ${{ matrix.arduino-boards-fqbn }} 90 | platform-url: ${{ matrix.platform-url }} 91 | required-libraries: ${{ env.REQUIRED_LIBRARIES }} 92 | sketches-exclude: ${{ matrix.sketches-exclude }} 93 | build-properties: ${{ toJson(matrix.build-properties) }} 94 | extra-arduino-cli-args: ${{ matrix.extra-arduino-cli-args }} 95 | -------------------------------------------------------------------------------- /test/catch2/LppMessageTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CayenneLPP - Catch2 Unit Tests 3 | * 4 | * Copyright (C) 2021 by Manuel Weichselbaumer 5 | * 6 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 7 | * 8 | */ 9 | 10 | #include 11 | 12 | #include 13 | 14 | TEST_CASE("CayenneLPPMessage parameters A-C are decoded", "[LppMessage]") { 15 | CayenneLPPMessage in; 16 | in.accelerometer[0] = 1.0; 17 | in.accelerometer[1] = 2.0; 18 | in.accelerometer[2] = 3.0; 19 | in.altitude = 4.0; 20 | in.analogInput = 5.0; 21 | in.analogOutput = 6.0; 22 | in.barometricPressure = 7.0; 23 | in.colour[0] = 8; 24 | in.colour[1] = 9; 25 | in.colour[2] = 10; 26 | in.concentration = 11; 27 | in.current = 12.0; 28 | 29 | CayenneLPP clpp(255); 30 | clpp.addAccelerometer(0, in.accelerometer[0], in.accelerometer[1], in.accelerometer[2]); 31 | clpp.addAltitude(0, in.altitude); 32 | clpp.addAnalogInput(0, in.analogInput); 33 | clpp.addAnalogOutput(0, in.analogOutput); 34 | clpp.addBarometricPressure(1, in.barometricPressure); 35 | clpp.addColour(1, in.colour[0], in.colour[1], in.colour[2]); 36 | clpp.addConcentration(1, in.concentration); 37 | clpp.addCurrent(1, in.current); 38 | 39 | std::map out; 40 | clpp.decode(clpp.getBuffer(), clpp.getSize(), out); 41 | REQUIRE(out.size() == 2); 42 | REQUIRE(in.accelerometer == out[0].accelerometer); 43 | REQUIRE(in.altitude == out[0].altitude); 44 | REQUIRE(in.analogInput == out[0].analogInput); 45 | REQUIRE(in.analogOutput == out[0].analogOutput); 46 | REQUIRE(in.barometricPressure == out[1].barometricPressure); 47 | REQUIRE(in.colour[0] == out[1].colour[0]); 48 | REQUIRE(in.colour[1] == out[1].colour[1]); 49 | REQUIRE(in.colour[2] == out[1].colour[2]); 50 | REQUIRE(in.concentration == out[1].concentration); 51 | REQUIRE(in.current == out[1].current); 52 | } 53 | 54 | TEST_CASE("CayenneLPPMessage parameters D-O are decoded", "[LppMessage]") { 55 | CayenneLPPMessage in; 56 | in.digitalInput = 13; 57 | in.digitalOutput = 14; 58 | in.direction = 15.0; 59 | in.distance = 16.0; 60 | in.energy = 17.0; 61 | in.frequency = 18; 62 | in.genericSensor = 19.0; 63 | in.gps = { 20.0, 21.0, 22.0 }; 64 | in.gyrometer = { 23.0, 24.0, 25.0 }; 65 | in.luminosity = 26; 66 | in.onOffSwitch = 27; 67 | 68 | CayenneLPP clpp(255); 69 | clpp.addDigitalInput(2, in.digitalInput); 70 | clpp.addDigitalOutput(2, in.digitalOutput); 71 | clpp.addDirection(2, in.direction); 72 | clpp.addDistance(2, in.distance); 73 | clpp.addEnergy(3, in.energy); 74 | clpp.addFrequency(4, in.frequency); 75 | clpp.addGenericSensor(5, in.genericSensor); 76 | clpp.addGPS(5, in.gps.at(0), in.gps.at(1), in.gps.at(2)); 77 | clpp.addGyrometer(5, in.gyrometer.at(0), in.gyrometer.at(1), in.gyrometer.at(2)); 78 | clpp.addLuminosity(6, in.luminosity); 79 | clpp.addSwitch(7, in.onOffSwitch); 80 | 81 | std::map out; 82 | clpp.decode(clpp.getBuffer(), clpp.getSize(), out); 83 | REQUIRE(out.size() == 6); 84 | REQUIRE(in.digitalInput == out[2].digitalInput); 85 | REQUIRE(in.digitalOutput == out[2].digitalOutput); 86 | REQUIRE(in.direction == out[2].direction); 87 | REQUIRE(in.distance == out[2].distance); 88 | REQUIRE(in.energy == out[3].energy); 89 | REQUIRE(in.frequency == out[4].frequency); 90 | REQUIRE(in.genericSensor == out[5].genericSensor); 91 | REQUIRE(in.gps == out[5].gps); 92 | REQUIRE(in.gyrometer == out[5].gyrometer); 93 | REQUIRE(in.luminosity == out[6].luminosity); 94 | REQUIRE(in.onOffSwitch == out[7].onOffSwitch); 95 | } 96 | 97 | TEST_CASE("CayenneLPPMessage parameters P-V are decoded", "[LppMessage]") { 98 | CayenneLPPMessage in; 99 | in.percentage = 28; 100 | in.power = 29; 101 | in.presence = 30; 102 | in.relativeHumidity = 31.0; 103 | in.temperature = 32.0; 104 | in.unixTime = 33; 105 | in.voltage = 34.0; 106 | 107 | CayenneLPP clpp(255); 108 | clpp.addPercentage(8, in.percentage); 109 | clpp.addPower(8, in.power); 110 | clpp.addPresence(8, in.presence); 111 | clpp.addRelativeHumidity(9, in.relativeHumidity); 112 | clpp.addTemperature(10, in.temperature); 113 | clpp.addUnixTime(11, in.unixTime); 114 | clpp.addVoltage(12, in.voltage); 115 | 116 | std::map out; 117 | clpp.decode(clpp.getBuffer(), clpp.getSize(), out); 118 | REQUIRE(out.size() == 5); 119 | REQUIRE(in.percentage == out[8].percentage); 120 | REQUIRE(in.power == out[8].power); 121 | REQUIRE(in.presence == out[8].presence); 122 | REQUIRE(in.relativeHumidity == out[9].relativeHumidity); 123 | REQUIRE(in.temperature == out[10].temperature); 124 | REQUIRE(in.unixTime == out[11].unixTime); 125 | REQUIRE(in.voltage == out[12].voltage); 126 | } 127 | -------------------------------------------------------------------------------- /src/CayenneLPPPolyline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CayenneLPP - CayenneLPP Polyline Codec 3 | * 4 | * Copyright (C) 2021 by Manuel Weichselbaumer 5 | * 6 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 7 | * 8 | */ 9 | 10 | #ifndef CAYENNELPPPOLYLINE_H 11 | #define CAYENNELPPPOLYLINE_H 12 | 13 | #include 14 | #include 15 | // ESP-IDF framework 16 | #if !defined(ARDUINO) && defined(IDF_VER) 17 | #include 18 | #endif 19 | 20 | struct DeltaCoord; 21 | 22 | class CayenneLPPPolyline { 23 | public: 24 | enum Precision : uint8_t { 25 | //Reserved = 200, 26 | //Prec0_00001 = 224, 27 | //Prec0_000025 = 225, 28 | //Prec0_00005 = 226, 29 | Prec0_0001 = 227, 30 | Prec0_0002 = 228, 31 | Prec0_0005 = 229, 32 | Prec0_001 = 230, 33 | Prec0_002 = 231, 34 | Prec0_005 = 232, 35 | Prec0_01 = 233, 36 | Prec0_02 = 234, 37 | Prec0_05 = 235, 38 | Prec0_1 = 236, 39 | Prec0_2 = 237, 40 | Prec0_5 = 238, 41 | Prec1_0 = 239, 42 | //Prec2_0 = 240, 43 | //Prec5_0 = 241, 44 | //Reserved = 242 45 | }; 46 | 47 | /** 48 | * @brief The Simplification algorithm to apply 49 | */ 50 | enum Simplification { 51 | None = 0, ///< No simplifaction applied 52 | PerpendicularDistance = 1, ///< A simple and fast algorithm 53 | DouglasPeucker = 2 ///< A sophisticated but complex algorithm 54 | }; 55 | 56 | struct Stats { 57 | uint32_t keptCoords = 0; 58 | uint32_t addedCoords = 0; 59 | uint32_t removedCoords = 0; 60 | }; 61 | 62 | using Point = std::pair; 63 | 64 | CayenneLPPPolyline(uint32_t size); 65 | 66 | /** 67 | * @brief encode Encodes a set of GPS Coordinates into a byte buffer. 68 | * The first pair of lat/lon will be encoded a a resolution of 0.0001 degrees, while subsequent 69 | * pair will be encoded at lower resolutions as defined by a factor. 70 | * @param coords The set of coords to be serialized and compressed. 71 | * @param factor This factor is used as quantization factor for delta compression. 72 | * A value of 1 results in a precision of 0.0001 degrees. 73 | * This value scales linearly, so a value of 10 would result in a precision of 0.001 degrees. 74 | * You can set values as high as 199. 75 | * Special values start from 227 (see CayenneLPPPolyline::Precision). 76 | * Values 200-226 and 240-255 are reserved for future usage. 77 | * @param simplification The simplification to apply to coordinates. 78 | * @return buffer The byte buffer which results from serialization. 79 | */ 80 | std::vector encode(const std::vector& coords, 81 | uint8_t factor, 82 | Simplification simplification = DouglasPeucker); 83 | 84 | /** 85 | * @brief encode Encodes a set of GPS Coordinates into a byte buffer. 86 | * @param coords The set of coords to be serialized and compressed. 87 | * @param precision This defines the precision for delta compression (see CayenneLPPPolyline::Precision). 88 | * @param simplification The simplification to apply to coordinates. 89 | * @return buffer The byte buffer which results from serialization. 90 | */ 91 | std::vector encode(const std::vector& coords, 92 | Precision precision = Prec0_0001, 93 | Simplification simplification = DouglasPeucker); 94 | 95 | /** 96 | * @brief decode Decodes a byte buffer back to a set of GPS Coordinates. 97 | * @param buffer The byte buffer to be deserialized. 98 | * @return coordinates The resulting coordinates from deserialization. 99 | */ 100 | static std::vector> decode(const std::vector& buffer); 101 | 102 | /** 103 | * @brief getEncodeStats Obtain some statistics from the encoding process. 104 | * @return stats The resulting statistics from encoding. 105 | */ 106 | Stats getEncodeStats() const; 107 | 108 | private: 109 | void reset(); 110 | static double getFactor(uint8_t factor); 111 | void push(double lat, double lon, bool optimize); 112 | void pushFirst(double lat, double lon, uint8_t factor); 113 | 114 | void writeHeader(int32_t lat, int32_t lon, uint8_t factor); 115 | void writeDelta(int8_t lat, int8_t lon, bool optimize); 116 | 117 | static std::vector douglasPeucker(const std::vector& pointList, double epsilon); 118 | static double distance(const Point& point, const Point& lineStart, const Point& lineEnd); 119 | 120 | const uint32_t m_maxSize = 0; 121 | std::vector m_buffer; 122 | 123 | double m_prevLat = 0.0; 124 | double m_prevLon = 0.0; 125 | 126 | double m_errLat = 0.0; 127 | double m_errLon = 0.0; 128 | 129 | Stats m_stats; 130 | }; 131 | 132 | #endif // CAYENNELPPPOLYLINE_H 133 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # CayenneLPP API Reference 2 | 3 | The `CayenneLPP` class enables Arduino and ESP-IDF devices to encode data with the Cayenne Lower Power Protocol (LPP). [Read more about Cayenne LPP](https://mydevices.com/cayenne/docs/#lora-cayenne-low-power-payload) 4 | 5 | ### Class: `CayenneLPP` 6 | 7 | Include and instantiate the CayenneLPP class. The constructor takes the size of the allocated buffer. Depending on the LoRa frequency plan and data rate used, the maximum payload varies. It's safe to send up to 51 bytes of payload. 8 | 9 | ```c 10 | #include 11 | 12 | CayenneLPP lpp(uint8_t size); 13 | ``` 14 | 15 | - `uint8_t size`: The maximum payload size to send, e.g. `51`. 16 | 17 | ### Example 18 | 19 | ```c 20 | TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); 21 | CayenneLPP lpp(51); 22 | 23 | lpp.reset(); 24 | lpp.addTemperature(1, 22.5); 25 | lpp.addBarometricPressure(2, 1073.21); 26 | lpp.addGPS(3, 52.37365, 4.88650, 2); 27 | 28 | ttn.sendBytes(lpp.getBuffer(), lpp.getSize()); 29 | ``` 30 | 31 | See the [CayenneLPP](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/CayenneLPP/CayenneLPP.ino) example. 32 | 33 | ### Method: `reset` 34 | 35 | Resets the buffer. 36 | 37 | ```c 38 | void reset(void); 39 | ``` 40 | 41 | ### Method: `getSize` 42 | 43 | Returns the size of the buffer. 44 | 45 | ```c 46 | uint8_t getSize(void); 47 | ``` 48 | 49 | ### Method: `getBuffer` 50 | 51 | Returns a pointer to the buffer. 52 | 53 | ```c 54 | uint8_t *getBuffer(void); 55 | ``` 56 | 57 | ### Method: `copy` 58 | 59 | Copies the internal buffer to a specified buffer and returns the copied size. 60 | 61 | ```c 62 | uint8_t copy(uint8_t *buffer); 63 | ``` 64 | 65 | ### Method: `decode` 66 | 67 | Decodes a byte array into a JsonArray (requires ArduinoJson library). The result is an array of objects, each one containing channel, type, type name and value. The value can be a scalar or an object (for accelerometer, gyroscope and GPS data). The method call returns the number of decoded fields or 0 if error. 68 | 69 | ```c 70 | uint8_t decode(uint8_t *buffer, uint8_t size, JsonArray& root); 71 | ``` 72 | 73 | Example output: 74 | 75 | ``` 76 | [ 77 | { 78 | "channel": 1, 79 | "type": 136, 80 | "name": "gps", 81 | "value": { 82 | "latitude": 42.3518, 83 | "longitude": -87.9094, 84 | "altitude": 10 85 | } 86 | } 87 | ] 88 | ``` 89 | 90 | ### Method: `decodeTTN` 91 | 92 | Decodes a byte array into a JsonObject (requires ArduinoJson library). The result is a json objects, each object name contain name type plus channel. The value can be a scalar or an object (for accelerometer, gyroscope and GPS data). The method call returns the number of decoded fields or 0 if error. 93 | 94 | ```c 95 | uint8_t decodeTTN(uint8_t *buffer, uint8_t size, JsonObject& root); 96 | ``` 97 | 98 | Example output: 99 | 100 | ``` 101 | { 102 | "gps_1": { 103 | "latitude": 42.3518, 104 | "longitude": -87.9094, 105 | "altitude": 10 106 | } 107 | } 108 | ``` 109 | 110 | 111 | ### Method: `getTypeName` 112 | 113 | Returns a pointer to a C-string containing the name of the requested type. 114 | 115 | ```c 116 | const char * getTypeName(uint8_t type); 117 | ``` 118 | 119 | ### Methods: `add...` 120 | 121 | Add data to the buffer. The `channel` parameter acts as a key for the data field. The data fields you send are dynamic; you can selectively send data as long as the channel matches. 122 | 123 | ```c 124 | uint8_t addDigitalInput(uint8_t channel, uint32_t value); 125 | uint8_t addDigitalOutput(uint8_t channel, uint32_t value); 126 | uint8_t addAnalogInput(uint8_t channel, float value); // 3 decimals 127 | uint8_t addAnalogOutput(uint8_t channel, float value); // 3 decimals 128 | uint8_t addLuminosity(uint8_t channel, uint32_t value); // in luxes 129 | uint8_t addPresence(uint8_t channel, uint32_t value); 130 | uint8_t addTemperature(uint8_t channel, float value); // in celcius (1 decimal) 131 | uint8_t addRelativeHumidity(uint8_t channel, float value); // in % (0.5% steps) 132 | uint8_t addAccelerometer(uint8_t channel, float x, float y, float z); // 3 decimals for each axis 133 | uint8_t addBarometricPressure(uint8_t channel, float value); // in hPa (1 decimal) 134 | uint8_t addGyrometer(uint8_t channel, float x, float y, float z); // 2 decimals for each axis 135 | uint8_t addGPS(uint8_t channel, float latitude, float longitude, float altitude); // lat & long with 4 decimals, altitude with 2 decimals 136 | 137 | uint8_t addUnixTime(uint8_t channel, uint32_t value); 138 | 139 | uint8_t addGenericSensor(uint8_t channel, float value); 140 | uint8_t addVoltage(uint8_t channel, float value); // in volts (2 decimals) 141 | uint8_t addCurrent(uint8_t channel, float value); // in amperes (3 decimals) 142 | uint8_t addFrequency(uint8_t channel, uint32_t value); // in hertzs 143 | uint8_t addPercentage(uint8_t channel, uint32_t value); // 0 to 100 144 | uint8_t addAltitude(uint8_t channel, float value); // in meters 145 | uint8_t addPower(uint8_t channel, float value); // in watts 146 | uint8_t addDistance(uint8_t channel, float value); // in meters (3 decimals) 147 | uint8_t addEnergy(uint8_t channel, float value); // in kWh (3 decimals) 148 | uint8_t addDirection(uint8_t channel, float value); // in degrees 149 | uint8_t addSwitch(uint8_t channel, uint32_t value); // 0 or 1 150 | 151 | uint8_t CayenneLPP::addConcentration(uint8_t channel, uint32_t value); // 1 PPM unsigned - PPM means Parts per million 1PPM = 1 * 10 ^-6 = 0.000 001 152 | uint8_t CayenneLPP::addColour(uint8_t channel, uint8_t r, uint8_t g, uint8_t b); // R: 255 G: 255 B: 255 153 | ``` 154 | 155 | ### Method: `getError` 156 | 157 | Returns the last error ID, once returned the error is reset to OK. Possible error values are: 158 | 159 | * `LPP_ERROR_OK`: no error 160 | * `LPP_ERROR_OVERFLOW`: When encoding, the latest field would have exceeded the internal buffer size. Try increasing the buffer size in the constructor. When decoding, the payload is not long enough to hold the expected data. Probably a size mismatch. 161 | * `LPP_ERROR_UNKNOWN_TYPE`: When decoding, the decoded type does not match any of the supported ones. 162 | 163 | ```c 164 | uint8_t getError(void); 165 | ``` 166 | -------------------------------------------------------------------------------- /decoders/decoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @reference https://github.com/myDevicesIoT/cayenne-docs/blob/master/docs/LORA.md 3 | * @reference http://openmobilealliance.org/wp/OMNA/LwM2M/LwM2MRegistry.html#extlabel 4 | * 5 | * Adapted for lora-app-server from https://gist.github.com/iPAS/e24970a91463a4a8177f9806d1ef14b8 6 | * 7 | * Type IPSO LPP Hex Data Size Data Resolution per bit 8 | * Digital Input 3200 0 0 1 1 9 | * Digital Output 3201 1 1 1 1 10 | * Analog Input 3202 2 2 2 0.01 Signed 11 | * Analog Output 3203 3 3 2 0.01 Signed 12 | * Illuminance Sensor 3301 101 65 2 1 Lux Unsigned MSB 13 | * Presence Sensor 3302 102 66 1 1 14 | * Temperature Sensor 3303 103 67 2 0.1 °C Signed MSB 15 | * Humidity Sensor 3304 104 68 1 0.5 % Unsigned 16 | * Accelerometer 3313 113 71 6 0.001 G Signed MSB per axis 17 | * Barometer 3315 115 73 2 0.1 hPa Unsigned MSB 18 | * Time 3333 133 85 4 Unix time MSB 19 | * Gyrometer 3334 134 86 6 0.01 °/s Signed MSB per axis 20 | * GPS Location 3336 136 88 9 Latitude : 0.0001 ° Signed MSB 21 | * Longitude : 0.0001 ° Signed MSB 22 | * Altitude : 0.01 meter Signed MSB 23 | * 24 | * Additional types 25 | * Generic Sensor 3300 100 64 4 Unsigned integer MSB 26 | * Voltage 3316 116 74 2 0.01 V Unsigned MSB 27 | * Current 3317 117 75 2 0.001 A Unsigned MSB 28 | * Frequency 3318 118 76 4 1 Hz Unsigned MSB 29 | * Percentage 3320 120 78 1 1% Unsigned 30 | * Altitude 3321 121 79 2 1m Signed MSB 31 | * Concentration 3325 125 7D 2 1 PPM unsigned : 1pmm = 1 * 10 ^-6 = 0.000 001 32 | * Power 3328 128 80 2 1 W Unsigned MSB 33 | * Distance 3330 130 82 4 0.001m Unsigned MSB 34 | * Energy 3331 131 83 4 0.001kWh Unsigned MSB 35 | * Colour 3335 135 87 3 R: 255 G: 255 B: 255 36 | * Direction 3332 132 84 2 1º Unsigned MSB 37 | * Switch 3342 142 8E 1 0/1 38 | 39 | * 40 | */ 41 | 42 | // lppDecode decodes an array of bytes into an array of ojects, 43 | // each one with the channel, the data type and the value. 44 | function lppDecode(bytes) { 45 | 46 | var sensor_types = { 47 | 0 : {'size': 1, 'name': 'digital_in', 'signed': false, 'divisor': 1}, 48 | 1 : {'size': 1, 'name': 'digital_out', 'signed': false, 'divisor': 1}, 49 | 2 : {'size': 2, 'name': 'analog_in', 'signed': true , 'divisor': 100}, 50 | 3 : {'size': 2, 'name': 'analog_out', 'signed': true , 'divisor': 100}, 51 | 100: {'size': 4, 'name': 'generic', 'signed': false, 'divisor': 1}, 52 | 101: {'size': 2, 'name': 'illuminance', 'signed': false, 'divisor': 1}, 53 | 102: {'size': 1, 'name': 'presence', 'signed': false, 'divisor': 1}, 54 | 103: {'size': 2, 'name': 'temperature', 'signed': true , 'divisor': 10}, 55 | 104: {'size': 1, 'name': 'humidity', 'signed': false, 'divisor': 2}, 56 | 113: {'size': 6, 'name': 'accelerometer', 'signed': true , 'divisor': 1000}, 57 | 115: {'size': 2, 'name': 'barometer', 'signed': false, 'divisor': 10}, 58 | 116: {'size': 2, 'name': 'voltage', 'signed': false, 'divisor': 100}, 59 | 117: {'size': 2, 'name': 'current', 'signed': false, 'divisor': 1000}, 60 | 118: {'size': 4, 'name': 'frequency', 'signed': false, 'divisor': 1}, 61 | 120: {'size': 1, 'name': 'percentage', 'signed': false, 'divisor': 1}, 62 | 121: {'size': 2, 'name': 'altitude', 'signed': true, 'divisor': 1}, 63 | 125: {'size': 2, 'name': 'concentration', 'signed': false, 'divisor': 1}, 64 | 128: {'size': 2, 'name': 'power', 'signed': false, 'divisor': 1}, 65 | 130: {'size': 4, 'name': 'distance', 'signed': false, 'divisor': 1000}, 66 | 131: {'size': 4, 'name': 'energy', 'signed': false, 'divisor': 1000}, 67 | 132: {'size': 2, 'name': 'direction', 'signed': false, 'divisor': 1}, 68 | 133: {'size': 4, 'name': 'time', 'signed': false, 'divisor': 1}, 69 | 134: {'size': 6, 'name': 'gyrometer', 'signed': true , 'divisor': 100}, 70 | 135: {'size': 3, 'name': 'colour', 'signed': false, 'divisor': 1}, 71 | 136: {'size': 9, 'name': 'gps', 'signed': true, 'divisor': [10000,10000,100]}, 72 | 142: {'size': 1, 'name': 'switch', 'signed': false, 'divisor': 1}, 73 | }; 74 | 75 | function arrayToDecimal(stream, is_signed, divisor) { 76 | 77 | var value = 0; 78 | for (var i = 0; i < stream.length; i++) { 79 | if (stream[i] > 0xFF) 80 | throw 'Byte value overflow!'; 81 | value = (value << 8) | stream[i]; 82 | } 83 | 84 | if (is_signed) { 85 | var edge = 1 << (stream.length) * 8; // 0x1000.. 86 | var max = (edge - 1) >> 1; // 0x0FFF.. >> 1 87 | value = (value > max) ? value - edge : value; 88 | } 89 | 90 | value /= divisor; 91 | 92 | return value; 93 | 94 | } 95 | 96 | var sensors = []; 97 | var i = 0; 98 | while (i < bytes.length) { 99 | 100 | var s_no = bytes[i++]; 101 | var s_type = bytes[i++]; 102 | if (typeof sensor_types[s_type] == 'undefined') { 103 | throw 'Sensor type error!: ' + s_type; 104 | } 105 | 106 | var s_value = 0; 107 | var type = sensor_types[s_type]; 108 | switch (s_type) { 109 | 110 | case 113: // Accelerometer 111 | case 134: // Gyrometer 112 | s_value = { 113 | 'x': arrayToDecimal(bytes.slice(i+0, i+2), type.signed, type.divisor), 114 | 'y': arrayToDecimal(bytes.slice(i+2, i+4), type.signed, type.divisor), 115 | 'z': arrayToDecimal(bytes.slice(i+4, i+6), type.signed, type.divisor) 116 | }; 117 | break; 118 | 119 | case 136: // GPS Location 120 | s_value = { 121 | 'latitude': arrayToDecimal(bytes.slice(i+0, i+3), type.signed, type.divisor[0]), 122 | 'longitude': arrayToDecimal(bytes.slice(i+3, i+6), type.signed, type.divisor[1]), 123 | 'altitude': arrayToDecimal(bytes.slice(i+6, i+9), type.signed, type.divisor[2]) 124 | }; 125 | break; 126 | case 135: // Colour 127 | s_value = { 128 | 'r': arrayToDecimal(bytes.slice(i+0, i+1), type.signed, type.divisor), 129 | 'g': arrayToDecimal(bytes.slice(i+1, i+2), type.signed, type.divisor), 130 | 'b': arrayToDecimal(bytes.slice(i+2, i+3), type.signed, type.divisor) 131 | }; 132 | break; 133 | 134 | default: // All the rest 135 | s_value = arrayToDecimal(bytes.slice(i, i + type.size), type.signed, type.divisor); 136 | break; 137 | } 138 | 139 | sensors.push({ 140 | 'channel': s_no, 141 | 'type': s_type, 142 | 'name': type.name, 143 | 'value': s_value 144 | }); 145 | 146 | i += type.size; 147 | 148 | } 149 | 150 | return sensors; 151 | 152 | } 153 | 154 | // To use with TTN 155 | function decodeUplink(input) { 156 | 157 | bytes = input.bytes; 158 | fPort = input.fPort; 159 | 160 | // flat output (like original decoder): 161 | var response = {}; 162 | lppDecode(bytes, 1).forEach(function(field) { 163 | response[field['name'] + '_' + field['channel']] = field['value']; 164 | }); 165 | return { 166 |  data: response 167 |  }; 168 | 169 | // field output 170 | //return {'fields': lppDecode(bytes, fPort)}; 171 | 172 | } 173 | 174 | // To use with NodeRED 175 | // Assuming msg.payload contains the LPP-encoded byte array 176 | /* 177 | msg.fields = lppDecode(msg.payload); 178 | return msg; 179 | */ 180 | -------------------------------------------------------------------------------- /src/CayenneLPP.h: -------------------------------------------------------------------------------- 1 | // Adapted from https://developer.mbed.org/teams/myDevicesIoT/code/Cayenne-LPP/ 2 | 3 | // Copyright © 2017 The Things Network 4 | // Use of this source code is governed by the MIT license that can be found in 5 | // the LICENSE file. 6 | 7 | #ifndef CAYENNE_LPP_H 8 | #define CAYENNE_LPP_H 9 | 10 | // Arduino framework 11 | #ifdef ARDUINO 12 | #include 13 | #include 14 | #endif 15 | // ESP-IDF framework 16 | #if !defined(ARDUINO) && defined(IDF_VER) 17 | #include 18 | #endif 19 | // Non Arduino frameworks 20 | #ifndef ARDUINO 21 | #include 22 | #include 23 | #include "CayenneLPPMessage.h" 24 | #include "CayenneLPPPolyline.h" 25 | #endif 26 | 27 | #define LPP_DIGITAL_INPUT 0 // 1 byte 28 | #define LPP_DIGITAL_OUTPUT 1 // 1 byte 29 | #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed 30 | #define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed 31 | #define LPP_GENERIC_SENSOR 100 // 4 bytes, unsigned 32 | #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned 33 | #define LPP_PRESENCE 102 // 1 byte, bool 34 | #define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed 35 | #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned 36 | #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G 37 | #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1hPa unsigned 38 | #define LPP_VOLTAGE 116 // 2 bytes 0.01V unsigned 39 | #define LPP_CURRENT 117 // 2 bytes 0.001A unsigned 40 | #define LPP_FREQUENCY 118 // 4 bytes 1Hz unsigned 41 | #define LPP_PERCENTAGE 120 // 1 byte 1-100% unsigned 42 | #define LPP_ALTITUDE 121 // 2 byte 1m signed 43 | #define LPP_CONCENTRATION 125 // 2 bytes, 1 ppm unsigned 44 | #define LPP_POWER 128 // 2 byte, 1W, unsigned 45 | #define LPP_DISTANCE 130 // 4 byte, 0.001m, unsigned 46 | #define LPP_ENERGY 131 // 4 byte, 0.001kWh, unsigned 47 | #define LPP_DIRECTION 132 // 2 bytes, 1deg, unsigned 48 | #define LPP_UNIXTIME 133 // 4 bytes, unsigned 49 | #define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s 50 | #define LPP_COLOUR 135 // 1 byte per RGB Color 51 | #define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter 52 | #define LPP_SWITCH 142 // 1 byte, 0/1 53 | #define LPP_POLYLINE 240 // 1 byte size, 1 byte delta factor, 3 byte lon/lat 0.0001° * factor, n (size-8) bytes deltas 54 | 55 | // Only Data Size 56 | #define LPP_DIGITAL_INPUT_SIZE 1 57 | #define LPP_DIGITAL_OUTPUT_SIZE 1 58 | #define LPP_ANALOG_INPUT_SIZE 2 59 | #define LPP_ANALOG_OUTPUT_SIZE 2 60 | #define LPP_GENERIC_SENSOR_SIZE 4 61 | #define LPP_LUMINOSITY_SIZE 2 62 | #define LPP_PRESENCE_SIZE 1 63 | #define LPP_TEMPERATURE_SIZE 2 64 | #define LPP_RELATIVE_HUMIDITY_SIZE 1 65 | #define LPP_ACCELEROMETER_SIZE 6 66 | #define LPP_BAROMETRIC_PRESSURE_SIZE 2 67 | #define LPP_VOLTAGE_SIZE 2 68 | #define LPP_CURRENT_SIZE 2 69 | #define LPP_FREQUENCY_SIZE 4 70 | #define LPP_PERCENTAGE_SIZE 1 71 | #define LPP_ALTITUDE_SIZE 2 72 | #define LPP_POWER_SIZE 2 73 | #define LPP_DISTANCE_SIZE 4 74 | #define LPP_ENERGY_SIZE 4 75 | #define LPP_DIRECTION_SIZE 2 76 | #define LPP_UNIXTIME_SIZE 4 77 | #define LPP_GYROMETER_SIZE 6 78 | #define LPP_GPS_SIZE 9 79 | #define LPP_SWITCH_SIZE 1 80 | #define LPP_CONCENTRATION_SIZE 2 81 | #define LPP_COLOUR_SIZE 3 82 | #define LPP_MIN_POLYLINE_SIZE 8 83 | 84 | // Multipliers 85 | #define LPP_DIGITAL_INPUT_MULT 1 86 | #define LPP_DIGITAL_OUTPUT_MULT 1 87 | #define LPP_ANALOG_INPUT_MULT 100 88 | #define LPP_ANALOG_OUTPUT_MULT 100 89 | #define LPP_GENERIC_SENSOR_MULT 1 90 | #define LPP_LUMINOSITY_MULT 1 91 | #define LPP_PRESENCE_MULT 1 92 | #define LPP_TEMPERATURE_MULT 10 93 | #define LPP_RELATIVE_HUMIDITY_MULT 2 94 | #define LPP_ACCELEROMETER_MULT 1000 95 | #define LPP_BAROMETRIC_PRESSURE_MULT 10 96 | #define LPP_VOLTAGE_MULT 100 97 | #define LPP_CURRENT_MULT 1000 98 | #define LPP_FREQUENCY_MULT 1 99 | #define LPP_PERCENTAGE_MULT 1 100 | #define LPP_ALTITUDE_MULT 1 101 | #define LPP_POWER_MULT 1 102 | #define LPP_DISTANCE_MULT 1000 103 | #define LPP_ENERGY_MULT 1000 104 | #define LPP_DIRECTION_MULT 1 105 | #define LPP_UNIXTIME_MULT 1 106 | #define LPP_GYROMETER_MULT 100 107 | #define LPP_GPS_LAT_LON_MULT 10000 108 | #define LPP_GPS_ALT_MULT 100 109 | #define LPP_SWITCH_MULT 1 110 | #define LPP_CONCENTRATION_MULT 1 111 | #define LPP_COLOUR_MULT 1 112 | 113 | #define LPP_ERROR_OK 0 114 | #define LPP_ERROR_OVERFLOW 1 115 | #define LPP_ERROR_UNKOWN_TYPE 2 116 | 117 | class CayenneLPP { 118 | 119 | public: 120 | CayenneLPP(uint8_t size); 121 | ~CayenneLPP(); 122 | 123 | void reset(void); 124 | uint8_t getSize(void); 125 | uint8_t *getBuffer(void); 126 | uint8_t copy(uint8_t *buffer); 127 | uint8_t getError(); 128 | 129 | // Decoder methods 130 | const char *getTypeName(uint8_t type); 131 | // Arduino or ESP-IDF framework 132 | #if defined(ARDUINO) || defined(IDF_VER) 133 | uint8_t decode(uint8_t *buffer, uint8_t size, JsonArray &root); 134 | uint8_t decodeTTN(uint8_t *buffer, uint8_t size, JsonObject &root); 135 | #endif 136 | // Non Arduino frameworks 137 | #ifndef ARDUINO 138 | uint8_t decode(uint8_t *buffer, uint8_t size, std::map &messageMap); 139 | #endif 140 | 141 | // Original LPPv1 data types 142 | #ifndef CAYENNE_DISABLE_DIGITAL_INPUT 143 | uint8_t addDigitalInput(uint8_t channel, uint32_t value); 144 | #endif 145 | #ifndef CAYENNE_DISABLE_DIGITAL_OUTPUT 146 | uint8_t addDigitalOutput(uint8_t channel, uint32_t value); 147 | #endif 148 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 149 | uint8_t addAnalogInput(uint8_t channel, float value); 150 | #endif 151 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 152 | uint8_t addAnalogOutput(uint8_t channel, float value); 153 | #endif 154 | #ifndef CAYENNE_DISABLE_LUMINOSITY 155 | uint8_t addLuminosity(uint8_t channel, uint32_t value); 156 | #endif 157 | #ifndef CAYENNE_DISABLE_PRESENCE 158 | uint8_t addPresence(uint8_t channel, uint32_t value); 159 | #endif 160 | #ifndef CAYENNE_DISABLE_TEMPERATURE 161 | uint8_t addTemperature(uint8_t channel, float value); 162 | #endif 163 | #ifndef CAYENNE_DISABLE_RELATIVE_HUMIDITY 164 | uint8_t addRelativeHumidity(uint8_t channel, float value); 165 | #endif 166 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 167 | uint8_t addAccelerometer(uint8_t channel, float x, float y, float z); 168 | #endif 169 | #ifndef CAYENNE_DISABLE_BAROMETRIC_PRESSUE 170 | uint8_t addBarometricPressure(uint8_t channel, float value); 171 | #endif 172 | #ifndef CAYENNE_DISABLE_GYROMETER 173 | uint8_t addGyrometer(uint8_t channel, float x, float y, float z); 174 | #endif 175 | #ifndef CAYENNE_DISABLE_GPS 176 | uint8_t addGPS(uint8_t channel, float latitude, float longitude, 177 | float altitude); 178 | #endif 179 | 180 | // Additional data types 181 | #ifndef CAYENNE_DISABLE_UNIX_TIME 182 | uint8_t addUnixTime(uint8_t channel, uint32_t value); 183 | #endif 184 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 185 | uint8_t addGenericSensor(uint8_t channel, float value); 186 | #endif 187 | #ifndef CAYENNE_DISABLE_VOLTAGE 188 | uint8_t addVoltage(uint8_t channel, float value); 189 | #endif 190 | #ifndef CAYENNE_DISABLE_CURRENT 191 | uint8_t addCurrent(uint8_t channel, float value); 192 | #endif 193 | #ifndef CAYENNE_DISABLE_FREQUENCY 194 | uint8_t addFrequency(uint8_t channel, uint32_t value); 195 | #endif 196 | #ifndef CAYENNE_DISABLE_PERCENTAGE 197 | uint8_t addPercentage(uint8_t channel, uint32_t value); 198 | #endif 199 | #ifndef CAYENNE_DISABLE_ALTITUDE 200 | uint8_t addAltitude(uint8_t channel, float value); 201 | #endif 202 | #ifndef CAYENNE_DISABLE_POWER 203 | uint8_t addPower(uint8_t channel, float value); 204 | #endif 205 | #ifndef CAYENNE_DISABLE_DISTANCE 206 | uint8_t addDistance(uint8_t channel, float value); 207 | #endif 208 | #ifndef CAYENNE_DISABLE_ENERGY 209 | uint8_t addEnergy(uint8_t channel, float value); 210 | #endif 211 | #ifndef CAYENNE_DISABLE_DIRECTION 212 | uint8_t addDirection(uint8_t channel, float value); 213 | #endif 214 | #ifndef CAYENNE_DISABLE_SWITCH 215 | uint8_t addSwitch(uint8_t channel, uint32_t value); 216 | #endif 217 | #ifndef CAYENNE_DISABLE_CONCENTRATION 218 | uint8_t addConcentration(uint8_t channel, uint32_t value); 219 | #endif 220 | #ifndef CAYENNE_DISABLE_COLOUR 221 | uint8_t addColour(uint8_t channel, uint8_t r, uint8_t g, uint8_t b); 222 | #endif 223 | #ifndef ARDUINO 224 | uint8_t addPolyline(uint8_t channel, 225 | const std::vector>& coords, 226 | CayenneLPPPolyline::Precision precision = CayenneLPPPolyline::Prec0_0001, 227 | CayenneLPPPolyline::Simplification simplification = CayenneLPPPolyline::DouglasPeucker); 228 | #endif 229 | 230 | protected: 231 | bool isType(uint8_t type); 232 | uint8_t getTypeSize(uint8_t type); 233 | uint32_t getTypeMultiplier(uint8_t type); 234 | bool getTypeSigned(uint8_t type); 235 | 236 | float getValue(uint8_t *buffer, uint8_t size, uint32_t multiplier, 237 | bool is_signed); 238 | uint32_t getValue32(uint8_t *buffer, uint8_t size); 239 | template 240 | uint8_t addField(uint8_t type, uint8_t channel, T value); 241 | 242 | uint8_t *_buffer; 243 | uint8_t _maxsize; 244 | uint8_t _cursor; 245 | uint8_t _error = LPP_ERROR_OK; 246 | 247 | #ifndef ARDUINO 248 | CayenneLPPPolyline _polyline; 249 | #endif 250 | }; 251 | 252 | #endif 253 | -------------------------------------------------------------------------------- /test/aunit/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | CayenneLPP 4 | 5 | AUnit Unit Tests 6 | 7 | Copyright (C) 2019 by Xose Pérez 8 | 9 | The rpnlib library is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Lesser General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | The rpnlib library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public License 20 | along with the rpnlib library. If not, see . 21 | 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | using namespace aunit; 30 | 31 | #define LPP_TEST_VERBOSE 0 32 | 33 | // ----------------------------------------------------------------------------- 34 | // Test class 35 | // ----------------------------------------------------------------------------- 36 | 37 | class EncoderTest: public TestOnce { 38 | 39 | protected: 40 | 41 | virtual void setup() override { 42 | lpp = new CayenneLPP(16); 43 | lpp->reset(); 44 | } 45 | 46 | virtual void teardown() override { 47 | delete lpp; 48 | } 49 | 50 | virtual void compare(unsigned char depth, uint8_t * expected) { 51 | 52 | uint8_t * actual = lpp->getBuffer(); 53 | 54 | #if LPP_TEST_VERBOSE 55 | PC_SERIAL.println(); 56 | char buff[6]; 57 | PC_SERIAL.print("Expected: "); 58 | for (unsigned char i=0; igetSize(); i++) { 65 | snprintf(buff, sizeof(buff), "%02X ", actual[i]); 66 | PC_SERIAL.print(buff); 67 | } 68 | PC_SERIAL.println(); 69 | #endif 70 | 71 | assertEqual(depth, lpp->getSize()); 72 | for (unsigned char i=0; ireset(); 89 | } 90 | 91 | virtual void teardown() override { 92 | delete lpp; 93 | } 94 | 95 | virtual void compare(uint8_t * buffer, unsigned char len, uint8_t fields, float value = 0, float precission = 0) { 96 | 97 | StaticJsonDocument<512> jsonBuffer; 98 | JsonArray root = jsonBuffer.createNestedArray(); 99 | assertEqual(fields, lpp->decode(buffer, len, root)); 100 | assertEqual(fields, (uint8_t) root.size()); 101 | 102 | #if LPP_TEST_VERBOSE 103 | PC_SERIAL.println(); 104 | serializeJsonPretty(root, PC_SERIAL); 105 | PC_SERIAL.println(); 106 | #endif 107 | 108 | if (precission > 0) assertNear(value, root[0]["value"], precission); 109 | 110 | } 111 | 112 | CayenneLPP * lpp; 113 | 114 | 115 | }; 116 | 117 | // ----------------------------------------------------------------------------- 118 | // Tests 119 | // ----------------------------------------------------------------------------- 120 | 121 | testF(EncoderTest, Multichannel) { 122 | lpp->addTemperature(3, 27.2); 123 | lpp->addRelativeHumidity(2, 54); 124 | uint8_t expected[] = {0x03,0x67,0x01,0x10,0x02,0x68,0x6C}; 125 | compare(sizeof(expected), expected); 126 | } 127 | 128 | testF(EncoderTest, Overflow) { 129 | assertEqual(4, lpp->addTemperature(1, 27.2)); 130 | assertEqual(8, lpp->addTemperature(1, 27.2)); 131 | assertEqual(12, lpp->addTemperature(1, 27.2)); 132 | assertEqual(16, lpp->addTemperature(1, 27.2)); 133 | assertEqual(LPP_ERROR_OK, lpp->getError()); 134 | assertEqual(0, lpp->addTemperature(1, 27.2)); 135 | assertEqual(LPP_ERROR_OVERFLOW, lpp->getError()); 136 | } 137 | 138 | testF(EncoderTest, Reset) { 139 | lpp->addTemperature(3, 27.2); 140 | lpp->reset(); 141 | lpp->addTemperature(5, 25.5); 142 | uint8_t expected[] = {0x05,0x67,0x00,0xFF}; 143 | compare(sizeof(expected), expected); 144 | } 145 | 146 | testF(EncoderTest, Limit) { 147 | lpp->addPower(1, 0xFFFF); 148 | uint8_t expected[] = {0x01,0x80,0xFF,0xFF}; 149 | compare(sizeof(expected), expected); 150 | } 151 | 152 | testF(EncoderTest, Negative_Temperature) { 153 | lpp->addTemperature(5, -4.7); 154 | uint8_t expected[] = {0x05,0x67,0xFF,0xD1}; 155 | compare(sizeof(expected), expected); 156 | } 157 | 158 | testF(EncoderTest, Humidity) { 159 | lpp->addRelativeHumidity(2, 54); 160 | uint8_t expected[] = {0x02,0x68,0x6C}; 161 | compare(sizeof(expected), expected); 162 | } 163 | 164 | testF(EncoderTest, Generic_Sensor) { 165 | lpp->addGenericSensor(1, 998); 166 | uint8_t expected[] = {0x01,0x64,0x00,0x00,0x03,0xE6}; 167 | compare(sizeof(expected), expected); 168 | } 169 | 170 | testF(EncoderTest, Accelerometer) { 171 | lpp->addAccelerometer(6, 1.234, -1.234, 0); 172 | uint8_t expected[] = {0x06,0x71,0x04,0xD2,0xFB,0x2E,0x00,0x00}; 173 | compare(sizeof(expected), expected); 174 | } 175 | 176 | testF(EncoderTest, Voltage) { 177 | lpp->addVoltage(3, 224.56); 178 | uint8_t expected[] = {0x03,0x74,0x57,0xB8}; 179 | compare(sizeof(expected), expected); 180 | } 181 | 182 | testF(EncoderTest, Current) { 183 | lpp->addCurrent(1, 0.237); 184 | uint8_t expected[] = {0x01,0x75,0x00,0xED}; 185 | compare(sizeof(expected), expected); 186 | } 187 | 188 | testF(EncoderTest, Frequency) { 189 | lpp->addFrequency(1, 868100000); 190 | uint8_t expected[] = {0x01,0x76,0x33,0xBe,0x27,0xA0}; 191 | compare(sizeof(expected), expected); 192 | } 193 | 194 | testF(EncoderTest, Altitude) { 195 | lpp->addAltitude(5, -17); 196 | uint8_t expected[] = {0x05,0x79,0xFF,0xEF}; 197 | compare(sizeof(expected), expected); 198 | } 199 | 200 | testF(EncoderTest, Concentration) { 201 | lpp->addConcentration(4, 4079); 202 | uint8_t expected[] = {0x05,0x7D,0x0F,0xEF}; 203 | compare(sizeof(expected), expected); 204 | } 205 | 206 | testF(EncoderTest, Colour) { 207 | lpp->addColour(7, 24, 239, 15); 208 | uint8_t expected[] = {0x07,0x87,0x0F,0xEF, 0x18}; 209 | compare(sizeof(expected), expected); 210 | } 211 | 212 | testF(EncoderTest, Power) { 213 | lpp->addPower(5, 3450); 214 | uint8_t expected[] = {0x05,0x80,0x0D,0x7A}; 215 | compare(sizeof(expected), expected); 216 | } 217 | 218 | testF(EncoderTest, Distance) { 219 | lpp->addDistance(1, 0.034); 220 | uint8_t expected[] = {0x01,0x82,0x00,0x00,0x00,0x22}; 221 | compare(sizeof(expected), expected); 222 | } 223 | 224 | testF(EncoderTest, Energy) { 225 | lpp->addEnergy(1, 7953.2); 226 | uint8_t expected[] = {0x01,0x83,0x00,0x79,0x5B,0x30}; 227 | compare(sizeof(expected), expected); 228 | } 229 | 230 | testF(EncoderTest, Direction) { 231 | lpp->addDirection(1, 256); 232 | uint8_t expected[] = {0x01,0x84,0x01,0x00}; 233 | compare(sizeof(expected), expected); 234 | } 235 | 236 | testF(EncoderTest, GPS) { 237 | lpp->addGPS(1, 42.3519, -87.9094, 10); 238 | uint8_t expected[] = {0x01,0x88,0x06,0x76,0x5e,0xf2,0x96,0x0a,0x00,0x03,0xe8}; 239 | compare(sizeof(expected), expected); 240 | } 241 | 242 | testF(EncoderTest, Switch) { 243 | lpp->addSwitch(5, 1); 244 | uint8_t expected[] = {0x05,0x8E,0x01}; 245 | compare(sizeof(expected), expected); 246 | } 247 | 248 | testF(DecoderTest, Multichannel) { 249 | uint8_t buffer[] = {0x03,0x67,0x01,0x10,0x05,0x67,0x00,0xFF}; 250 | compare(buffer, sizeof(buffer), 2, 27.2, 0.01); 251 | } 252 | 253 | testF(DecoderTest, UnknownType) { 254 | uint8_t buffer[] = {0x03,0x24,0x01,0x10}; 255 | compare(buffer, sizeof(buffer), 0); 256 | assertEqual(LPP_ERROR_UNKOWN_TYPE, lpp->getError()); 257 | assertEqual(LPP_ERROR_OK, lpp->getError()); 258 | } 259 | 260 | testF(DecoderTest, Overflow) { 261 | uint8_t buffer[] = {0x05,0x67,0xFF}; 262 | compare(buffer, sizeof(buffer), 0); 263 | assertEqual(LPP_ERROR_OVERFLOW, lpp->getError()); 264 | assertEqual(LPP_ERROR_OK, lpp->getError()); 265 | } 266 | 267 | testF(DecoderTest, Negative_Temperature) { 268 | uint8_t buffer[] = {0x05,0x67,0xFF,0xD1}; 269 | compare(buffer, sizeof(buffer), 1, -4.7, 0.01); 270 | } 271 | 272 | testF(DecoderTest, GPS) { 273 | uint8_t buffer[] = {0x01,0x88,0x06,0x76,0x5e,0xf2,0x96,0x0a,0x00,0x03,0xe8}; 274 | compare(buffer, sizeof(buffer), 1); 275 | } 276 | 277 | testF(DecoderTest, Voltage) { 278 | uint8_t buffer[] = {0x03,0x74,0x57,0xB8}; 279 | compare(buffer, sizeof(buffer), 1, 224.56, 0.01); 280 | } 281 | 282 | testF(DecoderTest, Distance) { 283 | uint8_t buffer[] = {0x01,0x82,0x00,0x00,0x00,0x22}; 284 | compare(buffer, sizeof(buffer), 1, 0.034, 0.001); 285 | } 286 | 287 | testF(DecoderTest, Frequency) { 288 | uint8_t buffer[] = {0x01,0x76,0x33,0xBe,0x27,0xA0}; 289 | compare(buffer, sizeof(buffer), 1, 868100000, 0.1); 290 | } 291 | 292 | // ----------------------------------------------------------------------------- 293 | // Main 294 | // ----------------------------------------------------------------------------- 295 | 296 | void setup() { 297 | 298 | PC_SERIAL.begin(115200); 299 | while (!PC_SERIAL && millis() < 5000); 300 | 301 | Printer::setPrinter(&PC_SERIAL); 302 | //TestRunner::setVerbosity(Verbosity::kAll); 303 | 304 | } 305 | 306 | void loop() { 307 | TestRunner::run(); 308 | delay(1); 309 | } 310 | -------------------------------------------------------------------------------- /src/CayenneLPPPolyline.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CayenneLPP - CayenneLPP Polyline Codec 3 | * 4 | * Copyright (C) 2021 by Manuel Weichselbaumer 5 | * 6 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 7 | * 8 | */ 9 | 10 | #ifndef ARDUINO 11 | #include "CayenneLPPPolyline.h" 12 | 13 | #include 14 | #include 15 | 16 | const double scaleFactor = 10000.0; 17 | const std::map s_valueMap { 18 | //{ 224, 0.0 }, // 0.00001, reserved 19 | //{ 225, 0.0 }, // 0.000025, reserved 20 | //{ 226, 0.0 }, // 0.00005, reserved 21 | { 227, 1.0 }, // 0.0001 22 | { 228, 2.0 }, // 0.0002 23 | { 229, 5.0 }, // 0.0005 24 | { 230, 10.0 }, // 0.001 25 | { 231, 20.0 }, // 0.002 26 | { 232, 50.0 }, // 0.005 27 | { 233, 100.0 }, // 0.01 28 | { 234, 200.0 }, // 0.02 29 | { 235, 500.0 }, // 0.05 30 | { 236, 1000.0 }, // 0.1 31 | { 237, 2000.0 }, // 0.2 32 | { 238, 5000.0 }, // 0.5 33 | { 239, 10000.0 }, // 1.0 34 | //{ 240, 20000.0 }, // 2.0 35 | //{ 241, 50000.0 }, // 5.0 36 | //{ 255, 0.0 } // reserved 37 | }; 38 | 39 | struct InitialCoord { 40 | int32_t lat:24; 41 | int32_t lon:24; 42 | }; 43 | 44 | struct DeltaCoord { 45 | int8_t dLat:4; 46 | int8_t dLon:4; 47 | }; 48 | 49 | CayenneLPPPolyline::CayenneLPPPolyline(uint32_t size) : m_maxSize(size) { 50 | } 51 | 52 | std::vector CayenneLPPPolyline::encode(const std::vector& coords, 53 | uint8_t factor, 54 | Simplification simplification) { 55 | reset(); 56 | 57 | if (coords.size() < 2) { 58 | return {}; 59 | } 60 | 61 | const double dFactor = getFactor(factor); 62 | 63 | // Apply Douglas–Peucker first 64 | auto coords2 = simplification == DouglasPeucker ? douglasPeucker(coords, dFactor/scaleFactor * 0.5) : coords; 65 | 66 | // Push initial item to init encoder 67 | pushFirst(coords2.front().first * scaleFactor / dFactor, coords2.front().second * scaleFactor / dFactor, factor); 68 | for (auto it = coords2.begin()+1; it != coords2.end() && m_buffer.size() < m_maxSize; ++it) { 69 | // Latitude -/+ 90 70 | // Longitude -/+ 180 71 | if (abs(it->first) > 90.0 || abs(it->second) > 180.0) { 72 | break; 73 | } 74 | 75 | // Push each item to encoder 76 | push(it->first * scaleFactor / dFactor, it->second * scaleFactor / dFactor, simplification == PerpendicularDistance); 77 | } 78 | // Write final header 79 | pushFirst(coords2.front().first * scaleFactor / dFactor, coords2.front().second * scaleFactor / dFactor, factor); 80 | 81 | return m_buffer; 82 | } 83 | 84 | std::vector CayenneLPPPolyline::encode(const std::vector& coords, 85 | Precision precision, 86 | Simplification simplification) { 87 | return encode(coords, static_cast(precision), simplification); 88 | } 89 | 90 | std::vector> CayenneLPPPolyline::decode(const std::vector& buffer) { 91 | if (buffer.size() < 7) { 92 | return {}; 93 | } 94 | 95 | const double dFactor = getFactor(buffer[1]); 96 | if (dFactor == 0.0) { 97 | return {}; 98 | } 99 | 100 | std::vector> coords; 101 | //uint8_t size = buffer[0]; 102 | int32_t prevLat = buffer[2] << 24 | buffer[3] << 16 | buffer[4] << 8; 103 | prevLat /= 256; 104 | prevLat *= dFactor; 105 | int32_t prevLon = buffer[5] << 24 | buffer[6] << 16 | buffer[7] << 8; 106 | prevLon /= 256; 107 | prevLon *= dFactor; 108 | coords.push_back( { prevLat / scaleFactor, prevLon / scaleFactor } ); 109 | 110 | if (buffer.size() == 7) { 111 | return coords; 112 | } 113 | 114 | for (auto it = buffer.begin()+8; it != buffer.end(); ++it) { 115 | const auto dc = *(DeltaCoord*)&*it; 116 | const int dLat = dc.dLat * dFactor; 117 | const int dLon = dc.dLon * dFactor; 118 | coords.push_back( { (prevLat + dLat) / scaleFactor, (prevLon + dLon) / scaleFactor } ); 119 | prevLat += dLat; 120 | prevLon += dLon; 121 | } 122 | 123 | return coords; 124 | } 125 | 126 | CayenneLPPPolyline::Stats CayenneLPPPolyline::getEncodeStats() const { 127 | return m_stats; 128 | } 129 | 130 | void CayenneLPPPolyline::reset() { 131 | m_buffer.clear(); 132 | m_prevLat = 0.0; 133 | m_prevLon = 0.0; 134 | m_errLat = 0.0; 135 | m_errLon = 0.0; 136 | m_stats = {}; 137 | } 138 | 139 | double CayenneLPPPolyline::getFactor(uint8_t factor) { 140 | if ( factor == 0) { 141 | return 0.0; 142 | } else if (factor < 200) { 143 | return factor; 144 | } else if (s_valueMap.count(factor)) { 145 | return s_valueMap.at(factor); 146 | } else { 147 | return 0.0; 148 | } 149 | } 150 | 151 | void CayenneLPPPolyline::push(double lat, double lon, bool optimize) { 152 | // Compute delta and correct error from previous rounding 153 | const double dLat = (lat - m_prevLat) + m_errLat; 154 | const double dLon = (lon - m_prevLon) + m_errLon; 155 | 156 | // Round values 157 | const int32_t roundLat = round(dLat); 158 | const int32_t roundLon = round(dLon); 159 | 160 | // Ignore items with zero delta 161 | if ((abs(roundLat) < 1) && (abs(roundLon) < 1)) { 162 | ++m_stats.removedCoords; 163 | // Delta fits into one nibble, push it 164 | } else if (abs(roundLat) < 8 && abs(roundLon) < 8) { 165 | writeDelta(roundLat, roundLon, optimize); 166 | // Delta is too big for one nibble. Compute intermediates. 167 | } else { 168 | ++m_stats.addedCoords; 169 | --m_stats.keptCoords; 170 | // Push intermediate. This is a simplified solution. 171 | // A more sophisticated computation can be found her: https://www.movable-type.co.uk/scripts/latlong.html 172 | // Please check the Intermediate point section. 173 | const double divisor = ceil(std::max(abs(dLat/7.0), abs(dLon/7.0))); 174 | push(m_prevLat + dLat / divisor, m_prevLon + dLon / divisor, optimize); 175 | // Push original lat/lon after intermediate. 176 | push(lat, lon, optimize); 177 | return; 178 | } 179 | 180 | // Compute error from rounding 181 | m_errLat = dLat - roundLat; 182 | m_errLon = dLon - roundLon; 183 | 184 | m_prevLat = lat; 185 | m_prevLon = lon; 186 | } 187 | 188 | void CayenneLPPPolyline::pushFirst(double lat, double lon, uint8_t factor) { 189 | const int32_t roundLat = round(lat); 190 | const int32_t roundLon = round(lon); 191 | 192 | writeHeader(roundLat, roundLon, factor); 193 | 194 | m_errLat = (lat - roundLat); 195 | m_errLon = (lon - roundLon); 196 | 197 | m_prevLat = lat; 198 | m_prevLon = lon; 199 | } 200 | 201 | void CayenneLPPPolyline::writeHeader(int32_t lat, int32_t lon, uint8_t factor) { 202 | // ESP-IDF framework 203 | #if !defined(ARDUINO) && defined(IDF_VER) 204 | m_buffer.resize(std::max(static_cast(m_buffer.size()), 8UL)); 205 | #else 206 | m_buffer.resize(std::max(m_buffer.size(), 8UL)); 207 | #endif 208 | m_buffer[0] = m_buffer.size(); 209 | m_buffer[1] = factor; 210 | m_buffer[2] = (lat >> 16); m_buffer[3] = (lat >> 8); m_buffer[4] = (lat); 211 | m_buffer[5] = (lon >> 16); m_buffer[6] = (lon >> 8); m_buffer[7] = (lon); 212 | } 213 | 214 | void CayenneLPPPolyline::writeDelta(int8_t lat, int8_t lon, bool optimize) { 215 | const DeltaCoord& prevDelta = (DeltaCoord&)m_buffer.back(); 216 | const DeltaCoord currDelta { lat, lon }; 217 | 218 | // This is a cheap optimization as an alternative to Douglas-Peucker 219 | // Check if the sum of this and next delta is within range 220 | const int8_t dLat = prevDelta.dLat + currDelta.dLat; 221 | const int8_t dLon = prevDelta.dLon + currDelta.dLon; 222 | if (optimize && m_buffer.size() > 8 && abs(dLat) < 8 && abs(dLon) < 8) { 223 | // Check if previous delta only differs slightly from straight line to current delta 224 | const double distance = abs(dLat * -1.0 * prevDelta.dLon + prevDelta.dLat * dLon) / sqrt(dLat * dLat + dLon * dLon); 225 | if (distance < 0.5) { 226 | ((DeltaCoord&)m_buffer.back()).dLat = dLat; 227 | ((DeltaCoord&)m_buffer.back()).dLon = dLon; 228 | ++m_stats.removedCoords; 229 | return; 230 | } 231 | } 232 | 233 | m_buffer.push_back(*(uint8_t*)(&currDelta)); 234 | ++m_stats.keptCoords; 235 | } 236 | 237 | std::vector CayenneLPPPolyline::douglasPeucker(const std::vector &pointList, double epsilon) { 238 | if (pointList.size() < 2) { 239 | return {}; 240 | } 241 | 242 | std::vector out; 243 | 244 | // Find the point with the maximum distance from line between start and end 245 | double dmax = 0.0; 246 | size_t index = 0; 247 | size_t end = pointList.size()-1; 248 | for (size_t i = 1; i < end; i++) { 249 | double d = distance(pointList[i], pointList[0], pointList[end]); 250 | if (d > dmax) { 251 | index = i; 252 | dmax = d; 253 | } 254 | } 255 | 256 | // If max distance is greater than epsilon, recursively simplify 257 | if (dmax > epsilon) { 258 | // Recursive call 259 | std::vector recResults1; 260 | std::vector recResults2; 261 | std::vector firstLine(pointList.begin(), pointList.begin()+index+1); 262 | std::vector lastLine(pointList.begin()+index, pointList.end()); 263 | recResults1 = douglasPeucker(firstLine, epsilon); 264 | recResults2 = douglasPeucker(lastLine, epsilon); 265 | 266 | // Build the result list 267 | out.assign(recResults1.begin(), recResults1.end()-1); 268 | out.insert(out.end(), recResults2.begin(), recResults2.end()); 269 | 270 | if (out.size() < 2) { 271 | return {}; 272 | } 273 | } else { 274 | //Just return start and end points 275 | out.clear(); 276 | out.push_back(pointList[0]); 277 | out.push_back(pointList[end]); 278 | } 279 | 280 | return out; 281 | } 282 | 283 | double CayenneLPPPolyline::distance(const Point& point, const Point& lineStart, const Point& lineEnd) { 284 | double dLat = lineEnd.first - lineStart.first; 285 | double dLon = lineEnd.second - lineStart.second; 286 | 287 | // Normalise 288 | const double mag = sqrt(dLat * dLat + dLon * dLon); 289 | if (mag > 0.0) { 290 | dLat /= mag; dLon /= mag; 291 | } 292 | 293 | const double pvx = point.first - lineStart.first; 294 | const double pvy = point.second - lineStart.second; 295 | 296 | // Get dot product (project pv onto normalized direction) 297 | const double pvdot = dLat * pvx + dLon * pvy; 298 | 299 | // Scale line direction vector 300 | const double dsx = pvdot * dLat; 301 | const double dsy = pvdot * dLon; 302 | 303 | // Subtract this from pv 304 | const double ax = pvx - dsx; 305 | const double ay = pvy - dsy; 306 | 307 | return sqrt(ax * ax + ay * ay); 308 | } 309 | 310 | #endif 311 | -------------------------------------------------------------------------------- /src/CayenneLPP.cpp: -------------------------------------------------------------------------------- 1 | // Adapted from https://developer.mbed.org/teams/myDevicesIoT/code/Cayenne-LPP/ 2 | 3 | // Copyright © 2017 The Things Network 4 | // Use of this source code is governed by the MIT license that can be found in the LICENSE file. 5 | 6 | #include "CayenneLPP.h" 7 | 8 | #ifndef ARDUINO 9 | #include 10 | #include 11 | #endif 12 | 13 | // ---------------------------------------------------------------------------- 14 | 15 | CayenneLPP::CayenneLPP(uint8_t size) : _maxsize(size) 16 | #ifndef ARDUINO 17 | , _polyline(size - 2) 18 | #endif 19 | { 20 | _buffer = (uint8_t *)malloc(size); 21 | _cursor = 0; 22 | } 23 | 24 | CayenneLPP::~CayenneLPP(void) { 25 | free(_buffer); 26 | } 27 | 28 | void CayenneLPP::reset(void) { 29 | _cursor = 0; 30 | } 31 | 32 | uint8_t CayenneLPP::getSize(void) { 33 | return _cursor; 34 | } 35 | 36 | uint8_t *CayenneLPP::getBuffer(void) { 37 | return _buffer; 38 | } 39 | 40 | uint8_t CayenneLPP::copy(uint8_t *dst) { 41 | memcpy(dst, _buffer, _cursor); 42 | return _cursor; 43 | } 44 | 45 | uint8_t CayenneLPP::getError() { 46 | uint8_t error = _error; 47 | _error = LPP_ERROR_OK; 48 | return error; 49 | } 50 | 51 | // ---------------------------------------------------------------------------- 52 | 53 | bool CayenneLPP::isType(uint8_t type) { 54 | 55 | switch (type) { 56 | #ifndef CAYENNE_DISABLE_DIGITAL_INPUT 57 | case LPP_DIGITAL_INPUT: 58 | #endif 59 | #ifndef CAYENNE_DISABLE_DIGITAL_OUTPUT 60 | case LPP_DIGITAL_OUTPUT: 61 | #endif 62 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 63 | case LPP_ANALOG_INPUT: 64 | #endif 65 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 66 | case LPP_ANALOG_OUTPUT: 67 | #endif 68 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 69 | case LPP_GENERIC_SENSOR: 70 | #endif 71 | #ifndef CAYENNE_DISABLE_LUMINOSITY 72 | case LPP_LUMINOSITY: 73 | #endif 74 | #ifndef CAYENNE_DISABLE_PRESENCE 75 | case LPP_PRESENCE: 76 | #endif 77 | #ifndef CAYENNE_DISABLE_TEMPERATURE 78 | case LPP_TEMPERATURE: 79 | #endif 80 | #ifndef CAYENNE_DISABLE_RELATIVE_HUMIDITY 81 | case LPP_RELATIVE_HUMIDITY: 82 | #endif 83 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 84 | case LPP_ACCELEROMETER: 85 | #endif 86 | #ifndef CAYENNE_DISABLE_BAROMETRIC_PRESSUE 87 | case LPP_BAROMETRIC_PRESSURE: 88 | #endif 89 | #ifndef CAYENNE_DISABLE_VOLTAGE 90 | case LPP_VOLTAGE: 91 | #endif 92 | #ifndef CAYENNE_DISABLE_CURRENT 93 | case LPP_CURRENT: 94 | #endif 95 | #ifndef CAYENNE_DISABLE_FREQUENCY 96 | case LPP_FREQUENCY: 97 | #endif 98 | #ifndef CAYENNE_DISABLE_PERCENTAGE 99 | case LPP_PERCENTAGE: 100 | #endif 101 | #ifndef CAYENNE_DISABLE_ALTITUDE 102 | case LPP_ALTITUDE: 103 | #endif 104 | #ifndef CAYENNE_DISABLE_POWER 105 | case LPP_POWER: 106 | #endif 107 | #ifndef CAYENNE_DISABLE_DISTANCE 108 | case LPP_DISTANCE: 109 | #endif 110 | #ifndef CAYENNE_DISABLE_ENERGY 111 | case LPP_ENERGY: 112 | #endif 113 | #ifndef CAYENNE_DISABLE_DIRECTION 114 | case LPP_DIRECTION: 115 | #endif 116 | #ifndef CAYENNE_DISABLE_UNIX_TIME 117 | case LPP_UNIXTIME: 118 | #endif 119 | #ifndef CAYENNE_DISABLE_GYROMETER 120 | case LPP_GYROMETER: 121 | #endif 122 | #ifndef CAYENNE_DISABLE_GPS 123 | case LPP_GPS: 124 | #endif 125 | #ifndef CAYENNE_DISABLE_SWITCH 126 | case LPP_SWITCH: 127 | #endif 128 | #ifndef CAYENNE_DISABLE_CONCENTRATION 129 | case LPP_CONCENTRATION: 130 | #endif 131 | #ifndef CAYENNE_DISABLE_COLOUR 132 | case LPP_COLOUR: 133 | #endif 134 | #ifndef ARDUINO 135 | case LPP_POLYLINE: 136 | #endif 137 | return true; 138 | } 139 | 140 | return false; 141 | } 142 | 143 | const char * CayenneLPP::getTypeName(uint8_t type) { 144 | 145 | switch (type) { 146 | 147 | #ifndef CAYENNE_DISABLE_DIGITAL_INPUT 148 | case LPP_DIGITAL_INPUT: 149 | return "digital_in"; 150 | #endif 151 | 152 | #ifndef CAYENNE_DISABLE_DIGITAL_OUTPUT 153 | case LPP_DIGITAL_OUTPUT: 154 | return "digital_out"; 155 | #endif 156 | 157 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 158 | case LPP_ANALOG_INPUT: 159 | return "analog_in"; 160 | #endif 161 | 162 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 163 | case LPP_ANALOG_OUTPUT: 164 | return "analog_out"; 165 | #endif 166 | 167 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 168 | case LPP_GENERIC_SENSOR: 169 | return "generic"; 170 | #endif 171 | 172 | #ifndef CAYENNE_DISABLE_LUMINOSITY 173 | case LPP_LUMINOSITY: 174 | return "luminosity"; 175 | #endif 176 | 177 | #ifndef CAYENNE_DISABLE_PRESENCE 178 | case LPP_PRESENCE: 179 | return "presence"; 180 | #endif 181 | 182 | #ifndef CAYENNE_DISABLE_TEMPERATURE 183 | case LPP_TEMPERATURE: 184 | return "temperature"; 185 | #endif 186 | 187 | #ifndef CAYENNE_DISABLE_RELATIVE_HUMIDITY 188 | case LPP_RELATIVE_HUMIDITY: 189 | return "humidity"; 190 | #endif 191 | 192 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 193 | case LPP_ACCELEROMETER: 194 | return "accelerometer"; 195 | #endif 196 | 197 | #ifndef CAYENNE_DISABLE_BAROMETRIC_PRESSUE 198 | case LPP_BAROMETRIC_PRESSURE: 199 | return "pressure"; 200 | #endif 201 | 202 | #ifndef CAYENNE_DISABLE_VOLTAGE 203 | case LPP_VOLTAGE: 204 | return "voltage"; 205 | #endif 206 | 207 | #ifndef CAYENNE_DISABLE_CURRENT 208 | case LPP_CURRENT: 209 | return "current"; 210 | #endif 211 | 212 | #ifndef CAYENNE_DISABLE_FREQUENCY 213 | case LPP_FREQUENCY: 214 | return "frequency"; 215 | #endif 216 | 217 | #ifndef CAYENNE_DISABLE_PERCENTAGE 218 | case LPP_PERCENTAGE: 219 | return "percentage"; 220 | #endif 221 | 222 | #ifndef CAYENNE_DISABLE_ALTITUDE 223 | case LPP_ALTITUDE: 224 | return "altitude"; 225 | #endif 226 | 227 | #ifndef CAYENNE_DISABLE_POWER 228 | case LPP_POWER: 229 | return "power"; 230 | #endif 231 | 232 | #ifndef CAYENNE_DISABLE_DISTANCE 233 | case LPP_DISTANCE: 234 | return "distance"; 235 | #endif 236 | 237 | #ifndef CAYENNE_DISABLE_ENERGY 238 | case LPP_ENERGY: 239 | return "energy"; 240 | #endif 241 | 242 | #ifndef CAYENNE_DISABLE_DIRECTION 243 | case LPP_DIRECTION: 244 | return "direction"; 245 | #endif 246 | 247 | #ifndef CAYENNE_DISABLE_UNIX_TIME 248 | case LPP_UNIXTIME: 249 | return "time"; 250 | #endif 251 | 252 | #ifndef CAYENNE_DISABLE_GYROMETER 253 | case LPP_GYROMETER: 254 | return "gyrometer"; 255 | #endif 256 | 257 | #ifndef CAYENNE_DISABLE_GPS 258 | case LPP_GPS: 259 | return "gps"; 260 | #endif 261 | 262 | #ifndef CAYENNE_DISABLE_SWITCH 263 | case LPP_SWITCH: 264 | return "switch"; 265 | #endif 266 | 267 | #ifndef CAYENNE_DISABLE_CONCENTRATION 268 | case LPP_CONCENTRATION: 269 | return "concentration"; 270 | #endif 271 | 272 | #ifndef CAYENNE_DISABLE_COLOUR 273 | case LPP_COLOUR: 274 | return "colour"; 275 | #endif 276 | 277 | default: 278 | return nullptr; 279 | } 280 | 281 | } 282 | 283 | uint8_t CayenneLPP::getTypeSize(uint8_t type) { 284 | 285 | switch (type) { 286 | 287 | #ifndef CAYENNE_DISABLE_DIGITAL_INPUT 288 | case LPP_DIGITAL_INPUT: 289 | return LPP_DIGITAL_INPUT_SIZE; 290 | #endif 291 | 292 | #ifndef CAYENNE_DISABLE_DIGITAL_OUTPUT 293 | case LPP_DIGITAL_OUTPUT: 294 | return LPP_DIGITAL_OUTPUT_SIZE; 295 | #endif 296 | 297 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 298 | case LPP_ANALOG_INPUT: 299 | return LPP_ANALOG_INPUT_SIZE; 300 | #endif 301 | 302 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 303 | case LPP_ANALOG_OUTPUT: 304 | return LPP_ANALOG_OUTPUT_SIZE; 305 | #endif 306 | 307 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 308 | case LPP_GENERIC_SENSOR: 309 | return LPP_GENERIC_SENSOR_SIZE; 310 | #endif 311 | 312 | #ifndef CAYENNE_DISABLE_LUMINOSITY 313 | case LPP_LUMINOSITY: 314 | return LPP_LUMINOSITY_SIZE; 315 | #endif 316 | 317 | #ifndef CAYENNE_DISABLE_PRESENCE 318 | case LPP_PRESENCE: 319 | return LPP_PRESENCE_SIZE; 320 | #endif 321 | 322 | #ifndef CAYENNE_DISABLE_TEMPERATURE 323 | case LPP_TEMPERATURE: 324 | return LPP_TEMPERATURE_SIZE; 325 | #endif 326 | 327 | #ifndef CAYENNE_DISABLE_RELATIVE_HUMIDITY 328 | case LPP_RELATIVE_HUMIDITY: 329 | return LPP_RELATIVE_HUMIDITY_SIZE; 330 | #endif 331 | 332 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 333 | case LPP_ACCELEROMETER: 334 | return LPP_ACCELEROMETER_SIZE; 335 | #endif 336 | 337 | #ifndef CAYENNE_DISABLE_BAROMETRIC_PRESSUE 338 | case LPP_BAROMETRIC_PRESSURE: 339 | return LPP_BAROMETRIC_PRESSURE_SIZE; 340 | #endif 341 | 342 | #ifndef CAYENNE_DISABLE_VOLTAGE 343 | case LPP_VOLTAGE: 344 | return LPP_VOLTAGE_SIZE; 345 | #endif 346 | 347 | #ifndef CAYENNE_DISABLE_CURRENT 348 | case LPP_CURRENT: 349 | return LPP_CURRENT_SIZE; 350 | #endif 351 | 352 | #ifndef CAYENNE_DISABLE_FREQUENCY 353 | case LPP_FREQUENCY: 354 | return LPP_FREQUENCY_SIZE; 355 | #endif 356 | 357 | #ifndef CAYENNE_DISABLE_PERCENTAGE 358 | case LPP_PERCENTAGE: 359 | return LPP_PERCENTAGE_SIZE; 360 | #endif 361 | 362 | #ifndef CAYENNE_DISABLE_ALTITUDE 363 | case LPP_ALTITUDE: 364 | return LPP_ALTITUDE_SIZE; 365 | #endif 366 | 367 | #ifndef CAYENNE_DISABLE_POWER 368 | case LPP_POWER: 369 | return LPP_POWER_SIZE; 370 | #endif 371 | 372 | #ifndef CAYENNE_DISABLE_DISTANCE 373 | case LPP_DISTANCE: 374 | return LPP_DISTANCE_SIZE; 375 | #endif 376 | 377 | #ifndef CAYENNE_DISABLE_ENERGY 378 | case LPP_ENERGY: 379 | return LPP_ENERGY_SIZE; 380 | #endif 381 | 382 | #ifndef CAYENNE_DISABLE_DIRECTION 383 | case LPP_DIRECTION: 384 | return LPP_DIRECTION_SIZE; 385 | #endif 386 | 387 | #ifndef CAYENNE_DISABLE_UNIX_TIME 388 | case LPP_UNIXTIME: 389 | return LPP_UNIXTIME_SIZE; 390 | #endif 391 | 392 | #ifndef CAYENNE_DISABLE_GYROMETER 393 | case LPP_GYROMETER: 394 | return LPP_GYROMETER_SIZE; 395 | #endif 396 | 397 | #ifndef CAYENNE_DISABLE_GPS 398 | case LPP_GPS: 399 | return LPP_GPS_SIZE; 400 | #endif 401 | 402 | #ifndef CAYENNE_DISABLE_SWITCH 403 | case LPP_SWITCH: 404 | return LPP_SWITCH_SIZE; 405 | #endif 406 | 407 | #ifndef CAYENNE_DISABLE_CONCENTRATION 408 | case LPP_CONCENTRATION: 409 | return LPP_CONCENTRATION_SIZE; 410 | #endif 411 | 412 | #ifndef CAYENNE_DISABLE_COLOUR 413 | case LPP_COLOUR: 414 | return LPP_COLOUR_SIZE; 415 | #endif 416 | 417 | default: 418 | return 0; 419 | } 420 | } 421 | 422 | uint32_t CayenneLPP::getTypeMultiplier(uint8_t type) { 423 | 424 | switch (type) { 425 | 426 | #ifndef CAYENNE_DISABLE_DIGITAL_INPUT 427 | case LPP_DIGITAL_INPUT: 428 | return LPP_DIGITAL_INPUT_MULT; 429 | #endif 430 | 431 | #ifndef CAYENNE_DISABLE_DIGITAL_OUTPUT 432 | case LPP_DIGITAL_OUTPUT: 433 | return LPP_DIGITAL_OUTPUT_MULT; 434 | #endif 435 | 436 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 437 | case LPP_ANALOG_INPUT: 438 | return LPP_ANALOG_INPUT_MULT; 439 | #endif 440 | 441 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 442 | case LPP_ANALOG_OUTPUT: 443 | return LPP_ANALOG_OUTPUT_MULT; 444 | #endif 445 | 446 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 447 | case LPP_GENERIC_SENSOR: 448 | return LPP_GENERIC_SENSOR_MULT; 449 | #endif 450 | 451 | #ifndef CAYENNE_DISABLE_LUMINOSITY 452 | case LPP_LUMINOSITY: 453 | return LPP_LUMINOSITY_MULT; 454 | #endif 455 | 456 | #ifndef CAYENNE_DISABLE_PRESENCE 457 | case LPP_PRESENCE: 458 | return LPP_PRESENCE_MULT; 459 | #endif 460 | 461 | #ifndef CAYENNE_DISABLE_TEMPERATURE 462 | case LPP_TEMPERATURE: 463 | return LPP_TEMPERATURE_MULT; 464 | #endif 465 | 466 | #ifndef CAYENNE_DISABLE_RELATIVE_HUMIDITY 467 | case LPP_RELATIVE_HUMIDITY: 468 | return LPP_RELATIVE_HUMIDITY_MULT; 469 | #endif 470 | 471 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 472 | case LPP_ACCELEROMETER: 473 | return LPP_ACCELEROMETER_MULT; 474 | #endif 475 | 476 | #ifndef CAYENNE_DISABLE_BAROMETRIC_PRESSUE 477 | case LPP_BAROMETRIC_PRESSURE: 478 | return LPP_BAROMETRIC_PRESSURE_MULT; 479 | #endif 480 | 481 | #ifndef CAYENNE_DISABLE_VOLTAGE 482 | case LPP_VOLTAGE: 483 | return LPP_VOLTAGE_MULT; 484 | #endif 485 | 486 | #ifndef CAYENNE_DISABLE_CURRENT 487 | case LPP_CURRENT: 488 | return LPP_CURRENT_MULT; 489 | #endif 490 | 491 | #ifndef CAYENNE_DISABLE_FREQUENCY 492 | case LPP_FREQUENCY: 493 | return LPP_FREQUENCY_MULT; 494 | #endif 495 | 496 | #ifndef CAYENNE_DISABLE_PERCENTAGE 497 | case LPP_PERCENTAGE: 498 | return LPP_PERCENTAGE_MULT; 499 | #endif 500 | 501 | #ifndef CAYENNE_DISABLE_ALTITUDE 502 | case LPP_ALTITUDE: 503 | return LPP_ALTITUDE_MULT; 504 | #endif 505 | 506 | #ifndef CAYENNE_DISABLE_POWER 507 | case LPP_POWER: 508 | return LPP_POWER_MULT; 509 | #endif 510 | 511 | #ifndef CAYENNE_DISABLE_DISTANCE 512 | case LPP_DISTANCE: 513 | return LPP_DISTANCE_MULT; 514 | #endif 515 | 516 | #ifndef CAYENNE_DISABLE_ENERGY 517 | case LPP_ENERGY: 518 | return LPP_ENERGY_MULT; 519 | #endif 520 | 521 | #ifndef CAYENNE_DISABLE_DIRECTION 522 | case LPP_DIRECTION: 523 | return LPP_DIRECTION_MULT; 524 | #endif 525 | 526 | #ifndef CAYENNE_DISABLE_UNIX_TIME 527 | case LPP_UNIXTIME: 528 | return LPP_UNIXTIME_MULT; 529 | #endif 530 | 531 | #ifndef CAYENNE_DISABLE_GYROMETER 532 | case LPP_GYROMETER: 533 | return LPP_GYROMETER_MULT; 534 | #endif 535 | 536 | #ifndef CAYENNE_DISABLE_SWITCH 537 | case LPP_SWITCH: 538 | return LPP_SWITCH_MULT; 539 | #endif 540 | 541 | #ifndef CAYENNE_DISABLE_CONCENTRATION 542 | case LPP_CONCENTRATION: 543 | return LPP_CONCENTRATION_MULT; 544 | #endif 545 | 546 | #ifndef CAYENNE_DISABLE_COLOUR 547 | case LPP_COLOUR: 548 | return LPP_COLOUR_MULT; 549 | #endif 550 | 551 | default: 552 | return 0; 553 | } 554 | } 555 | 556 | bool CayenneLPP::getTypeSigned(uint8_t type) { 557 | 558 | switch (type) { 559 | #ifndef CAYENNE_DISABLE_VOLTAGE 560 | case LPP_VOLTAGE: 561 | #endif 562 | #ifndef CAYENNE_DISABLE_CURRENT 563 | case LPP_CURRENT: 564 | #endif 565 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 566 | case LPP_ANALOG_INPUT: 567 | #endif 568 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 569 | case LPP_ANALOG_OUTPUT: 570 | #endif 571 | #ifndef CAYENNE_DISABLE_TEMPERATURE 572 | case LPP_TEMPERATURE: 573 | #endif 574 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 575 | case LPP_ACCELEROMETER: 576 | #endif 577 | #ifndef CAYENNE_DISABLE_ALTITUDE 578 | case LPP_ALTITUDE: 579 | #endif 580 | #ifndef CAYENNE_DISABLE_GYROMETER 581 | case LPP_GYROMETER: 582 | #endif 583 | #ifndef CAYENNE_DISABLE_GPS 584 | case LPP_GPS: 585 | #endif 586 | return true; 587 | } 588 | return false; 589 | } 590 | 591 | // ---------------------------------------------------------------------------- 592 | 593 | template uint8_t CayenneLPP::addField(uint8_t type, uint8_t channel, T value) { 594 | 595 | // Check type 596 | if (!isType(type)) { 597 | _error = LPP_ERROR_UNKOWN_TYPE; 598 | return 0; 599 | } 600 | 601 | // Type definition 602 | uint8_t size = getTypeSize(type); 603 | uint32_t multiplier = getTypeMultiplier(type); 604 | bool is_signed = getTypeSigned(type); 605 | 606 | // check buffer overflow 607 | if ((_cursor + size + 2) > _maxsize) { 608 | _error = LPP_ERROR_OVERFLOW; 609 | return 0; 610 | } 611 | 612 | // check sign 613 | bool sign = value < 0; 614 | if (sign) value = -value; 615 | 616 | // get value to store 617 | uint32_t v = value * multiplier; 618 | 619 | // format an uint32_t as if it was an int32_t 620 | if (is_signed & sign) { 621 | uint32_t mask = (1 << (size * 8)) - 1; 622 | v = v & mask; 623 | if (sign) v = mask - v + 1; 624 | } 625 | 626 | // header 627 | _buffer[_cursor++] = channel; 628 | _buffer[_cursor++] = type; 629 | 630 | // add bytes (MSB first) 631 | for (uint8_t i=1; i<=size; i++) { 632 | _buffer[_cursor + size - i] = (v & 0xFF); 633 | v >>= 8; 634 | } 635 | 636 | // update & return _cursor 637 | _cursor += size; 638 | return _cursor; 639 | 640 | } 641 | 642 | #ifndef CAYENNE_DISABLE_DIGITAL_INPUT 643 | uint8_t CayenneLPP::addDigitalInput(uint8_t channel, uint32_t value) { 644 | return addField(LPP_DIGITAL_INPUT, channel, value); 645 | } 646 | #endif 647 | 648 | #ifndef CAYENNE_DISABLE_DIGITAL_OUTPUT 649 | uint8_t CayenneLPP::addDigitalOutput(uint8_t channel, uint32_t value) { 650 | return addField(LPP_DIGITAL_OUTPUT, channel, value); 651 | } 652 | #endif 653 | 654 | #ifndef CAYENNE_DISABLE_ANALOG_INPUT 655 | uint8_t CayenneLPP::addAnalogInput(uint8_t channel, float value) { 656 | return addField(LPP_ANALOG_INPUT, channel, value); 657 | } 658 | #endif 659 | 660 | #ifndef CAYENNE_DISABLE_ANALOG_OUTPUT 661 | uint8_t CayenneLPP::addAnalogOutput(uint8_t channel, float value) { 662 | return addField(LPP_ANALOG_OUTPUT, channel, value); 663 | } 664 | #endif 665 | 666 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 667 | uint8_t CayenneLPP::addGenericSensor(uint8_t channel, float value) { 668 | return addField(LPP_GENERIC_SENSOR, channel, value); 669 | } 670 | #endif 671 | 672 | #ifndef CAYENNE_DISABLE_LUMINOSITY 673 | uint8_t CayenneLPP::addLuminosity(uint8_t channel, uint32_t value) { 674 | return addField(LPP_LUMINOSITY, channel, value); 675 | } 676 | #endif 677 | 678 | #ifndef CAYENNE_DISABLE_PRESENCE 679 | uint8_t CayenneLPP::addPresence(uint8_t channel, uint32_t value) { 680 | return addField(LPP_PRESENCE, channel, value); 681 | } 682 | #endif 683 | 684 | #ifndef CAYENNE_DISABLE_TEMPERATURE 685 | uint8_t CayenneLPP::addTemperature(uint8_t channel, float value) { 686 | return addField(LPP_TEMPERATURE, channel, value); 687 | } 688 | #endif 689 | 690 | #ifndef CAYENNE_DISABLE_RELATIVE_HUMIDITY 691 | uint8_t CayenneLPP::addRelativeHumidity(uint8_t channel, float value) { 692 | return addField(LPP_RELATIVE_HUMIDITY, channel, value); 693 | } 694 | #endif 695 | 696 | #ifndef CAYENNE_DISABLE_VOLTAGE 697 | uint8_t CayenneLPP::addVoltage(uint8_t channel, float value) { 698 | return addField(LPP_VOLTAGE, channel, value); 699 | } 700 | #endif 701 | 702 | #ifndef CAYENNE_DISABLE_CURRENT 703 | uint8_t CayenneLPP::addCurrent(uint8_t channel, float value) { 704 | return addField(LPP_CURRENT, channel, value); 705 | } 706 | #endif 707 | 708 | #ifndef CAYENNE_DISABLE_FREQUENCY 709 | uint8_t CayenneLPP::addFrequency(uint8_t channel, uint32_t value) { 710 | return addField(LPP_FREQUENCY, channel, value); 711 | } 712 | #endif 713 | 714 | #ifndef CAYENNE_DISABLE_PERCENTAGE 715 | uint8_t CayenneLPP::addPercentage(uint8_t channel, uint32_t value) { 716 | return addField(LPP_PERCENTAGE, channel, value); 717 | } 718 | #endif 719 | 720 | #ifndef CAYENNE_DISABLE_ALTITUDE 721 | uint8_t CayenneLPP::addAltitude(uint8_t channel, float value) { 722 | return addField(LPP_ALTITUDE, channel, value); 723 | } 724 | #endif 725 | 726 | #ifndef CAYENNE_DISABLE_POWER 727 | uint8_t CayenneLPP::addPower(uint8_t channel, float value) { 728 | return addField(LPP_POWER, channel, value); 729 | } 730 | #endif 731 | 732 | #ifndef CAYENNE_DISABLE_DISTANCE 733 | uint8_t CayenneLPP::addDistance(uint8_t channel, float value) { 734 | return addField(LPP_DISTANCE, channel, value); 735 | } 736 | #endif 737 | 738 | #ifndef CAYENNE_DISABLE_ENERGY 739 | uint8_t CayenneLPP::addEnergy(uint8_t channel, float value) { 740 | return addField(LPP_ENERGY, channel, value); 741 | } 742 | #endif 743 | 744 | #ifndef CAYENNE_DISABLE_BAROMETRIC_PRESSUE 745 | uint8_t CayenneLPP::addBarometricPressure(uint8_t channel, float value) { 746 | return addField(LPP_BAROMETRIC_PRESSURE, channel, value); 747 | } 748 | #endif 749 | 750 | #ifndef CAYENNE_DISABLE_UNIX_TIME 751 | uint8_t CayenneLPP::addUnixTime(uint8_t channel, uint32_t value) { 752 | return addField(LPP_UNIXTIME, channel, value); 753 | } 754 | #endif 755 | 756 | #ifndef CAYENNE_DISABLE_DIRECTION 757 | uint8_t CayenneLPP::addDirection(uint8_t channel, float value) { 758 | return addField(LPP_DIRECTION, channel, value); 759 | } 760 | #endif 761 | 762 | #ifndef CAYENNE_DISABLE_SWITCH 763 | uint8_t CayenneLPP::addSwitch(uint8_t channel, uint32_t value) { 764 | return addField(LPP_SWITCH, channel, value); 765 | } 766 | #endif 767 | 768 | #ifndef CAYENNE_DISABLE_CONCENTRATION 769 | uint8_t CayenneLPP::addConcentration(uint8_t channel, uint32_t value) { 770 | return addField(LPP_CONCENTRATION, channel, value); 771 | } 772 | #endif 773 | 774 | #ifndef CAYENNE_DISABLE_COLOUR 775 | uint8_t CayenneLPP::addColour(uint8_t channel, uint8_t r, uint8_t g, uint8_t b) 776 | { 777 | // check buffer overflow 778 | if ((_cursor + LPP_COLOUR_SIZE + 2) > _maxsize) { 779 | _error = LPP_ERROR_OVERFLOW; 780 | return 0; 781 | } 782 | _buffer[_cursor++] = channel; 783 | _buffer[_cursor++] = LPP_COLOUR; 784 | _buffer[_cursor++] = r; 785 | _buffer[_cursor++] = g; 786 | _buffer[_cursor++] = b; 787 | 788 | 789 | return _cursor; 790 | } 791 | #endif 792 | 793 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 794 | uint8_t CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) { 795 | 796 | // check buffer overflow 797 | if ((_cursor + LPP_ACCELEROMETER_SIZE + 2) > _maxsize) { 798 | _error = LPP_ERROR_OVERFLOW; 799 | return 0; 800 | } 801 | 802 | int16_t vx = x * LPP_ACCELEROMETER_MULT; 803 | int16_t vy = y * LPP_ACCELEROMETER_MULT; 804 | int16_t vz = z * LPP_ACCELEROMETER_MULT; 805 | 806 | _buffer[_cursor++] = channel; 807 | _buffer[_cursor++] = LPP_ACCELEROMETER; 808 | _buffer[_cursor++] = vx >> 8; 809 | _buffer[_cursor++] = vx; 810 | _buffer[_cursor++] = vy >> 8; 811 | _buffer[_cursor++] = vy; 812 | _buffer[_cursor++] = vz >> 8; 813 | _buffer[_cursor++] = vz; 814 | 815 | return _cursor; 816 | 817 | } 818 | #endif 819 | 820 | #ifndef CAYENNE_DISABLE_GYROMETER 821 | uint8_t CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) { 822 | 823 | // check buffer overflow 824 | if ((_cursor + LPP_GYROMETER_SIZE + 2) > _maxsize) { 825 | _error = LPP_ERROR_OVERFLOW; 826 | return 0; 827 | } 828 | 829 | int16_t vx = x * LPP_GYROMETER_MULT; 830 | int16_t vy = y * LPP_GYROMETER_MULT; 831 | int16_t vz = z * LPP_GYROMETER_MULT; 832 | 833 | _buffer[_cursor++] = channel; 834 | _buffer[_cursor++] = LPP_GYROMETER; 835 | _buffer[_cursor++] = vx >> 8; 836 | _buffer[_cursor++] = vx; 837 | _buffer[_cursor++] = vy >> 8; 838 | _buffer[_cursor++] = vy; 839 | _buffer[_cursor++] = vz >> 8; 840 | _buffer[_cursor++] = vz; 841 | 842 | return _cursor; 843 | } 844 | #endif 845 | 846 | #ifndef CAYENNE_DISABLE_GPS 847 | uint8_t CayenneLPP::addGPS(uint8_t channel, float latitude, float longitude, float altitude) { 848 | 849 | // check buffer overflow 850 | if ((_cursor + LPP_GPS_SIZE + 2) > _maxsize) { 851 | _error = LPP_ERROR_OVERFLOW; 852 | return 0; 853 | } 854 | 855 | int32_t lat = latitude * LPP_GPS_LAT_LON_MULT; 856 | int32_t lon = longitude * LPP_GPS_LAT_LON_MULT; 857 | int32_t alt = altitude * LPP_GPS_ALT_MULT; 858 | 859 | _buffer[_cursor++] = channel; 860 | _buffer[_cursor++] = LPP_GPS; 861 | _buffer[_cursor++] = lat >> 16; 862 | _buffer[_cursor++] = lat >> 8; 863 | _buffer[_cursor++] = lat; 864 | _buffer[_cursor++] = lon >> 16; 865 | _buffer[_cursor++] = lon >> 8; 866 | _buffer[_cursor++] = lon; 867 | _buffer[_cursor++] = alt >> 16; 868 | _buffer[_cursor++] = alt >> 8; 869 | _buffer[_cursor++] = alt; 870 | 871 | return _cursor; 872 | 873 | } 874 | #endif 875 | 876 | 877 | #ifndef ARDUINO 878 | uint8_t CayenneLPP::addPolyline(uint8_t channel, 879 | const std::vector>& coords, 880 | CayenneLPPPolyline::Precision precision, 881 | CayenneLPPPolyline::Simplification simplification) { 882 | 883 | // check buffer overflow for minimum size 884 | if ((_cursor + LPP_MIN_POLYLINE_SIZE + 2) > _maxsize) { 885 | _error = LPP_ERROR_OVERFLOW; 886 | return 0; 887 | } 888 | 889 | // encode coordinates 890 | auto buffer = _polyline.encode(coords, precision, simplification); 891 | 892 | // check buffer overflow for encoded size 893 | if ((_cursor + buffer.size() + 2) > _maxsize) { 894 | _error = LPP_ERROR_OVERFLOW; 895 | return 0; 896 | } 897 | 898 | _buffer[_cursor++] = channel; 899 | _buffer[_cursor++] = LPP_POLYLINE; 900 | std::memcpy(_buffer+_cursor, buffer.data(), buffer.size()); 901 | _cursor += buffer.size(); 902 | 903 | return _cursor; 904 | } 905 | #endif 906 | 907 | // ---------------------------------------------------------------------------- 908 | 909 | float CayenneLPP::getValue(uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) { 910 | 911 | uint32_t value = 0; 912 | for (uint8_t i=0; i len) { 967 | _error = LPP_ERROR_OVERFLOW; 968 | return 0; 969 | } 970 | 971 | // Init object 972 | JsonObject data = root.add(); 973 | data["channel"] = channel; 974 | data["type"] = type; 975 | #ifdef ARDUINO 976 | data["name"] = String(getTypeName(type)); 977 | #else 978 | data["name"] = std::string(getTypeName(type)); 979 | #endif 980 | 981 | // Parse types 982 | if (false) { 983 | } 984 | #ifndef CAYENNE_DISABLE_COLOUR 985 | else if (LPP_COLOUR == type) { 986 | 987 | JsonObject object = data["value"].to(); 988 | object["r"] = getValue(&buffer[index], 1, multiplier, is_signed); 989 | object["g"] = getValue(&buffer[index+1], 1, multiplier, is_signed); 990 | object["b"] = getValue(&buffer[index+2], 1, multiplier, is_signed); 991 | 992 | } 993 | #endif 994 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 995 | else if (LPP_ACCELEROMETER == type) { 996 | JsonObject object = data["value"].to(); 997 | object["x"] = getValue(&buffer[index], 2, multiplier, is_signed); 998 | object["y"] = getValue(&buffer[index+2], 2, multiplier, is_signed); 999 | object["z"] = getValue(&buffer[index+4], 2, multiplier, is_signed); 1000 | } 1001 | #endif 1002 | #ifndef CAYENNE_DISABLE_GYROMETER 1003 | else if (LPP_GYROMETER == type) { 1004 | JsonObject object = data["value"].to(); 1005 | object["x"] = getValue(&buffer[index], 2, multiplier, is_signed); 1006 | object["y"] = getValue(&buffer[index+2], 2, multiplier, is_signed); 1007 | object["z"] = getValue(&buffer[index+4], 2, multiplier, is_signed); 1008 | } 1009 | #endif 1010 | #ifndef CAYENNE_DISABLE_GPS 1011 | else if (LPP_GPS == type) { 1012 | JsonObject object = data["value"].to(); 1013 | object["latitude"] = getValue(&buffer[index], 3, 10000, is_signed); 1014 | object["longitude"] = getValue(&buffer[index+3], 3, 10000, is_signed); 1015 | object["altitude"] = getValue(&buffer[index+6], 3, 100, is_signed); 1016 | } 1017 | #endif 1018 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 1019 | else if (LPP_GENERIC_SENSOR == type) { 1020 | data["value"] = getValue32(&buffer[index], size); 1021 | } 1022 | #endif 1023 | #ifndef CAYENNE_DISABLE_UNIX_TIME 1024 | else if (LPP_UNIXTIME == type) { 1025 | data["value"] = getValue32(&buffer[index], size); 1026 | } 1027 | #endif 1028 | else { 1029 | data["value"] = getValue(&buffer[index], size, multiplier, is_signed); 1030 | } 1031 | 1032 | index += size; 1033 | 1034 | } 1035 | 1036 | return count; 1037 | 1038 | } 1039 | 1040 | uint8_t CayenneLPP::decodeTTN(uint8_t *buffer, uint8_t len, JsonObject& root) { 1041 | 1042 | uint8_t count = 0; 1043 | uint8_t index = 0; 1044 | 1045 | while ((index + 2) < len) { 1046 | 1047 | count++; 1048 | 1049 | // Get channel # 1050 | uint8_t channel = buffer[index++]; 1051 | 1052 | // Get data type 1053 | uint8_t type = buffer[index++]; 1054 | if (!isType(type)) { 1055 | _error = LPP_ERROR_UNKOWN_TYPE; 1056 | return 0; 1057 | } 1058 | 1059 | // Type definition 1060 | uint8_t size = getTypeSize(type); 1061 | uint32_t multiplier = getTypeMultiplier(type); 1062 | bool is_signed = getTypeSigned(type); 1063 | 1064 | // Check buffer size 1065 | if (index + size > len) { 1066 | _error = LPP_ERROR_OVERFLOW; 1067 | return 0; 1068 | } 1069 | 1070 | // Init object 1071 | #ifdef ARDUINO 1072 | String name = String(getTypeName(type)) + "_" + channel; 1073 | #else 1074 | std::string name = std::string(getTypeName(type)) + "_" + std::to_string(channel); 1075 | #endif 1076 | 1077 | // Parse types 1078 | if (false) { 1079 | } 1080 | #ifndef CAYENNE_DISABLE_COLOUR 1081 | else if (LPP_COLOUR == type) { 1082 | JsonObject object = root[name].to(); 1083 | object["r"] = getValue(&buffer[index], 1, multiplier, is_signed); 1084 | object["g"] = getValue(&buffer[index+1], 1, multiplier, is_signed); 1085 | object["b"] = getValue(&buffer[index+2], 1, multiplier, is_signed); 1086 | 1087 | } 1088 | #endif 1089 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 1090 | else if (LPP_ACCELEROMETER == type) { 1091 | 1092 | JsonObject object = root[name].to(); 1093 | object["x"] = getValue(&buffer[index], 2, multiplier, is_signed); 1094 | object["y"] = getValue(&buffer[index+2], 2, multiplier, is_signed); 1095 | object["z"] = getValue(&buffer[index+4], 2, multiplier, is_signed); 1096 | 1097 | } 1098 | #endif 1099 | #ifndef CAYENNE_DISABLE_GYROMETER 1100 | else if (LPP_GYROMETER == type) { 1101 | 1102 | JsonObject object = root[name].to(); 1103 | object["x"] = getValue(&buffer[index], 2, multiplier, is_signed); 1104 | object["y"] = getValue(&buffer[index+2], 2, multiplier, is_signed); 1105 | object["z"] = getValue(&buffer[index+4], 2, multiplier, is_signed); 1106 | 1107 | } 1108 | #endif 1109 | #ifndef CAYENNE_DISABLE_GPS 1110 | else if (LPP_GPS == type) { 1111 | 1112 | JsonObject object = root[name].to(); 1113 | object["latitude"] = getValue(&buffer[index], 3, 10000, is_signed); 1114 | object["longitude"] = getValue(&buffer[index+3], 3, 10000, is_signed); 1115 | object["altitude"] = getValue(&buffer[index+6], 3, 100, is_signed); 1116 | 1117 | } 1118 | #endif 1119 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 1120 | else if (LPP_GENERIC_SENSOR == type) { 1121 | root[name] = getValue32(&buffer[index], size); 1122 | } 1123 | #endif 1124 | #ifndef CAYENNE_DISABLE_UNIX_TIME 1125 | else if (LPP_UNIXTIME == type) { 1126 | root[name] = getValue32(&buffer[index], size); 1127 | } 1128 | #endif 1129 | else { 1130 | 1131 | root[name] = getValue(&buffer[index], size, multiplier, is_signed); 1132 | 1133 | } 1134 | 1135 | index += size; 1136 | 1137 | } 1138 | 1139 | return count; 1140 | 1141 | } 1142 | #endif 1143 | // Non Arduino frameworks 1144 | #ifndef ARDUINO 1145 | uint8_t CayenneLPP::decode(uint8_t *buffer, uint8_t len, std::map &messageMap) { 1146 | 1147 | uint8_t count = 0; 1148 | uint8_t index = 0; 1149 | 1150 | while ((index + 2) < len) { 1151 | 1152 | count++; 1153 | 1154 | // Get channel # 1155 | uint8_t channel = buffer[index++]; 1156 | 1157 | // Get data type 1158 | uint8_t type = buffer[index++]; 1159 | if (!isType(type)) { 1160 | _error = LPP_ERROR_UNKOWN_TYPE; 1161 | return 0; 1162 | } 1163 | 1164 | // Type definition 1165 | uint8_t size = getTypeSize(type); 1166 | uint32_t multiplier = getTypeMultiplier(type); 1167 | bool is_signed = getTypeSigned(type); 1168 | 1169 | // Check buffer size 1170 | if (index + size > len) { 1171 | _error = LPP_ERROR_OVERFLOW; 1172 | return 0; 1173 | } 1174 | 1175 | // Parse types 1176 | switch (type) { 1177 | case LPP_DIGITAL_INPUT: 1178 | messageMap[channel].digitalInput = getValue(&buffer[index], size, multiplier, is_signed); 1179 | break; 1180 | case LPP_DIGITAL_OUTPUT: 1181 | messageMap[channel].digitalOutput = getValue(&buffer[index], size, multiplier, is_signed); 1182 | break; 1183 | case LPP_ANALOG_INPUT: 1184 | messageMap[channel].analogInput = getValue(&buffer[index], size, multiplier, is_signed); 1185 | break; 1186 | case LPP_ANALOG_OUTPUT: 1187 | messageMap[channel].analogOutput = getValue(&buffer[index], size, multiplier, is_signed); 1188 | break; 1189 | case LPP_LUMINOSITY: 1190 | messageMap[channel].luminosity = getValue(&buffer[index], size, multiplier, is_signed); 1191 | break; 1192 | case LPP_PRESENCE: 1193 | messageMap[channel].presence = getValue(&buffer[index], size, multiplier, is_signed); 1194 | break; 1195 | case LPP_TEMPERATURE: 1196 | messageMap[channel].temperature = getValue(&buffer[index], size, multiplier, is_signed); 1197 | break; 1198 | case LPP_RELATIVE_HUMIDITY: 1199 | messageMap[channel].relativeHumidity = getValue(&buffer[index], size, multiplier, is_signed); 1200 | break; 1201 | case LPP_BAROMETRIC_PRESSURE: 1202 | messageMap[channel].barometricPressure = getValue(&buffer[index], size, multiplier, is_signed); 1203 | break; 1204 | case LPP_VOLTAGE: 1205 | messageMap[channel].voltage = getValue(&buffer[index], size, multiplier, is_signed); 1206 | break; 1207 | case LPP_CURRENT: 1208 | messageMap[channel].current = getValue(&buffer[index], size, multiplier, is_signed); 1209 | break; 1210 | case LPP_FREQUENCY: 1211 | messageMap[channel].frequency = getValue(&buffer[index], size, multiplier, is_signed); 1212 | break; 1213 | case LPP_PERCENTAGE: 1214 | messageMap[channel].percentage = getValue(&buffer[index], size, multiplier, is_signed); 1215 | break; 1216 | case LPP_ALTITUDE: 1217 | messageMap[channel].altitude = getValue(&buffer[index], size, multiplier, is_signed); 1218 | break; 1219 | case LPP_CONCENTRATION: 1220 | messageMap[channel].concentration = getValue(&buffer[index], size, multiplier, is_signed); 1221 | break; 1222 | case LPP_POWER: 1223 | messageMap[channel].power = getValue(&buffer[index], size, multiplier, is_signed); 1224 | break; 1225 | case LPP_DISTANCE: 1226 | messageMap[channel].distance = getValue(&buffer[index], size, multiplier, is_signed); 1227 | break; 1228 | case LPP_ENERGY: 1229 | messageMap[channel].energy = getValue(&buffer[index], size, multiplier, is_signed); 1230 | break; 1231 | case LPP_DIRECTION: 1232 | messageMap[channel].direction = getValue(&buffer[index], size, multiplier, is_signed); 1233 | break; 1234 | case LPP_SWITCH: 1235 | messageMap[channel].onOffSwitch = getValue(&buffer[index], size, multiplier, is_signed); 1236 | break; 1237 | 1238 | #ifndef CAYENNE_DISABLE_COLOUR 1239 | case LPP_COLOUR: 1240 | messageMap[channel].colour[0] = getValue(&buffer[index], 1, multiplier, is_signed); 1241 | messageMap[channel].colour[1] = getValue(&buffer[index+1], 1, multiplier, is_signed); 1242 | messageMap[channel].colour[2] = getValue(&buffer[index+2], 1, multiplier, is_signed); 1243 | break; 1244 | #endif 1245 | #ifndef CAYENNE_DISABLE_ACCELEROMETER 1246 | case LPP_ACCELEROMETER: 1247 | messageMap[channel].accelerometer[0] = getValue(&buffer[index], 2, multiplier, is_signed); 1248 | messageMap[channel].accelerometer[1] = getValue(&buffer[index+2], 2, multiplier, is_signed); 1249 | messageMap[channel].accelerometer[2] = getValue(&buffer[index+4], 2, multiplier, is_signed); 1250 | break; 1251 | #endif 1252 | #ifndef CAYENNE_DISABLE_GYROMETER 1253 | case LPP_GYROMETER: 1254 | messageMap[channel].gyrometer[0] = getValue(&buffer[index], 2, multiplier, is_signed); 1255 | messageMap[channel].gyrometer[1] = getValue(&buffer[index+2], 2, multiplier, is_signed); 1256 | messageMap[channel].gyrometer[2] = getValue(&buffer[index+4], 2, multiplier, is_signed); 1257 | break; 1258 | #endif 1259 | #ifndef CAYENNE_DISABLE_GPS 1260 | case LPP_GPS: 1261 | messageMap[channel].gps[0] = getValue(&buffer[index], 3, 10000, is_signed); 1262 | messageMap[channel].gps[1] = getValue(&buffer[index+3], 3, 10000, is_signed); 1263 | messageMap[channel].gps[2] = getValue(&buffer[index+6], 3, 100, is_signed); 1264 | break; 1265 | #endif 1266 | #ifndef CAYENNE_DISABLE_GENERIC_SENSOR 1267 | case LPP_GENERIC_SENSOR: 1268 | messageMap[channel].genericSensor = getValue32(&buffer[index], size); 1269 | break; 1270 | #endif 1271 | #ifndef CAYENNE_DISABLE_UNIX_TIME 1272 | case LPP_UNIXTIME: 1273 | messageMap[channel].unixTime = getValue32(&buffer[index], size); 1274 | break; 1275 | #endif 1276 | #ifndef ARDUINO 1277 | case LPP_POLYLINE: { 1278 | size = buffer[index]; 1279 | const std::vector buffer2(&buffer[index], &buffer[index] + size); 1280 | messageMap[channel].polyline = _polyline.decode(buffer2); 1281 | break; 1282 | } 1283 | #endif 1284 | default: 1285 | return 0; 1286 | break; 1287 | } 1288 | 1289 | index += size; 1290 | 1291 | } 1292 | 1293 | return count; 1294 | 1295 | } 1296 | #endif 1297 | -------------------------------------------------------------------------------- /test/catch2/LppPolylineTest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CayenneLPP - Catch2 Unit Tests 3 | * 4 | * Copyright (C) 2021 by Manuel Weichselbaumer 5 | * 6 | * Use of this source code is governed by the MIT license that can be found in the LICENSE file. 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | using Expectations = std::vector>; 20 | using SampleData = std::vector; 21 | 22 | const SampleData sampleData1 { 23 | { 48.501959, 11.501984 }, 24 | { 48.502972, 11.501884 }, 25 | { 48.503043, 11.501878 }, 26 | { 48.503052, 11.50226 }, 27 | { 48.502984, 11.502808 }, 28 | { 48.503126, 11.502846 }, 29 | { 48.503536, 11.503009 }, 30 | { 48.50367, 11.503083 }, 31 | { 48.503744, 11.503146 }, 32 | { 48.503797, 11.50318 }, 33 | { 48.503889, 11.503215 }, 34 | { 48.504481, 11.503381 }, 35 | { 48.5045, 11.503395 }, 36 | { 48.504521, 11.503433 }, 37 | { 48.504525, 11.503455 }, 38 | { 48.504523, 11.503504 }, 39 | { 48.504518, 11.503635 }, 40 | { 48.504553, 11.503733 }, 41 | { 48.505365, 11.50313 }, 42 | { 48.505567, 11.503016 }, 43 | { 48.505773, 11.502945 }, 44 | { 48.505804, 11.502937 }, 45 | { 48.506076, 11.502894 }, 46 | { 48.506272, 11.502901 }, 47 | { 48.506465, 11.502936 }, 48 | { 48.506553, 11.502961 }, 49 | { 48.506965, 11.503111 }, 50 | { 48.507336, 11.503289 }, 51 | { 48.507363, 11.503301 }, 52 | { 48.507517, 11.502853 }, 53 | { 48.50776, 11.502969 }, 54 | { 48.507998, 11.503067 }, 55 | { 48.50827, 11.503152 }, 56 | { 48.508522, 11.503222 }, 57 | { 48.508919, 11.503308 }, 58 | { 48.50947, 11.503318 }, 59 | { 48.50964, 11.503317 }, 60 | { 48.509682, 11.503818 }, 61 | { 48.509697, 11.503929 }, 62 | { 48.509785, 11.503895 }, 63 | { 48.510027, 11.503791 }, 64 | { 48.510153, 11.503729 }, 65 | { 48.511002, 11.503243 }, 66 | { 48.511108, 11.503177 }, 67 | { 48.511171, 11.503154 }, 68 | { 48.511277, 11.503121 }, 69 | { 48.511413, 11.50309 }, 70 | { 48.511736, 11.503055 }, 71 | { 48.511791, 11.503049 }, 72 | { 48.512226, 11.503024 }, 73 | { 48.512507, 11.503022 }, 74 | { 48.513184, 11.503176 }, 75 | { 48.513938, 11.503301 }, 76 | { 48.514023, 11.503335 }, 77 | { 48.514049, 11.503351 }, 78 | { 48.514101, 11.503383 }, 79 | { 48.514272, 11.503459 }, 80 | { 48.514347, 11.503485 }, 81 | { 48.514502, 11.503541 }, 82 | { 48.514477, 11.503678 }, 83 | { 48.514458, 11.503825 }, 84 | { 48.514368, 11.504809 }, 85 | { 48.514345, 11.505073 }, 86 | { 48.514335, 11.505192 }, 87 | { 48.514313, 11.505438 }, 88 | { 48.514302, 11.505566 }, 89 | { 48.514295, 11.505699 }, 90 | { 48.514284, 11.506058 }, 91 | { 48.514299, 11.506227 }, 92 | { 48.514348, 11.506747 }, 93 | { 48.514372, 11.507706 }, 94 | { 48.514355, 11.508547 }, 95 | { 48.51444, 11.508559 }, 96 | { 48.514995, 11.508741 }, 97 | { 48.515595, 11.508932 }, 98 | { 48.516207, 11.509173 }, 99 | { 48.516658, 11.509386 }, 100 | { 48.516895, 11.50952 }, 101 | { 48.517121, 11.509701 }, 102 | { 48.517447, 11.51 }, 103 | { 48.517815, 11.510317 }, 104 | { 48.518054, 11.51045 }, 105 | { 48.518414, 11.510556 }, 106 | { 48.518684, 11.510625 }, 107 | { 48.518851, 11.510668 }, 108 | { 48.519037, 11.510718 }, 109 | { 48.519229, 11.510769 }, 110 | { 48.519391, 11.510805 }, 111 | { 48.519699, 11.510875 }, 112 | { 48.519842, 11.510908 }, 113 | { 48.520026, 11.510947 }, 114 | { 48.520149, 11.510973 }, 115 | { 48.520452, 11.511031 }, 116 | { 48.520547, 11.511048 }, 117 | { 48.520655, 11.511067 }, 118 | { 48.520955, 11.511095 }, 119 | { 48.521696, 11.511101 }, 120 | { 48.522108, 11.511088 }, 121 | { 48.522254, 11.511083 }, 122 | { 48.522253, 11.511211 }, 123 | { 48.522253, 11.511323 }, 124 | { 48.522272, 11.511329 }, 125 | { 48.522308, 11.511356 }, 126 | { 48.522322, 11.511377 }, 127 | { 48.522419, 11.511382 }, 128 | { 48.522495, 11.511386 }, 129 | { 48.522488, 11.511416 }, 130 | { 48.522474, 11.511628 }, 131 | { 48.522474, 11.511676 }, 132 | { 48.52247, 11.511714 }, 133 | { 48.522434, 11.512065 }, 134 | { 48.522432, 11.512089 }, 135 | { 48.522549, 11.512102 }, 136 | { 48.522549, 11.512121 }, 137 | { 48.522539, 11.5123 }, 138 | { 48.522538, 11.512343 }, 139 | { 48.522541, 11.512377 }, 140 | { 48.522581, 11.51248 }, 141 | { 48.522614, 11.512552 }, 142 | { 48.522645, 11.512529 }, 143 | { 48.522812, 11.512563 }, 144 | { 48.522883, 11.512537 }, 145 | { 48.523051, 11.512574 }, 146 | { 48.523074, 11.512561 }, 147 | { 48.523186, 11.512669 }, 148 | { 48.523211, 11.512602 }, 149 | { 48.523243, 11.512628 }, 150 | { 48.523292, 11.512647 }, 151 | { 48.523344, 11.512632 }, 152 | { 48.52336, 11.512619 }, 153 | { 48.523439, 11.512679 }, 154 | { 48.523744, 11.512765 }, 155 | { 48.523906, 11.512775 }, 156 | { 48.524379, 11.512782 }, 157 | { 48.52474, 11.512802 }, 158 | { 48.525188, 11.512865 }, 159 | { 48.525419, 11.512963 }, 160 | { 48.525516, 11.513005 }, 161 | { 48.52571, 11.51299 }, 162 | { 48.52581, 11.51299 }, 163 | { 48.525902, 11.512999 }, 164 | { 48.526049, 11.513021 }, 165 | { 48.526081, 11.513031 }, 166 | { 48.526103, 11.513038 }, 167 | { 48.526228, 11.513091 }, 168 | { 48.526374, 11.5132 }, 169 | { 48.526449, 11.513274 }, 170 | { 48.526549, 11.513382 }, 171 | { 48.526581, 11.51342 }, 172 | { 48.526639, 11.513495 }, 173 | { 48.526719, 11.513583 }, 174 | { 48.526769, 11.513611 }, 175 | { 48.52675, 11.513725 }, 176 | { 48.52675, 11.513959 }, 177 | { 48.526807, 11.513961 }, 178 | { 48.526842, 11.513963 }, 179 | { 48.526899, 11.513969 }, 180 | { 48.526902, 11.513866 }, 181 | { 48.526924, 11.513669 }, 182 | { 48.526957, 11.513686 }, 183 | { 48.527553, 11.514128 }, 184 | { 48.528623, 11.515066 }, 185 | { 48.528847, 11.515222 }, 186 | { 48.52939, 11.515421 }, 187 | { 48.530046, 11.515665 }, 188 | { 48.530186, 11.515732 }, 189 | { 48.530249, 11.515766 }, 190 | { 48.530275, 11.51578 }, 191 | { 48.530499, 11.515935 }, 192 | { 48.530563, 11.516002 }, 193 | { 48.530697, 11.51623 }, 194 | { 48.530736, 11.516309 }, 195 | { 48.530892, 11.516607 }, 196 | { 48.531101, 11.517036 }, 197 | { 48.531124, 11.517081 }, 198 | { 48.53116, 11.517131 }, 199 | { 48.531216, 11.517196 }, 200 | { 48.531258, 11.517237 }, 201 | { 48.531422, 11.517327 }, 202 | { 48.531568, 11.51736 }, 203 | { 48.531589, 11.517363 }, 204 | { 48.531735, 11.517385 }, 205 | { 48.531764, 11.517389 }, 206 | { 48.53185, 11.517396 }, 207 | { 48.531893, 11.517403 }, 208 | { 48.531934, 11.517416 }, 209 | { 48.53196, 11.517437 }, 210 | { 48.531993, 11.517486 }, 211 | { 48.532012, 11.517568 }, 212 | { 48.531993, 11.518085 }, 213 | { 48.531985, 11.51818 }, 214 | { 48.531977, 11.518268 }, 215 | { 48.53197, 11.5192 }, 216 | { 48.531976, 11.519266 }, 217 | { 48.531999, 11.51931 }, 218 | { 48.532008, 11.519317 }, 219 | { 48.532045, 11.519321 }, 220 | { 48.532083, 11.519325 }, 221 | { 48.532234, 11.519332 }, 222 | { 48.532427, 11.519342 }, 223 | { 48.532465, 11.519344 }, 224 | { 48.532467, 11.519327 }, 225 | { 48.532522, 11.519336 }, 226 | { 48.532502, 11.519586 }, 227 | { 48.532488, 11.520043 }, 228 | { 48.532494, 11.520272 }, 229 | { 48.532512, 11.520898 }, 230 | { 48.532523, 11.521236 }, 231 | { 48.532495, 11.521695 }, 232 | { 48.532503, 11.521806 }, 233 | { 48.532561, 11.522048 }, 234 | { 48.532681, 11.522452 }, 235 | { 48.532765, 11.522688 }, 236 | { 48.53283, 11.522902 }, 237 | { 48.532993, 11.523574 }, 238 | { 48.533051, 11.523736 }, 239 | { 48.533088, 11.523813 }, 240 | { 48.533307, 11.524174 }, 241 | { 48.53334, 11.524248 }, 242 | { 48.533399, 11.524496 }, 243 | { 48.533425, 11.524633 }, 244 | { 48.53348, 11.524881 }, 245 | { 48.533664, 11.525571 }, 246 | { 48.533954, 11.526317 }, 247 | { 48.534009, 11.526478 }, 248 | { 48.534046, 11.526568 }, 249 | { 48.534092, 11.5267 }, 250 | { 48.534208, 11.526954 }, 251 | { 48.534274, 11.527072 }, 252 | { 48.534437, 11.527225 }, 253 | { 48.534634, 11.527279 }, 254 | { 48.53498, 11.527325 }, 255 | { 48.535069, 11.527378 }, 256 | { 48.535209, 11.527572 }, 257 | { 48.535304, 11.52778 }, 258 | { 48.535327, 11.527844 }, 259 | { 48.535344, 11.52791 }, 260 | { 48.535374, 11.528082 }, 261 | { 48.535344, 11.528621 }, 262 | { 48.535333, 11.529239 }, 263 | { 48.535321, 11.529427 }, 264 | { 48.53523, 11.529921 }, 265 | { 48.535207, 11.530124 }, 266 | { 48.535207, 11.530225 }, 267 | { 48.535215, 11.530325 }, 268 | { 48.535272, 11.530652 }, 269 | { 48.535416, 11.531326 }, 270 | { 48.535426, 11.531433 }, 271 | { 48.535386, 11.531747 }, 272 | { 48.535413, 11.532104 }, 273 | { 48.535488, 11.532672 }, 274 | { 48.535605, 11.533276 }, 275 | { 48.535603, 11.533338 }, 276 | { 48.535528, 11.533527 }, 277 | { 48.535366, 11.533911 }, 278 | { 48.535278, 11.534094 }, 279 | { 48.535137, 11.534324 }, 280 | { 48.535016, 11.534505 }, 281 | { 48.534858, 11.534784 }, 282 | { 48.534776, 11.534979 }, 283 | { 48.534744, 11.535085 }, 284 | { 48.534721, 11.535201 }, 285 | { 48.534706, 11.535283 }, 286 | { 48.534692, 11.535377 }, 287 | { 48.534666, 11.535824 }, 288 | { 48.534646, 11.536132 }, 289 | { 48.534625, 11.536568 }, 290 | { 48.534633, 11.536698 }, 291 | { 48.534708, 11.536979 }, 292 | { 48.534795, 11.537248 }, 293 | { 48.534922, 11.537521 }, 294 | { 48.535072, 11.537852 }, 295 | { 48.535234, 11.538167 }, 296 | { 48.535539, 11.538621 }, 297 | { 48.535809, 11.539014 }, 298 | { 48.535922, 11.53921 }, 299 | { 48.536523, 11.540643 }, 300 | { 48.536876, 11.541297 }, 301 | { 48.537349, 11.542173 }, 302 | { 48.537507, 11.542486 }, 303 | { 48.537644, 11.542475 }, 304 | { 48.538147, 11.542479 }, 305 | { 48.538199, 11.542529 }, 306 | { 48.538279, 11.542671 }, 307 | { 48.538329, 11.542698 }, 308 | { 48.539064, 11.542768 }, 309 | { 48.539583, 11.542748 }, 310 | { 48.540001, 11.542976 }, 311 | { 48.540123, 11.543107 }, 312 | { 48.540143, 11.543209 }, 313 | { 48.5402, 11.54337 }, 314 | { 48.540253, 11.543443 }, 315 | { 48.540531, 11.543596 }, 316 | { 48.541004, 11.543779 }, 317 | { 48.541241, 11.543932 }, 318 | { 48.541315, 11.544095 }, 319 | { 48.541324, 11.544235 }, 320 | { 48.541266, 11.544611 }, 321 | { 48.541265, 11.54471 }, 322 | { 48.541282, 11.544788 }, 323 | { 48.541335, 11.544909 }, 324 | { 48.54142, 11.545008 }, 325 | { 48.541513, 11.545071 }, 326 | { 48.541601, 11.545133 }, 327 | { 48.54228, 11.545505 }, 328 | { 48.542317, 11.545538 }, 329 | { 48.542658, 11.545847 }, 330 | { 48.542771, 11.545956 }, 331 | { 48.543504, 11.546672 }, 332 | { 48.543568, 11.546684 }, 333 | { 48.543618, 11.546667 }, 334 | { 48.543589, 11.546979 }, 335 | { 48.543511, 11.547258 }, 336 | { 48.543322, 11.547602 }, 337 | { 48.543013, 11.548892 }, 338 | { 48.542994, 11.54907 }, 339 | { 48.543006, 11.550214 }, 340 | { 48.54302, 11.550348 }, 341 | { 48.543198, 11.550702 }, 342 | { 48.544417, 11.552403 }, 343 | { 48.544522, 11.552581 }, 344 | { 48.544793, 11.553347 }, 345 | { 48.544861, 11.553455 }, 346 | { 48.544938, 11.553633 }, 347 | { 48.54532, 11.554355 }, 348 | { 48.545622, 11.55478 }, 349 | { 48.545849, 11.55573 }, 350 | { 48.545817, 11.555919 }, 351 | { 48.545797, 11.556003 }, 352 | { 48.545687, 11.556278 }, 353 | { 48.545584, 11.556461 }, 354 | { 48.545538, 11.556836 }, 355 | { 48.545531, 11.557067 }, 356 | { 48.545555, 11.557459 }, 357 | { 48.54561, 11.557634 }, 358 | { 48.545555, 11.557685 }, 359 | { 48.54556, 11.557975 }, 360 | { 48.545553, 11.558459 }, 361 | { 48.545518, 11.55912 }, 362 | { 48.545468, 11.559531 }, 363 | { 48.545548, 11.560298 }, 364 | { 48.545577, 11.560641 }, 365 | { 48.545719, 11.560956 }, 366 | { 48.545963, 11.560956 }, 367 | { 48.546261, 11.560819 }, 368 | { 48.546405, 11.560974 }, 369 | { 48.546537, 11.560934 }, 370 | { 48.546657, 11.560923 }, 371 | { 48.546967, 11.56098 }, 372 | { 48.54703, 11.560987 }, 373 | { 48.547229, 11.561123 }, 374 | { 48.547539, 11.560902 }, 375 | { 48.547872, 11.560708 }, 376 | { 48.547942, 11.560674 }, 377 | { 48.547991, 11.560713 }, 378 | { 48.548305, 11.561028 }, 379 | { 48.548593, 11.561074 }, 380 | { 48.548608, 11.561086 }, 381 | { 48.54881, 11.561256 }, 382 | { 48.549135, 11.561529 }, 383 | { 48.549352, 11.561845 }, 384 | { 48.549379, 11.561884 }, 385 | { 48.549521, 11.562133 }, 386 | { 48.54973, 11.562393 }, 387 | { 48.549788, 11.562465 }, 388 | { 48.55003, 11.562826 }, 389 | { 48.550518, 11.562853 }, 390 | { 48.551132, 11.563126 }, 391 | { 48.551777, 11.563454 }, 392 | { 48.552018, 11.563577 }, 393 | { 48.552695, 11.564696 }, 394 | { 48.552732, 11.565051 }, 395 | { 48.552894, 11.565338 }, 396 | { 48.552985, 11.56539 }, 397 | { 48.553653, 11.565775 }, 398 | { 48.554277, 11.565747 }, 399 | { 48.554332, 11.565734 }, 400 | { 48.554774, 11.565624 }, 401 | { 48.554842, 11.56589 }, 402 | { 48.554953, 11.566108 }, 403 | { 48.554897, 11.56646 }, 404 | { 48.554808, 11.566862 }, 405 | { 48.554698, 11.567248 }, 406 | { 48.554786, 11.567683 }, 407 | { 48.555052, 11.568438 }, 408 | { 48.555095, 11.56855 }, 409 | { 48.555197, 11.568823 }, 410 | { 48.55533, 11.569057 }, 411 | { 48.555485, 11.569208 }, 412 | { 48.555607, 11.569409 }, 413 | { 48.555962, 11.57013 }, 414 | { 48.556051, 11.570549 }, 415 | { 48.55615, 11.570884 }, 416 | { 48.556206, 11.571487 }, 417 | { 48.556416, 11.572358 }, 418 | { 48.556494, 11.572861 }, 419 | { 48.556494, 11.57318 }, 420 | { 48.556439, 11.573515 }, 421 | { 48.556416, 11.573783 }, 422 | { 48.556528, 11.574252 }, 423 | { 48.556661, 11.574772 }, 424 | { 48.556435, 11.574839 }, 425 | { 48.556262, 11.574906 }, 426 | { 48.55611, 11.575043 }, 427 | { 48.555963, 11.575325 }, 428 | { 48.555972, 11.575768 }, 429 | { 48.556323, 11.576834 }, 430 | { 48.556443, 11.577638 }, 431 | { 48.556377, 11.57786 }, 432 | { 48.556266, 11.578007 }, 433 | { 48.55638, 11.578554 }, 434 | { 48.556659, 11.579293 }, 435 | { 48.556899, 11.580041 }, 436 | { 48.557053, 11.58012 }, 437 | { 48.557391, 11.580052 }, 438 | { 48.557651, 11.580035 }, 439 | { 48.55774, 11.580123 }, 440 | { 48.557838, 11.580658 }, 441 | { 48.558123, 11.581371 }, 442 | { 48.558379, 11.581999 }, 443 | { 48.558468, 11.582222 }, 444 | { 48.558442, 11.582548 }, 445 | { 48.558365, 11.582937 }, 446 | { 48.558357, 11.582975 }, 447 | { 48.558032, 11.583584 }, 448 | { 48.557895, 11.583996 }, 449 | { 48.557657, 11.584903 }, 450 | { 48.557394, 11.585511 }, 451 | { 48.557332, 11.585652 }, 452 | { 48.557409, 11.586773 }, 453 | { 48.557448, 11.586832 }, 454 | { 48.558631, 11.587062 }, 455 | { 48.55877, 11.587186 }, 456 | { 48.558747, 11.587995 }, 457 | { 48.558748, 11.588082 }, 458 | { 48.558704, 11.588894 }, 459 | { 48.558649, 11.589196 }, 460 | { 48.558485, 11.58972 }, 461 | { 48.558427, 11.590139 }, 462 | { 48.558397, 11.590681 }, 463 | { 48.558308, 11.591181 }, 464 | { 48.557954, 11.591929 }, 465 | { 48.557796, 11.592419 }, 466 | { 48.557729, 11.592788 }, 467 | { 48.557595, 11.593371 }, 468 | { 48.557392, 11.594636 }, 469 | { 48.557306, 11.594975 }, 470 | { 48.557249, 11.595184 }, 471 | { 48.557139, 11.595554 }, 472 | { 48.55698, 11.595874 }, 473 | { 48.556675, 11.596121 }, 474 | { 48.556127, 11.59658 }, 475 | { 48.555801, 11.596793 }, 476 | { 48.555617, 11.596813 }, 477 | { 48.555429, 11.596761 }, 478 | { 48.555378, 11.596887 }, 479 | { 48.555301, 11.596969 }, 480 | { 48.55515, 11.597014 }, 481 | { 48.554649, 11.597011 }, 482 | { 48.554718, 11.597421 }, 483 | { 48.554721, 11.597756 }, 484 | { 48.5547, 11.598094 }, 485 | { 48.554635, 11.59857 }, 486 | { 48.554597, 11.598786 }, 487 | { 48.554576, 11.599189 }, 488 | { 48.554583, 11.599524 }, 489 | { 48.554475, 11.599559 }, 490 | { 48.554476, 11.5996 }, 491 | { 48.554569, 11.599716 }, 492 | { 48.554693, 11.600164 }, 493 | { 48.554936, 11.600793 }, 494 | { 48.55549, 11.602053 }, 495 | { 48.555961, 11.603059 }, 496 | { 48.556193, 11.603596 }, 497 | { 48.556411, 11.604203 }, 498 | { 48.55656, 11.604742 }, 499 | { 48.55664, 11.605095 }, 500 | { 48.556733, 11.605425 }, 501 | { 48.556862, 11.605846 }, 502 | { 48.557124, 11.606474 }, 503 | { 48.557284, 11.606812 }, 504 | { 48.557529, 11.607265 }, 505 | { 48.557901, 11.607866 }, 506 | { 48.558501, 11.608751 }, 507 | { 48.559533, 11.609973 }, 508 | { 48.55992, 11.610498 }, 509 | { 48.560604, 11.611535 }, 510 | { 48.56153, 11.613049 }, 511 | { 48.561582, 11.613121 }, 512 | { 48.561539, 11.613241 }, 513 | { 48.561425, 11.613699 }, 514 | { 48.561354, 11.613935 }, 515 | { 48.561288, 11.614192 }, 516 | { 48.561286, 11.61433 }, 517 | { 48.561301, 11.614446 }, 518 | { 48.561368, 11.614638 }, 519 | { 48.561507, 11.61494 }, 520 | { 48.561644, 11.615204 }, 521 | { 48.561799, 11.615468 }, 522 | { 48.561954, 11.615671 }, 523 | { 48.562311, 11.616152 }, 524 | { 48.56277, 11.617507 }, 525 | { 48.56296, 11.617979 }, 526 | { 48.563043, 11.618147 }, 527 | { 48.563177, 11.618388 }, 528 | { 48.563296, 11.618582 }, 529 | { 48.563537, 11.618809 }, 530 | { 48.564534, 11.619984 }, 531 | { 48.56475, 11.620263 }, 532 | { 48.564736, 11.62051 }, 533 | { 48.56476, 11.620672 }, 534 | { 48.564606, 11.621224 }, 535 | { 48.564762, 11.621266 }, 536 | { 48.564978, 11.621385 }, 537 | { 48.56566, 11.622359 }, 538 | { 48.565724, 11.62239 }, 539 | { 48.565926, 11.622462 }, 540 | { 48.566101, 11.622492 }, 541 | { 48.566128, 11.622615 }, 542 | { 48.566494, 11.623098 }, 543 | { 48.566522, 11.623334 }, 544 | { 48.566315, 11.623946 }, 545 | { 48.566254, 11.62418 }, 546 | { 48.565797, 11.625272 }, 547 | { 48.565464, 11.626188 }, 548 | { 48.565263, 11.626986 }, 549 | { 48.565388, 11.627062 }, 550 | { 48.56554, 11.627205 }, 551 | { 48.565921, 11.627788 }, 552 | { 48.56606, 11.627962 }, 553 | { 48.566168, 11.628029 }, 554 | { 48.56624, 11.628073 }, 555 | { 48.566407, 11.628108 }, 556 | { 48.566446, 11.628086 }, 557 | { 48.566498, 11.628224 }, 558 | { 48.566749, 11.628581 }, 559 | { 48.566985, 11.628748 }, 560 | { 48.567072, 11.628843 }, 561 | { 48.567293, 11.629411 }, 562 | { 48.567505, 11.629879 }, 563 | { 48.567543, 11.629963 }, 564 | { 48.567429, 11.630258 }, 565 | { 48.567155, 11.631036 }, 566 | { 48.567006, 11.631763 }, 567 | { 48.56675, 11.632351 }, 568 | { 48.566703, 11.632484 }, 569 | { 48.566673, 11.632653 }, 570 | { 48.566673, 11.632782 }, 571 | { 48.5667, 11.63294 }, 572 | { 48.566858, 11.633357 }, 573 | { 48.566911, 11.633565 }, 574 | { 48.566933, 11.633985 }, 575 | { 48.566918, 11.634118 }, 576 | { 48.566869, 11.634287 }, 577 | { 48.566712, 11.634611 }, 578 | { 48.566665, 11.634751 }, 579 | { 48.566617, 11.634995 }, 580 | { 48.566611, 11.635117 }, 581 | { 48.566649, 11.635433 }, 582 | { 48.56675, 11.635861 }, 583 | { 48.56695, 11.636451 }, 584 | { 48.567052, 11.63671 }, 585 | { 48.567207, 11.637029 }, 586 | { 48.567574, 11.637955 }, 587 | { 48.567763, 11.638382 }, 588 | { 48.567902, 11.638829 }, 589 | { 48.567959, 11.63903 }, 590 | { 48.568072, 11.639829 }, 591 | { 48.568116, 11.640412 }, 592 | { 48.568119, 11.640792 }, 593 | { 48.56815, 11.641118 }, 594 | { 48.56848, 11.642228 }, 595 | { 48.568675, 11.642574 }, 596 | { 48.56941, 11.643112 }, 597 | { 48.570066, 11.643538 }, 598 | { 48.570389, 11.644151 }, 599 | { 48.570618, 11.644585 }, 600 | { 48.571129, 11.645262 }, 601 | { 48.570262, 11.64747 }, 602 | { 48.569834, 11.648401 }, 603 | { 48.56974, 11.648565 }, 604 | { 48.569582, 11.648881 }, 605 | { 48.569008, 11.650014 }, 606 | { 48.568658, 11.650858 }, 607 | { 48.568387, 11.651639 }, 608 | { 48.568179, 11.652543 }, 609 | { 48.56817, 11.652686 }, 610 | { 48.568304, 11.652774 }, 611 | { 48.568153, 11.6532 }, 612 | { 48.568052, 11.653573 }, 613 | { 48.567999, 11.653841 }, 614 | { 48.567984, 11.65399 }, 615 | { 48.567972, 11.654504 }, 616 | { 48.568029, 11.65515 }, 617 | { 48.568143, 11.655565 }, 618 | { 48.56827, 11.655939 }, 619 | { 48.568371, 11.656341 }, 620 | { 48.568428, 11.656774 }, 621 | { 48.56844, 11.657182 }, 622 | { 48.568404, 11.657604 }, 623 | { 48.56831, 11.658034 }, 624 | { 48.568212, 11.658343 }, 625 | { 48.567999, 11.658943 }, 626 | { 48.567772, 11.659594 }, 627 | { 48.567753, 11.65988 }, 628 | { 48.567753, 11.660309 }, 629 | { 48.567758, 11.661285 }, 630 | { 48.56779, 11.661545 }, 631 | { 48.567843, 11.661784 }, 632 | { 48.567842, 11.662001 }, 633 | { 48.567772, 11.662614 }, 634 | { 48.567632, 11.663547 }, 635 | { 48.567569, 11.664111 }, 636 | { 48.567447, 11.66496 }, 637 | { 48.567422, 11.665304 }, 638 | { 48.567418, 11.665738 }, 639 | { 48.56739, 11.665906 }, 640 | { 48.567273, 11.66618 }, 641 | { 48.566967, 11.666716 }, 642 | { 48.566898, 11.6669 }, 643 | { 48.566838, 11.667261 }, 644 | { 48.5667, 11.667518 }, 645 | { 48.566485, 11.667861 }, 646 | { 48.566186, 11.668387 }, 647 | { 48.565862, 11.669066 }, 648 | { 48.5657, 11.669599 }, 649 | { 48.565653, 11.669771 }, 650 | { 48.565653, 11.669949 }, 651 | { 48.565699, 11.670179 }, 652 | { 48.565668, 11.670381 }, 653 | { 48.565534, 11.670752 }, 654 | { 48.565424, 11.671109 }, 655 | { 48.565282, 11.67136 }, 656 | { 48.565207, 11.671672 }, 657 | { 48.565152, 11.672038 }, 658 | { 48.565076, 11.67219 }, 659 | { 48.56495, 11.672616 }, 660 | { 48.564842, 11.67298 }, 661 | { 48.564824, 11.673112 }, 662 | { 48.564819, 11.673143 }, 663 | { 48.564813, 11.673185 }, 664 | { 48.564747, 11.673556 }, 665 | { 48.564422, 11.675082 }, 666 | { 48.564404, 11.675141 }, 667 | { 48.564352, 11.675318 }, 668 | { 48.564286, 11.675505 }, 669 | { 48.564174, 11.675774 }, 670 | { 48.563988, 11.67631 }, 671 | { 48.563539, 11.677485 }, 672 | { 48.563431, 11.677732 }, 673 | { 48.563358, 11.677834 }, 674 | { 48.563255, 11.677914 }, 675 | { 48.563143, 11.677946 }, 676 | { 48.562847, 11.679022 }, 677 | { 48.562625, 11.679288 }, 678 | { 48.562432, 11.679271 }, 679 | { 48.562017, 11.679151 }, 680 | { 48.561756, 11.678953 }, 681 | { 48.56137, 11.67982 }, 682 | { 48.56129, 11.68007 }, 683 | { 48.561191, 11.680441 }, 684 | { 48.560897, 11.680945 }, 685 | { 48.560837, 11.680969 } 686 | }; 687 | 688 | const Expectations expectations1 { 689 | { CayenneLPPPolyline::Prec0_0001, 0.0001, 686, 477, 439 }, 690 | { CayenneLPPPolyline::Prec0_0002, 0.0002, 560, 283, 238 }, 691 | { CayenneLPPPolyline::Prec0_0005, 0.0005, 402, 176, 120 }, 692 | { CayenneLPPPolyline::Prec0_001, 0.001, 251, 115, 69 }, 693 | { CayenneLPPPolyline::Prec0_002, 0.002, 150, 92, 42 }, 694 | { CayenneLPPPolyline::Prec0_005, 0.005, 68, 46, 20 }, 695 | { CayenneLPPPolyline::Prec0_01, 0.01, 38, 28, 14 } 696 | // { CayenneLPPPolyline::Prec0_02, 0.02, 22, 17, 12 }, 697 | // { CayenneLPPPolyline::Prec0_05, 0.05, 13, 10, 10 }, 698 | // { CayenneLPPPolyline::Prec0_1, 0.1, 11, 11, 9 }, 699 | // { CayenneLPPPolyline::Prec0_2, 0.2, 8, 8, 8 }, 700 | // { CayenneLPPPolyline::Prec0_5, 0.5, 8, 8, 8 }, 701 | // { CayenneLPPPolyline::Prec1_0, 1.0, 8, 8, 8 } 702 | }; 703 | 704 | const SampleData sampleData2 { 705 | { 53.007645, 0.08461 }, 706 | { 53.007374, 0.084953 }, 707 | { 53.007277, 0.084953 }, 708 | { 53.006999, 0.084492 }, 709 | { 53.006883, 0.084106 }, 710 | { 53.007045, 0.083955 }, 711 | { 53.007129, 0.083773 }, 712 | { 53.007144, 0.083697 }, 713 | { 53.00718, 0.083515 }, 714 | { 53.0072, 0.083215 }, 715 | { 53.007116, 0.082571 }, 716 | { 53.006993, 0.082056 }, 717 | { 53.00689, 0.081606 }, 718 | { 53.006819, 0.081144 }, 719 | { 53.006722, 0.080769 }, 720 | { 53.006586, 0.080318 }, 721 | { 53.006554, 0.080018 }, 722 | { 53.00656, 0.079696 }, 723 | { 53.006522, 0.078934 }, 724 | { 53.006393, 0.077475 }, 725 | { 53.006251, 0.076145 }, 726 | { 53.006096, 0.07519 }, 727 | { 53.005915, 0.074364 }, 728 | { 53.005618, 0.072883 }, 729 | { 53.005534, 0.0724 }, 730 | { 53.005547, 0.072261 }, 731 | { 53.006038, 0.071006 }, 732 | { 53.006638, 0.069181 }, 733 | { 53.006658, 0.069127 }, 734 | { 53.006774, 0.068806 }, 735 | { 53.00729, 0.067561 }, 736 | { 53.008071, 0.065952 }, 737 | { 53.008291, 0.065394 }, 738 | { 53.008523, 0.064375 }, 739 | { 53.008601, 0.06415 }, 740 | { 53.008717, 0.063967 }, 741 | { 53.008859, 0.063892 }, 742 | { 53.010595, 0.063613 }, 743 | { 53.010866, 0.063592 }, 744 | { 53.010995, 0.063538 }, 745 | { 53.012357, 0.062777 }, 746 | { 53.012878, 0.062529 }, 747 | { 53.013044, 0.062435 }, 748 | { 53.013141, 0.062242 }, 749 | { 53.013139, 0.062039 }, 750 | { 53.013092, 0.061652 }, 751 | { 53.012892, 0.057856 }, 752 | { 53.012905, 0.057103 }, 753 | { 53.012684, 0.052789 }, 754 | { 53.01258, 0.0514 }, 755 | { 53.012512, 0.051216 }, 756 | { 53.012418, 0.051103 }, 757 | { 53.012245, 0.051125 }, 758 | { 53.01096, 0.051499 }, 759 | { 53.010851, 0.051466 }, 760 | { 53.010771, 0.05142 }, 761 | { 53.010686, 0.05119 }, 762 | { 53.010352, 0.050155 }, 763 | { 53.010089, 0.049186 }, 764 | { 53.009983, 0.048679 }, 765 | { 53.009902, 0.048202 }, 766 | { 53.009812, 0.047117 }, 767 | { 53.00973, 0.046143 }, 768 | { 53.009364, 0.043948 }, 769 | { 53.009041, 0.042564 }, 770 | { 53.008656, 0.041473 }, 771 | { 53.008575, 0.041125 }, 772 | { 53.008484, 0.040529 }, 773 | { 53.008338, 0.039949 }, 774 | { 53.007624, 0.038081 }, 775 | { 53.00747, 0.037475 }, 776 | { 53.007461, 0.037252 }, 777 | { 53.007352, 0.037207 }, 778 | { 53.007357, 0.036993 }, 779 | { 53.007382, 0.036589 }, 780 | { 53.007338, 0.036416 }, 781 | { 53.007237, 0.036218 }, 782 | { 53.007104, 0.036035 }, 783 | { 53.006938, 0.035672 }, 784 | { 53.006713, 0.034901 }, 785 | { 53.006498, 0.033976 }, 786 | { 53.00634, 0.033012 }, 787 | { 53.005977, 0.032057 }, 788 | { 53.005418, 0.031041 }, 789 | { 53.005139, 0.030379 }, 790 | { 53.00471, 0.02918 }, 791 | { 53.004631, 0.028603 }, 792 | { 53.004628, 0.027635 }, 793 | { 53.004549, 0.027261 }, 794 | { 53.004278, 0.026546 }, 795 | { 53.004025, 0.025821 }, 796 | { 53.003934, 0.025406 }, 797 | { 53.003884, 0.024996 }, 798 | { 53.003836, 0.024335 }, 799 | { 53.00383, 0.024236 }, 800 | { 53.003826, 0.024114 }, 801 | { 53.003742, 0.024041 }, 802 | { 53.003705, 0.023998 }, 803 | { 53.003652, 0.023958 }, 804 | { 53.003109, 0.024214 }, 805 | { 53.002428, 0.024418 }, 806 | { 53.002189, 0.02449 }, 807 | { 53.002112, 0.024513 }, 808 | { 53.000724, 0.024902 }, 809 | { 53.00038, 0.024999 }, 810 | { 52.999643, 0.024955 }, 811 | { 52.99919, 0.025013 }, 812 | { 52.998564, 0.025225 }, 813 | { 52.997838, 0.025565 }, 814 | { 52.99498, 0.027034 }, 815 | { 52.994662, 0.027147 }, 816 | { 52.994096, 0.027348 }, 817 | { 52.994007, 0.027435 }, 818 | { 52.993913, 0.02766 }, 819 | { 52.993826, 0.02802 }, 820 | { 52.993748, 0.028338 }, 821 | { 52.993662, 0.028248 }, 822 | { 52.993355, 0.028085 }, 823 | { 52.993301, 0.028102 }, 824 | { 52.993326, 0.028005 }, 825 | { 52.995483, 0.017839 }, 826 | { 52.995504, 0.017606 }, 827 | { 52.995434, 0.017209 }, 828 | { 52.993549, 0.01501 }, 829 | { 52.993517, 0.014974 }, 830 | { 52.992852, 0.014176 }, 831 | { 52.991816, 0.012877 }, 832 | { 52.990825, 0.011378 }, 833 | { 52.99024, 0.010392 }, 834 | { 52.99007, 0.010184 }, 835 | { 52.989845, 0.009991 }, 836 | { 52.989642, 0.009769 }, 837 | { 52.989385, 0.009307 }, 838 | { 52.98917, 0.008883 }, 839 | { 52.989142, 0.008798 }, 840 | { 52.989126, 0.008701 }, 841 | { 52.989008, 0.008661 }, 842 | { 52.988696, 0.008549 }, 843 | { 52.988461, 0.008527 }, 844 | { 52.988267, 0.008528 }, 845 | { 52.987551, 0.008642 }, 846 | { 52.987186, 0.008637 }, 847 | { 52.986912, 0.008503 }, 848 | { 52.986511, 0.00824 }, 849 | { 52.986497, 0.008233 }, 850 | { 52.986622, 0.007557 }, 851 | { 52.98664, 0.007227 }, 852 | { 52.986622, 0.006545 }, 853 | { 52.986663, 0.006025 }, 854 | { 52.986774, 0.005061 }, 855 | { 52.986834, 0.004706 }, 856 | { 52.986924, 0.004157 }, 857 | { 52.987144, 0.002848 }, 858 | { 52.987149, 0.00282 }, 859 | { 52.987304, 0.001927 }, 860 | { 52.987335, 0.00195 }, 861 | { 52.9874, 0.001592 }, 862 | { 52.987417, 0.001499 }, 863 | { 52.987435, 0.001399 }, 864 | { 52.987452, 0.001311 }, 865 | { 52.98746, 0.001275 }, 866 | { 52.98749, 0.000966 }, 867 | { 52.987751, -0.00032 }, 868 | { 52.98811, -0.002033 }, 869 | { 52.988263, -0.00257 }, 870 | { 52.988326, -0.002823 }, 871 | { 52.988338, -0.002865 }, 872 | { 52.988479, -0.003416 }, 873 | { 52.988562, -0.004132 }, 874 | { 52.988582, -0.004204 }, 875 | { 52.988609, -0.004326 }, 876 | { 52.988548, -0.004365 }, 877 | { 52.9885, -0.004394 }, 878 | { 52.988465, -0.004422 }, 879 | { 52.988503, -0.004621 }, 880 | { 52.988431, -0.004824 }, 881 | { 52.988389, -0.005069 }, 882 | { 52.988362, -0.005126 }, 883 | { 52.988317, -0.00515 }, 884 | { 52.987969, -0.005301 }, 885 | { 52.987866, -0.005347 }, 886 | { 52.987843, -0.005382 }, 887 | { 52.987794, -0.005604 }, 888 | { 52.987781, -0.005659 }, 889 | { 52.987768, -0.005717 }, 890 | { 52.987683, -0.00593 }, 891 | { 52.987552, -0.006093 }, 892 | { 52.987509, -0.006122 }, 893 | { 52.987479, -0.006141 }, 894 | { 52.987409, -0.006167 }, 895 | { 52.987335, -0.006171 }, 896 | { 52.987305, -0.006173 }, 897 | { 52.987112, -0.006157 }, 898 | { 52.986966, -0.006229 }, 899 | { 52.986701, -0.006455 }, 900 | { 52.986626, -0.00654 }, 901 | { 52.986486, -0.006374 }, 902 | { 52.986424, -0.006397 }, 903 | { 52.986198, -0.006528 }, 904 | { 52.986022, -0.006574 }, 905 | { 52.98596, -0.006559 }, 906 | { 52.985752, -0.006398 }, 907 | { 52.985512, -0.006099 }, 908 | { 52.985261, -0.005647 }, 909 | { 52.98503, -0.005154 }, 910 | { 52.984962, -0.00496 }, 911 | { 52.98491, -0.004748 }, 912 | { 52.984849, -0.004663 }, 913 | { 52.98476, -0.004662 }, 914 | { 52.984113, -0.004884 }, 915 | { 52.984013, -0.004969 }, 916 | { 52.983974, -0.005047 }, 917 | { 52.983929, -0.005197 }, 918 | { 52.983891, -0.005172 }, 919 | { 52.983851, -0.005167 }, 920 | { 52.98381, -0.005187 }, 921 | { 52.983743, -0.005296 }, 922 | { 52.983687, -0.005509 }, 923 | { 52.983538, -0.006895 }, 924 | { 52.983514, -0.006964 }, 925 | { 52.983491, -0.006999 }, 926 | { 52.983461, -0.007018 }, 927 | { 52.983105, -0.007051 }, 928 | { 52.982883, -0.007076 }, 929 | { 52.982701, -0.007088 }, 930 | { 52.982362, -0.007017 }, 931 | { 52.982297, -0.007019 }, 932 | { 52.982252, -0.007533 }, 933 | { 52.9822, -0.008094 }, 934 | { 52.982109, -0.009158 }, 935 | { 52.982051, -0.009837 }, 936 | { 52.98202, -0.010076 }, 937 | { 52.98197, -0.010164 }, 938 | { 52.981873, -0.01019 }, 939 | { 52.98155, -0.010189 }, 940 | { 52.981103, -0.01018 }, 941 | { 52.981103, -0.009839 }, 942 | { 52.981092, -0.009468 }, 943 | { 52.98095, -0.00948 }, 944 | { 52.980739, -0.009482 }, 945 | { 52.978823, -0.009181 }, 946 | { 52.97819, -0.009081 }, 947 | { 52.978172, -0.009551 }, 948 | { 52.978156, -0.009911 }, 949 | { 52.978052, -0.011381 }, 950 | { 52.977989, -0.011812 }, 951 | { 52.977981, -0.011879 }, 952 | { 52.97781, -0.011965 }, 953 | { 52.977791, -0.011865 }, 954 | { 52.977691, -0.011907 }, 955 | { 52.977243, -0.012075 }, 956 | { 52.976994, -0.012171 }, 957 | { 52.977038, -0.012495 }, 958 | { 52.977097, -0.01294 }, 959 | { 52.977019, -0.012911 }, 960 | { 52.977027, -0.013213 }, 961 | { 52.977033, -0.013353 }, 962 | { 52.976938, -0.013458 }, 963 | { 52.976725, -0.013705 }, 964 | { 52.976622, -0.013832 }, 965 | { 52.976414, -0.014077 }, 966 | { 52.976373, -0.014111 }, 967 | { 52.976465, -0.014375 }, 968 | { 52.976291, -0.014348 }, 969 | { 52.974887, -0.014146 }, 970 | { 52.974873, -0.014138 }, 971 | { 52.97485, -0.014145 }, 972 | { 52.974802, -0.014139 }, 973 | { 52.974789, -0.014137 }, 974 | { 52.974766, -0.014639 }, 975 | { 52.974765, -0.014655 }, 976 | { 52.974771, -0.014663 }, 977 | { 52.97486, -0.014675 }, 978 | { 52.974879, -0.014795 }, 979 | { 52.974965, -0.015228 }, 980 | { 52.974975, -0.015285 }, 981 | { 52.974982, -0.015322 }, 982 | { 52.97512, -0.015986 }, 983 | { 52.97512, -0.016096 }, 984 | { 52.975159, -0.016111 }, 985 | { 52.975144, -0.016541 }, 986 | { 52.975124, -0.017092 }, 987 | { 52.975098, -0.017687 }, 988 | { 52.975023, -0.019437 }, 989 | { 52.97497, -0.020586 }, 990 | { 52.974947, -0.021082 }, 991 | { 52.974904, -0.021072 }, 992 | { 52.974889, -0.02124 }, 993 | { 52.974813, -0.02213 }, 994 | { 52.9748, -0.022305 }, 995 | { 52.974821, -0.022322 }, 996 | { 52.974814, -0.022403 }, 997 | { 52.974792, -0.022578 }, 998 | { 52.974787, -0.022706 }, 999 | { 52.974823, -0.022716 }, 1000 | { 52.974867, -0.02273 }, 1001 | { 52.974878, -0.022734 }, 1002 | { 52.974869, -0.02284 }, 1003 | { 52.974881, -0.022843 }, 1004 | { 52.974912, -0.02285 }, 1005 | { 52.97495, -0.022861 }, 1006 | { 52.974956, -0.022879 }, 1007 | { 52.974985, -0.022944 }, 1008 | { 52.974994, -0.022956 }, 1009 | { 52.974983, -0.023027 }, 1010 | { 52.974976, -0.023073 }, 1011 | { 52.974952, -0.023067 }, 1012 | { 52.974947, -0.023104 }, 1013 | { 52.974973, -0.023108 }, 1014 | { 52.97497, -0.023146 }, 1015 | { 52.974963, -0.02321 }, 1016 | { 52.974958, -0.02321 }, 1017 | { 52.974907, -0.023233 }, 1018 | { 52.974843, -0.023549 }, 1019 | { 52.974787, -0.02381 }, 1020 | { 52.974648, -0.024381 }, 1021 | { 52.974612, -0.024514 }, 1022 | { 52.974569, -0.024726 }, 1023 | { 52.974553, -0.024812 }, 1024 | { 52.974504, -0.024831 }, 1025 | { 52.974455, -0.025134 }, 1026 | { 52.974408, -0.025104 }, 1027 | { 52.97443, -0.024971 }, 1028 | { 52.974388, -0.024953 }, 1029 | { 52.97432, -0.024947 }, 1030 | { 52.974308, -0.025006 }, 1031 | { 52.974287, -0.024976 }, 1032 | { 52.974224, -0.02494 }, 1033 | { 52.974022, -0.025008 }, 1034 | { 52.973998, -0.025017 }, 1035 | { 52.973866, -0.02505 }, 1036 | { 52.973781, -0.025059 }, 1037 | { 52.973728, -0.025047 }, 1038 | { 52.973621, -0.025005 }, 1039 | { 52.973424, -0.024917 }, 1040 | { 52.973153, -0.024851 }, 1041 | { 52.97301, -0.024831 }, 1042 | { 52.972795, -0.02481 }, 1043 | { 52.972584, -0.02481 }, 1044 | { 52.972473, -0.024833 }, 1045 | { 52.972199, -0.024904 }, 1046 | { 52.972033, -0.024953 }, 1047 | { 52.971988, -0.024972 }, 1048 | { 52.971961, -0.024984 }, 1049 | { 52.971873, -0.025034 }, 1050 | { 52.971584, -0.02524 }, 1051 | { 52.971199, -0.02552 }, 1052 | { 52.97086, -0.025807 }, 1053 | { 52.970787, -0.025869 }, 1054 | { 52.970605, -0.026011 }, 1055 | { 52.97044, -0.026125 }, 1056 | { 52.970304, -0.026199 }, 1057 | { 52.969837, -0.02633 }, 1058 | { 52.969592, -0.02636 }, 1059 | { 52.969541, -0.026369 }, 1060 | { 52.969053, -0.026451 }, 1061 | { 52.968528, -0.026504 }, 1062 | { 52.968385, -0.026459 }, 1063 | { 52.968045, -0.026274 }, 1064 | { 52.967944, -0.026228 }, 1065 | { 52.967938, -0.026283 }, 1066 | { 52.967927, -0.026375 }, 1067 | { 52.967858, -0.026373 }, 1068 | { 52.967838, -0.026372 }, 1069 | { 52.967395, -0.026176 }, 1070 | { 52.967375, -0.02616 }, 1071 | { 52.96731, -0.026143 }, 1072 | { 52.967213, -0.026116 }, 1073 | { 52.967193, -0.026114 }, 1074 | { 52.967129, -0.02611 }, 1075 | { 52.966755, -0.026247 }, 1076 | { 52.966526, -0.026345 }, 1077 | { 52.966501, -0.026266 }, 1078 | { 52.966423, -0.026287 }, 1079 | { 52.966227, -0.026292 }, 1080 | { 52.966158, -0.026304 }, 1081 | { 52.966135, -0.026381 }, 1082 | { 52.96611, -0.026398 }, 1083 | { 52.966087, -0.026408 }, 1084 | { 52.966067, -0.026415 }, 1085 | { 52.966039, -0.026382 }, 1086 | { 52.965997, -0.026414 }, 1087 | { 52.965935, -0.026512 }, 1088 | { 52.965774, -0.026927 }, 1089 | { 52.965663, -0.027136 }, 1090 | { 52.965581, -0.02727 }, 1091 | { 52.965532, -0.0273 }, 1092 | { 52.965456, -0.027315 }, 1093 | { 52.965291, -0.027312 }, 1094 | { 52.965199, -0.027291 }, 1095 | { 52.965073, -0.027262 }, 1096 | { 52.964579, -0.027165 }, 1097 | { 52.964136, -0.027063 }, 1098 | { 52.964062, -0.027023 }, 1099 | { 52.963972, -0.026992 }, 1100 | { 52.963945, -0.026873 }, 1101 | { 52.963896, -0.026813 }, 1102 | { 52.963833, -0.026785 }, 1103 | { 52.963887, -0.02658 }, 1104 | { 52.963803, -0.026522 }, 1105 | { 52.963686, -0.026734 }, 1106 | { 52.963573, -0.026834 }, 1107 | { 52.963058, -0.026987 }, 1108 | { 52.962886, -0.026999 }, 1109 | { 52.962759, -0.027011 }, 1110 | { 52.962321, -0.027053 }, 1111 | { 52.961802, -0.027166 }, 1112 | { 52.961761, -0.027149 }, 1113 | { 52.961388, -0.027223 }, 1114 | { 52.961341, -0.027388 }, 1115 | { 52.96084, -0.027542 }, 1116 | { 52.960466, -0.027697 }, 1117 | { 52.959588, -0.028166 }, 1118 | { 52.959539, -0.028167 }, 1119 | { 52.95942, -0.028268 }, 1120 | { 52.959388, -0.028287 }, 1121 | { 52.95939, -0.028297 }, 1122 | { 52.958568, -0.028863 }, 1123 | { 52.958136, -0.029199 }, 1124 | { 52.958115, -0.029221 }, 1125 | { 52.958123, -0.029254 }, 1126 | { 52.958117, -0.029258 }, 1127 | { 52.958124, -0.029284 }, 1128 | { 52.958134, -0.029318 }, 1129 | { 52.958138, -0.02933 }, 1130 | { 52.958142, -0.029345 }, 1131 | { 52.958152, -0.029376 }, 1132 | { 52.958159, -0.029401 }, 1133 | { 52.957909, -0.029594 }, 1134 | { 52.957725, -0.029778 }, 1135 | { 52.957686, -0.0299 }, 1136 | { 52.957686, -0.029907 }, 1137 | { 52.957659, -0.029925 }, 1138 | { 52.95764, -0.029938 }, 1139 | { 52.957632, -0.029944 }, 1140 | { 52.957621, -0.029953 }, 1141 | { 52.957566, -0.030019 }, 1142 | { 52.957561, -0.030029 }, 1143 | { 52.957536, -0.03008 }, 1144 | { 52.957451, -0.03017 }, 1145 | { 52.957436, -0.030182 }, 1146 | { 52.957413, -0.0302 }, 1147 | { 52.957152, -0.0304 }, 1148 | { 52.956942, -0.030594 }, 1149 | { 52.95606, -0.031288 }, 1150 | { 52.955937, -0.031346 }, 1151 | { 52.95586, -0.031413 }, 1152 | { 52.955723, -0.031566 }, 1153 | { 52.955605, -0.031764 }, 1154 | { 52.955586, -0.031852 }, 1155 | { 52.95552, -0.031866 }, 1156 | { 52.955422, -0.031971 }, 1157 | { 52.955375, -0.032028 }, 1158 | { 52.955335, -0.031936 }, 1159 | { 52.955304, -0.031896 }, 1160 | { 52.955275, -0.031897 }, 1161 | { 52.954981, -0.032123 }, 1162 | { 52.954835, -0.032267 }, 1163 | { 52.95479, -0.032384 }, 1164 | { 52.954054, -0.032969 }, 1165 | { 52.953804, -0.033139 }, 1166 | { 52.953648, -0.033064 }, 1167 | { 52.953309, -0.033256 }, 1168 | { 52.953308, -0.033456 }, 1169 | { 52.952632, -0.033942 }, 1170 | { 52.951727, -0.034651 }, 1171 | { 52.951627, -0.034685 }, 1172 | { 52.951651, -0.034827 }, 1173 | { 52.95174, -0.035406 }, 1174 | { 52.951794, -0.03576 }, 1175 | { 52.951838, -0.036127 }, 1176 | { 52.951876, -0.036481 }, 1177 | { 52.951901, -0.03679 }, 1178 | { 52.951921, -0.037042 }, 1179 | { 52.951953, -0.037282 }, 1180 | { 52.952027, -0.037791 }, 1181 | { 52.952092, -0.038129 }, 1182 | { 52.95249, -0.039545 }, 1183 | { 52.952611, -0.040138 }, 1184 | { 52.952633, -0.040385 }, 1185 | { 52.952648, -0.041463 }, 1186 | { 52.952773, -0.041495 }, 1187 | { 52.952814, -0.04154 }, 1188 | { 52.952908, -0.041881 }, 1189 | { 52.952942, -0.041944 }, 1190 | { 52.952991, -0.04197 }, 1191 | { 52.953037, -0.041952 }, 1192 | { 52.953001, -0.042147 }, 1193 | { 52.953014, -0.04218 }, 1194 | { 52.952903, -0.042334 }, 1195 | { 52.952772, -0.042513 }, 1196 | { 52.952455, -0.042939 }, 1197 | { 52.9524, -0.042992 }, 1198 | { 52.952342, -0.042871 }, 1199 | { 52.952243, -0.04297 }, 1200 | { 52.95214, -0.043098 }, 1201 | { 52.952099, -0.043209 }, 1202 | { 52.952015, -0.043316 }, 1203 | { 52.951837, -0.043453 }, 1204 | { 52.951628, -0.043726 }, 1205 | { 52.951416, -0.044002 }, 1206 | { 52.951374, -0.044071 }, 1207 | { 52.951344, -0.04413 }, 1208 | { 52.951291, -0.044235 }, 1209 | { 52.950563, -0.045177 }, 1210 | { 52.950448, -0.045324 }, 1211 | { 52.950414, -0.04532 }, 1212 | { 52.950367, -0.045398 }, 1213 | { 52.950339, -0.045683 }, 1214 | { 52.949712, -0.046417 }, 1215 | { 52.948554, -0.047668 }, 1216 | { 52.947477, -0.049091 }, 1217 | { 52.94738, -0.049187 }, 1218 | { 52.94731, -0.049334 }, 1219 | { 52.946651, -0.050203 }, 1220 | { 52.9465, -0.050379 }, 1221 | { 52.946243, -0.050605 }, 1222 | { 52.946044, -0.050761 }, 1223 | { 52.945535, -0.051147 }, 1224 | { 52.944455, -0.052037 }, 1225 | { 52.944232, -0.052252 }, 1226 | { 52.943214, -0.053419 }, 1227 | { 52.941565, -0.055079 }, 1228 | { 52.941208, -0.055366 }, 1229 | { 52.940673, -0.055739 }, 1230 | { 52.94, -0.056176 }, 1231 | { 52.939642, -0.056355 }, 1232 | { 52.93959, -0.05648 }, 1233 | { 52.939032, -0.056781 }, 1234 | { 52.938494, -0.05707 }, 1235 | { 52.937865, -0.057351 }, 1236 | { 52.937139, -0.057773 }, 1237 | { 52.936872, -0.057329 }, 1238 | { 52.936654, -0.05699 }, 1239 | { 52.936375, -0.056551 }, 1240 | { 52.935909, -0.055848 }, 1241 | { 52.935398, -0.054951 }, 1242 | { 52.934825, -0.054095 }, 1243 | { 52.934633, -0.053743 }, 1244 | { 52.934327, -0.05299 }, 1245 | { 52.933422, -0.054261 }, 1246 | { 52.933313, -0.0544 }, 1247 | { 52.933209, -0.054503 }, 1248 | { 52.933162, -0.05453 }, 1249 | { 52.933078, -0.054578 }, 1250 | { 52.932847, -0.054629 }, 1251 | { 52.932729, -0.054619 }, 1252 | { 52.932601, -0.054576 }, 1253 | { 52.932466, -0.054479 }, 1254 | { 52.932131, -0.054206 }, 1255 | { 52.931795, -0.053907 }, 1256 | { 52.931531, -0.053711 }, 1257 | { 52.931116, -0.053446 }, 1258 | { 52.93091, -0.053331 }, 1259 | { 52.930515, -0.053038 }, 1260 | { 52.930467, -0.05302 }, 1261 | { 52.930373, -0.052984 }, 1262 | { 52.930264, -0.053018 }, 1263 | { 52.930195, -0.053081 }, 1264 | { 52.930037, -0.053321 }, 1265 | { 52.929849, -0.053522 }, 1266 | { 52.929787, -0.05356 }, 1267 | { 52.929716, -0.053602 }, 1268 | { 52.929588, -0.053704 }, 1269 | { 52.929505, -0.0538 }, 1270 | { 52.929358, -0.054068 }, 1271 | { 52.929288, -0.054149 }, 1272 | { 52.929052, -0.054334 }, 1273 | { 52.92895, -0.054366 }, 1274 | { 52.92885, -0.054358 }, 1275 | { 52.928701, -0.054281 }, 1276 | { 52.928639, -0.054293 }, 1277 | { 52.928565, -0.054329 }, 1278 | { 52.928534, -0.05441 }, 1279 | { 52.928491, -0.054588 }, 1280 | { 52.928453, -0.054679 }, 1281 | { 52.928156, -0.054977 }, 1282 | { 52.927959, -0.055109 }, 1283 | { 52.927811, -0.055243 }, 1284 | { 52.927378, -0.055682 }, 1285 | { 52.927583, -0.056132 }, 1286 | { 52.927285, -0.056459 }, 1287 | { 52.927256, -0.056558 }, 1288 | { 52.927077, -0.056891 }, 1289 | { 52.92703, -0.05686 }, 1290 | { 52.926975, -0.056869 }, 1291 | { 52.926931, -0.056906 }, 1292 | { 52.926857, -0.057021 }, 1293 | { 52.926887, -0.057104 }, 1294 | { 52.926738, -0.057372 }, 1295 | { 52.92666, -0.057576 }, 1296 | { 52.926544, -0.057705 }, 1297 | { 52.926317, -0.057276 }, 1298 | { 52.925739, -0.058086 }, 1299 | { 52.925095, -0.059247 }, 1300 | { 52.925029, -0.059325 }, 1301 | { 52.924956, -0.059365 }, 1302 | { 52.924833, -0.059389 }, 1303 | { 52.924708, -0.05943 }, 1304 | { 52.924598, -0.059534 }, 1305 | { 52.924514, -0.059638 }, 1306 | { 52.924461, -0.059818 }, 1307 | { 52.924309, -0.060372 }, 1308 | { 52.924241, -0.060654 }, 1309 | { 52.92418, -0.060814 }, 1310 | { 52.924104, -0.060916 }, 1311 | { 52.92401, -0.06098 }, 1312 | { 52.923627, -0.06102 }, 1313 | { 52.923536, -0.061074 }, 1314 | { 52.923323, -0.061331 }, 1315 | { 52.922829, -0.061602 }, 1316 | { 52.922947, -0.062292 }, 1317 | { 52.923058, -0.06278 }, 1318 | { 52.923125, -0.063015 } 1319 | }; 1320 | 1321 | const Expectations expectations2 { 1322 | { CayenneLPPPolyline::Prec0_0001, 0.0001, 664, 491, 439 }, 1323 | { CayenneLPPPolyline::Prec0_0002, 0.0002, 497, 290, 247 }, 1324 | { CayenneLPPPolyline::Prec0_0005, 0.0005, 327, 153, 111 }, 1325 | { CayenneLPPPolyline::Prec0_001, 0.001, 228, 120, 64 }, 1326 | { CayenneLPPPolyline::Prec0_002, 0.002, 149, 93, 37 }, 1327 | { CayenneLPPPolyline::Prec0_005, 0.005, 67, 45, 25 }, 1328 | { CayenneLPPPolyline::Prec0_01, 0.01, 45, 37, 18 } 1329 | // { CayenneLPPPolyline::Prec0_02, 0.02, 22, 18, 11 }, 1330 | // { CayenneLPPPolyline::Prec0_05, 0.05, 23, 20, 10 }, 1331 | // { CayenneLPPPolyline::Prec0_1, 0.1, 11, 11, 9 }, 1332 | // { CayenneLPPPolyline::Prec0_2, 0.2, 8, 8, 8 }, 1333 | // { CayenneLPPPolyline::Prec0_5, 0.5, 8, 8, 8 }, 1334 | // { CayenneLPPPolyline::Prec1_0, 1.0, 8, 8, 8 } 1335 | }; 1336 | 1337 | TEST_CASE("Coordinates within precision", "[LppPolyline]") { 1338 | std::vector> coords { 1339 | { 12.0, -13.0 }, 1340 | { 12.01, -13.01 } 1341 | }; 1342 | 1343 | CayenneLPPPolyline polyline(255); 1344 | auto buffer = polyline.encode(coords, CayenneLPPPolyline::Prec0_01); 1345 | auto stats = polyline.getEncodeStats(); 1346 | auto out = polyline.decode(buffer); 1347 | 1348 | REQUIRE(buffer.size() == 9); 1349 | REQUIRE(stats.keptCoords == 1); 1350 | REQUIRE(stats.addedCoords == 0); 1351 | REQUIRE(stats.removedCoords == 0); 1352 | REQUIRE(out == coords); 1353 | } 1354 | 1355 | TEST_CASE("Coordinates out of precision", "[LppPolyline]") { 1356 | std::vector> coords { 1357 | { -12.0, -13.0 }, 1358 | { -12.01, -13.01 } 1359 | }; 1360 | 1361 | CayenneLPPPolyline polyline(255); 1362 | auto buffer = polyline.encode(coords, CayenneLPPPolyline::Prec0_02); 1363 | auto stats = polyline.getEncodeStats(); 1364 | auto out = polyline.decode(buffer); 1365 | 1366 | REQUIRE(buffer.size() == 9); 1367 | REQUIRE(stats.keptCoords == 1); 1368 | REQUIRE(stats.addedCoords == 0); 1369 | REQUIRE(stats.removedCoords == 0); 1370 | REQUIRE(out.front() == coords.front()); 1371 | REQUIRE(out.back() == std::pair { -12.02, -13.02 }); 1372 | } 1373 | 1374 | TEST_CASE("Coordinates kept", "[LppPolyline]") { 1375 | std::vector> coords { 1376 | { 12.0, -13.0 }, 1377 | { 12.005, -13.005 } 1378 | }; 1379 | 1380 | CayenneLPPPolyline polyline(255); 1381 | auto buffer = polyline.encode(coords, CayenneLPPPolyline::Prec0_01); 1382 | auto stats = polyline.getEncodeStats(); 1383 | auto out = polyline.decode(buffer); 1384 | 1385 | REQUIRE(buffer.size() == 9); 1386 | REQUIRE(stats.keptCoords == 1); 1387 | REQUIRE(stats.addedCoords == 0); 1388 | REQUIRE(stats.removedCoords == 0); 1389 | REQUIRE(out.size() == 2); 1390 | } 1391 | 1392 | TEST_CASE("Coordinates added", "[LppPolyline]") { 1393 | std::vector> coords { 1394 | { 14.0, 78.0 }, 1395 | { 14.08, 78.08 } 1396 | }; 1397 | 1398 | CayenneLPPPolyline polyline(255); 1399 | auto buffer = polyline.encode(coords, CayenneLPPPolyline::Prec0_01); 1400 | auto stats = polyline.getEncodeStats(); 1401 | auto out = polyline.decode(buffer); 1402 | 1403 | REQUIRE(buffer.size() == 10); 1404 | REQUIRE(stats.keptCoords == 1); 1405 | REQUIRE(stats.addedCoords == 1); 1406 | REQUIRE(stats.removedCoords == 0); 1407 | REQUIRE(out.size() == 3); 1408 | REQUIRE(out.at(1) == std::pair { 14.04, 78.04 }); 1409 | } 1410 | 1411 | TEST_CASE("Coordinates removed", "[LppPolyline]") { 1412 | std::vector> coords { 1413 | { 78.0, -12.0 }, 1414 | { 78.024, -12.024 } 1415 | }; 1416 | 1417 | CayenneLPPPolyline polyline(255); 1418 | auto buffer = polyline.encode(coords, CayenneLPPPolyline::Prec0_05); 1419 | auto stats = polyline.getEncodeStats(); 1420 | auto out = polyline.decode(buffer); 1421 | 1422 | REQUIRE(buffer.size() == 8); 1423 | REQUIRE(stats.keptCoords == 0); 1424 | REQUIRE(stats.addedCoords == 0); 1425 | REQUIRE(stats.removedCoords == 1); 1426 | REQUIRE(out.size() == 1); 1427 | } 1428 | 1429 | TEST_CASE("Encode sample data", "[LppPolyline]") { 1430 | const auto i = GENERATE(std::tuple("sample1", sampleData1, expectations1), 1431 | std::tuple("sample2", sampleData2, expectations2)); 1432 | const auto j = GENERATE(CayenneLPPPolyline::None, 1433 | CayenneLPPPolyline::PerpendicularDistance, 1434 | CayenneLPPPolyline::DouglasPeucker); 1435 | 1436 | CayenneLPPPolyline polyline(65535); 1437 | 1438 | // Iterate test data sets 1439 | for (const auto& exp : std::get<2>(i)) { 1440 | std::vector buffer; 1441 | //BENCHMARK("Encode " + std::get<0>(i) 1442 | // + " with precision " + std::to_string(std::get<1>(exp)) 1443 | // + " and simplification " + std::to_string(j)) { 1444 | buffer = polyline.encode(std::get<1>(i), std::get<0>(exp), j); 1445 | //}; 1446 | //auto stats = polyline.getEncodeStats(); 1447 | //WARN("Polyine(" << std::get<0>(exp.second) << ") kept: " << stats.keptCoords 1448 | // << ", removed: " << stats.removedCoords 1449 | // << ", added: " << stats.addedCoords 1450 | // << " coordinates"); 1451 | //WARN("Size: " << buffer.size()); 1452 | const auto out = polyline.decode(buffer); 1453 | REQUIRE(buffer.size() == ((j == CayenneLPPPolyline::None) ? 1454 | std::get<2>(exp) : (j == CayenneLPPPolyline::PerpendicularDistance) ? 1455 | std::get<3>(exp) : std::get<4>(exp))); 1456 | REQUIRE(abs(std::get<1>(i).front().first - out.front().first) <= std::get<1>(exp) / 2.0); 1457 | REQUIRE(abs(std::get<1>(i).front().second - out.front().second) <= std::get<1>(exp) / 2.0); 1458 | REQUIRE(abs(std::get<1>(i).back().first - out.back().first) <= std::get<1>(exp) / 2.0); 1459 | REQUIRE(abs(std::get<1>(i).back().second - out.back().second) <= std::get<1>(exp) / 2.0); 1460 | 1461 | //std::ofstream myfile; 1462 | //myfile.open("lpp_" + std::get<0>(i) + "_" 1463 | // + std::to_string(std::get<1>(exp)) + ".txt"); 1464 | //for (const auto& c : out) { 1465 | // myfile << std::to_string(c.first) << ", " << std::to_string(c.second) << std::endl; 1466 | //} 1467 | //myfile.close(); 1468 | } 1469 | } 1470 | 1471 | TEST_CASE("Polyline fits in buffer", "[LppPolyline]") { 1472 | CayenneLPP lpp(16); 1473 | REQUIRE(lpp.addColour(1, 2, 3, 4) == 5); 1474 | REQUIRE(lpp.addPolyline(2, { { 13.0001, 12.0001 }, { 13.0002, 12.0002 } }) == 16); 1475 | REQUIRE(lpp.getError() == LPP_ERROR_OK); 1476 | } 1477 | 1478 | TEST_CASE("Polyline exceeds buffer", "[LppPolyline]") { 1479 | CayenneLPP lpp(15); 1480 | REQUIRE(lpp.addColour(1, 2, 3, 4) == 5); 1481 | REQUIRE(lpp.addPolyline(2, { { 13.0001, 12.0001 }, { 13.0002, 12.0002 } }) == 0); 1482 | REQUIRE(lpp.getError() == LPP_ERROR_OVERFLOW); 1483 | } 1484 | 1485 | TEST_CASE("Decode polyline from message", "[LppPolyline]") { 1486 | CayenneLPP lpp(16); 1487 | REQUIRE(lpp.addColour(1, 2, 3, 4) == 5); 1488 | std::vector> coords { 1489 | { 13.0001, 12.0001 }, 1490 | { 13.0002, 12.0002 } 1491 | }; 1492 | REQUIRE(lpp.addPolyline(2, coords) == 16); 1493 | 1494 | const std::vector buffer(lpp.getBuffer(), lpp.getBuffer()+lpp.getSize()); 1495 | std::map messages; 1496 | REQUIRE(lpp.decode(lpp.getBuffer(), lpp.getSize(), messages) == 2); 1497 | REQUIRE(messages.at(2).polyline == coords); 1498 | } 1499 | --------------------------------------------------------------------------------