From 89f33efa2530997c4e166e89b6d5a777950afbb3 Mon Sep 17 00:00:00 2001 From: David Flaig Date: Sun, 17 Mar 2024 13:01:18 +0100 Subject: [PATCH 01/65] test ruleset --- collector_scripts/master_collector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 5d97867..c106fe9 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -181,4 +181,6 @@ def stop_udp_listener(self): ble_fan_value = unity_values["bleFan"] print("ble fan from unity: ", ble_fan_value) ''' + + """TEst""" From 72cf7bcb3310ea0bcf96a263bad45803a4ff7505 Mon Sep 17 00:00:00 2001 From: David Flaig Date: Sun, 17 Mar 2024 13:02:07 +0100 Subject: [PATCH 02/65] test ruleset --- collector_scripts/master_collector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 5d97867..54983f1 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -181,4 +181,6 @@ def stop_udp_listener(self): ble_fan_value = unity_values["bleFan"] print("ble fan from unity: ", ble_fan_value) ''' + + """Test""" From 35f4b0ba807295042a15625bdbb24e96aae141a4 Mon Sep 17 00:00:00 2001 From: David Flaig Date: Sun, 17 Mar 2024 13:09:42 +0100 Subject: [PATCH 03/65] reverted testing --- collector_scripts/master_collector.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index c106fe9..b12b495 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -181,6 +181,3 @@ def stop_udp_listener(self): ble_fan_value = unity_values["bleFan"] print("ble fan from unity: ", ble_fan_value) ''' - - """TEst""" - From 108caa6d91616f2317d2be032059e58b76f7bdf3 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 19 Mar 2024 12:48:03 +0100 Subject: [PATCH 04/65] edit ip adresses --- collector_scripts/master_collector.py | 2 +- collector_scripts/p110_connect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 5d97867..578902a 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -110,7 +110,7 @@ def stop_udp_listener(self): # IP adresses to receive data from actuators and sensors UDP_IP = "127.0.0.1" # IP to receive data from elite_rizer.py as well as from direto_xr.py scripts via UDP # UDP_IP_UNITY_RECEIVE = "127.0.0.1" # Receives Data from unity such as the ble fan data - UDP_ESP_IP = "10.30.77.221" # External IP of the computer running this script to receive data from ESP32 -> Bicycle Simulator Desktop PC + UDP_ESP_IP = "10.30.77.40" # External IP of the computer running this script to receive data from ESP32 -> Bicycle Simulator Desktop PC # UDP_ESP_IP = "192.168.9.184" # Raspberry Pi 3 # UDP_ESP_IP = "192.168.9.198" # Raspberry Pi 5 diff --git a/collector_scripts/p110_connect.py b/collector_scripts/p110_connect.py index cd56a34..29f6c58 100644 --- a/collector_scripts/p110_connect.py +++ b/collector_scripts/p110_connect.py @@ -3,7 +3,7 @@ def connect_and_start_p100(): - p100 = PyP100.P100("10.30.77.220", "unitylab.hhn3@gmail.com", "Unitylab") #Creates a P100 plug object + p100 = PyP100.P100("10.30.77.191", "unitylab.hhn3@gmail.com", "Unitylab") #Creates a P100 plug object # p100.handshake() #Creates the cookies required for further methods # p100.login() print("Restarting P100 Power Outlet") From d94bcdea6efd4bff27419b8034635e1864dec176 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Wed, 20 Mar 2024 16:08:10 +0100 Subject: [PATCH 05/65] adjusted the ip adresses in the new Bicycle_Simulator_Network --- collector_scripts/master_collector.py | 4 ++-- collector_scripts/p110_connect.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index bb23b49..50cb483 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -110,7 +110,7 @@ def stop_udp_listener(self): # IP adresses to receive data from actuators and sensors UDP_IP = "127.0.0.1" # IP to receive data from elite_rizer.py as well as from direto_xr.py scripts via UDP # UDP_IP_UNITY_RECEIVE = "127.0.0.1" # Receives Data from unity such as the ble fan data - UDP_ESP_IP = "10.30.77.40" # External IP of the computer running this script to receive data from ESP32 -> Bicycle Simulator Desktop PC + UDP_ESP_IP = "192.168.0.101" # IP of the computer running this script to receive data from ESP32 -> Bicycle Simulator Desktop PC # UDP_ESP_IP = "192.168.9.184" # Raspberry Pi 3 # UDP_ESP_IP = "192.168.9.198" # Raspberry Pi 5 @@ -165,8 +165,8 @@ def stop_udp_listener(self): data_sender.collect_brake(brake_value) elif sock is udp_bno_socket: bno_value = json.loads(data.decode()) - bno_value = bno_value["euler_r"] # print("BNO_Value: ", bno_value) + bno_value = bno_value["euler_r"] data_sender.collect_bno(bno_value) elif sock is udp_roll_socket: roll_value = json.loads(data.decode()) diff --git a/collector_scripts/p110_connect.py b/collector_scripts/p110_connect.py index 29f6c58..d4016e9 100644 --- a/collector_scripts/p110_connect.py +++ b/collector_scripts/p110_connect.py @@ -3,7 +3,7 @@ def connect_and_start_p100(): - p100 = PyP100.P100("10.30.77.191", "unitylab.hhn3@gmail.com", "Unitylab") #Creates a P100 plug object + p100 = PyP100.P100("192.168.0.110", "unitylab.hhn3@gmail.com", "Unitylab") #Creates a P100 plug object # p100.handshake() #Creates the cookies required for further methods # p100.login() print("Restarting P100 Power Outlet") From e1ef3d95dc497f69876bdb54c546b44e5725144c Mon Sep 17 00:00:00 2001 From: PSenfft Date: Thu, 21 Mar 2024 16:53:24 +0100 Subject: [PATCH 06/65] Create hardware_idears.md Added doc directory added hardware ideas --- doc/hardware_idears.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/hardware_idears.md diff --git a/doc/hardware_idears.md b/doc/hardware_idears.md new file mode 100644 index 0000000..24ffef8 --- /dev/null +++ b/doc/hardware_idears.md @@ -0,0 +1,3 @@ +- Food sensor while stand still Structure-borne sound transducer maybe with light barrier or pressure sensor +- Structure-borne sound transducer for undergound feedback and crashes (rockhorn) +- Brake feedback (maybe with pressure sensor) From 06af585f19628239f73fc6a01bad199c7aad86af Mon Sep 17 00:00:00 2001 From: unitylab-dev <153496537+unitylab-dev@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:23:32 +0100 Subject: [PATCH 07/65] Rename hardware_idears.md to hardware_idears.md --- doc/hardware_idears.md => docs/ hardware_idears.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/hardware_idears.md => docs/ hardware_idears.md (100%) diff --git a/doc/hardware_idears.md b/docs/ hardware_idears.md similarity index 100% rename from doc/hardware_idears.md rename to docs/ hardware_idears.md From 4786863c082879e66b6efa50e29128cc37b806f6 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 4 Apr 2024 11:38:28 +0200 Subject: [PATCH 08/65] added folder with sensor scripts for the esp32 --- .../ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino | 302 +++++++++++ .../sensor_scripts/Brake_Sensor/.gitignore | 1 + .../sensor_scripts/Brake_Sensor/.travis.yml | 67 +++ .../.vscode/c_cpp_properties.json | 501 ++++++++++++++++++ .../Brake_Sensor/.vscode/extensions.json | 10 + .../Brake_Sensor/.vscode/launch.json | 44 ++ .../sensor_scripts/Brake_Sensor/README.rst | 38 ++ .../Brake_Sensor/platformio.ini | 17 + .../sensor_scripts/Brake_Sensor/src/main.cpp | 63 +++ .../sensor_scripts/Brake_Sensor/test/README | 11 + .../sensor_scripts/Gyro_Sensor/.gitignore | 5 + .../Gyro_Sensor/.vscode/extensions.json | 10 + .../sensor_scripts/Gyro_Sensor/platformio.ini | 18 + .../sensor_scripts/Gyro_Sensor/src/main.cpp | 84 +++ .../sensor_scripts/Gyro_Sensor/test/README | 11 + .../sensor_scripts/Roll_Sensor/.gitignore | 5 + .../Roll_Sensor/.vscode/extensions.json | 10 + .../sensor_scripts/Roll_Sensor/README.md | 0 .../sensor_scripts/Roll_Sensor/platformio.ini | 15 + .../sensor_scripts/Roll_Sensor/src/main.cpp | 300 +++++++++++ .../sensor_scripts/Roll_Sensor/test/README | 11 + 21 files changed, 1523 insertions(+) create mode 100644 collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/.gitignore create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/README.rst create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp create mode 100644 collector_scripts/sensor_scripts/Brake_Sensor/test/README create mode 100644 collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore create mode 100644 collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json create mode 100644 collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini create mode 100644 collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp create mode 100644 collector_scripts/sensor_scripts/Gyro_Sensor/test/README create mode 100644 collector_scripts/sensor_scripts/Roll_Sensor/.gitignore create mode 100644 collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json create mode 100644 collector_scripts/sensor_scripts/Roll_Sensor/README.md create mode 100644 collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini create mode 100644 collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp create mode 100644 collector_scripts/sensor_scripts/Roll_Sensor/test/README diff --git a/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino b/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino new file mode 100644 index 0000000..cc731f6 --- /dev/null +++ b/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino @@ -0,0 +1,302 @@ +#include +#include +#include +#include +#include + +const int HALL_PIN = 32; + +//const char* ssid = "raspi-webgui"; +//const char* password = "bikingismylife"; +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 6666; +const unsigned long udpSendInterval = 500; // Interval for sending UDP packets in milliseconds +unsigned long lastUdpSendTime = 0; // Variable to store the last time UDP packet was sent + +WiFiUDP udp; + +using namespace std; // Use the std namespace for ArduinoSTL + +// General control settings and flags +const int debugging = 0; +const int printResult = 1; +const int directionalMode = 1; + +// Control timings in ms +const int loopTime = 60; // time between value calculation and printing in average angle reading +const int iterationCount = 20; // how many angle readings to average in one loop +const int iterationPadding = 0; // time between individual value readings + +// Control settings for turn direction reading +const float turnSensitivityActivation = 6; // default: 22 - for turn direction +const float turnSensitivityDeactivation = 31; + +// AS5600 device specifics +const int deviceAddress = 0x36; +const int registerAddressHigh = 0x0E; // Register for high 4 bits +const int registerAddressLow = 0x0F; // Register for low 8 bits +const int maxSensorValue = 4095; +const int maxDegrees = 360; +const int SDA_PIN = 18; +const int SCL_PIN = 19; + +//reused variables +unsigned long loopStartTime; +unsigned long loopEndTime; +unsigned long executionTime; +float lastReadAngle = 0; +int directionBufferIndex = 0; +int directionBufferSize = 0; +uint64_t directionBuffer = 0; +int turnDirection = 0; +int lastTurnDirection = 0; +int nextBit = 0; +int angleTolerance = 0; + + +int readValues = 0; + +void AddToBuffer(int val) { + directionBuffer = (directionBuffer << 1) | (val & 1); +} + +int countOnes(uint64_t n) +{ + unsigned int c; // the total bits set in n + for (c = 0; n; n = n & (n-1)) + { + c++; + } + return c; +} + +void printBufferBits() { + int i; + // The number of bits in a uint64_t is 64 + for (i = 63; i >= 0; i--) { + // Check the i-th bit using bitwise AND + uint64_t bit = (directionBuffer >> i) & 1; + Serial.print((int)bit); + } + Serial.println(); // Print a new line after all bits +} + +void setup() { + Wire.begin(SDA_PIN, SCL_PIN); + Serial.begin(115200); + + WiFi.hostname("Roll_ESP"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + Serial.print("Connecting with WiFi with ssid: "); + Serial.print(ssid); + Serial.print(" and password: "); + Serial.println(password); + Serial.println(WiFi.status()); + } + + Serial.println("Connection with WiFi successful"); + udp.begin(localUdpPort); + + loopStartTime = millis(); +} + +void loop() { + if (directionalMode) { + getRotation(); + } else { + averageAngle(); + } + + // Check if it's time to send the UDP packet + unsigned long currentTime = millis(); + if (currentTime - lastUdpSendTime >= udpSendInterval) { + StaticJsonDocument<500> doc; + + String jsonStr; + doc["sensor"] = "AS5600"; + doc["sensor_value"] = turnDirection; + + serializeJson(doc, jsonStr); + + // Read UDP messages + int packetSize = udp.parsePacket(); + if (packetSize) { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + udp.beginPacket("192.168.0.101", 6666); + udp.print(jsonStr); + udp.endPacket(); + + // Update the last send time + lastUdpSendTime = currentTime; + } +} + +void averageAngle() { + loopStartTime = millis(); + float angleReadings[iterationCount]; + + for (int it = 0; it < iterationCount; ++it) { + float angle = readAngle(); + + // Check if readAngle encountered an error + if (isnan(angle)) { + Serial.print(" !Reading Error! "); + continue; // Skip the rest of the loop if there's an error + } + angleReadings[it] = angle; + if(iterationPadding > 0)delay(iterationPadding); + } + + int readValuesSize = sizeof(angleReadings) / sizeof(angleReadings[0]); + // Calculate the mean angle using the array of readings + float averagedAngle = meanAngle(angleReadings, readValuesSize); + + if(debugging){ + Serial.println(""); + Serial.print("Raw values: "); + Serial.println(readValuesSize); + printArray(angleReadings, readValuesSize); + } + if(printResult) { + Serial.print("Angle: "); + Serial.println(averagedAngle, 3); // Print with 3 decimal places + } + loopEndTime = millis(); + executionTime = loopEndTime - loopStartTime; + + if(debugging) { + Serial.print("execution time: "); + Serial.println(executionTime); + } + if(executionTime <= loopTime) { + delay((float)(loopTime - executionTime)); + } else { + if(debugging) { + Serial.println("Cant keep up! Execution time of " + (String)executionTime + "ms is " + (String) (executionTime - loopTime) + "ms over the budget!"); + } else { + Serial.println(" Device Slowdown!"); + } + } +} + +float readAngle() { + // Request the high byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressHigh); + int transmissionStatusHigh = Wire.endTransmission(); + + if (transmissionStatusHigh != 0) { + Serial.print("Error in I2C transmission (high byte). Status: "); + Serial.println(transmissionStatusHigh); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte highByte = Wire.read(); + + // Request the low byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressLow); + int transmissionStatusLow = Wire.endTransmission(); + + if (transmissionStatusLow != 0) { + Serial.print("Error in I2C transmission (low byte). Status: "); + Serial.println(transmissionStatusLow); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte lowByte = Wire.read(); + + // Combine the high and low bytes to form a 12-bit value + int sensorValue = (highByte << 8) | lowByte; + + // Map the sensor value to degrees + float degrees = (float(sensorValue) / maxSensorValue) * maxDegrees; + + // Check if the angle is exactly 360.0, and if so, set it to 0.0 + if (degrees == 360.0) { + degrees = 0.0; + } + readValues++; + return degrees; +} + +// Calculate the mean angle from -180 to 180 degrees +double meanAngle(const float angles[], int size) { + double x = 0.0; + double y = 0.0; + + for (int i = 0; i < size; ++i) { + x += cos(angles[i] * PI / 180); + y += sin(angles[i] * PI / 180); + } + + return (atan2(y, x) * 180 / PI) + 180; +} + +void printArray(const float arr[], int size) { + Serial.print("["); + for (int i = 0; i < size; ++i) { + Serial.print(arr[i], 3); // Print each element with 3 decimal places + if (i < size - 1) { + Serial.print(", "); + } + } + Serial.println("]"); +} + +void getRotation() { + float newAngle = readAngle(); + if(newAngle > lastReadAngle + angleTolerance) { + AddToBuffer(1); + } else if(newAngle < lastReadAngle - angleTolerance) { + AddToBuffer(0); + } else { + nextBit = (nextBit + 1) % 2; //Add 1 and 0 in equal amounts + AddToBuffer(nextBit); + } + + lastReadAngle = newAngle; + + int ones = countOnes(directionBuffer); + int zeroes = 64 - ones; + if(debugging) { + Serial.println("Zeroes count: " + (String)zeroes + ", Ones count: " + (String)ones + "."); + } + int sensitivity = (lastTurnDirection == -1 || lastTurnDirection == 1) ? turnSensitivityDeactivation : turnSensitivityActivation; + if(ones > 64 - sensitivity) turnDirection = 1; + else if(ones <= sensitivity) turnDirection = -1; + else turnDirection = 0; + + if(printResult && lastTurnDirection != turnDirection) { + lastTurnDirection = turnDirection; + if(turnDirection == 1) Serial.println("Turning clockwise..."); + else if(turnDirection == -1) Serial.println("Turning counter-clockwise..."); + else if(turnDirection == 0) Serial.println("Not turning..."); + } + if(millis() - loopStartTime > 10000){ + Serial.println("Rate of "+(String)(readValues / 10)+" values per second."); + loopStartTime = millis(); + readValues = 0; + } +} + + diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore b/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore new file mode 100644 index 0000000..03f4a3c --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml b/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml new file mode 100644 index 0000000..7c486f1 --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml @@ -0,0 +1,67 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < https://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < https://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < https://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choose one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to be used as a library with examples. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..d409ba9 --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json @@ -0,0 +1,501 @@ +// +// !!! WARNING !!! AUTO-GENERATED FILE! +// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": +// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags +// +{ + "configurations": [ + { + "name": "PlatformIO", + "includePath": [ + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/include", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/libdeps/esp32dev/ArduinoJson/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/cores/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/variants/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", + "" + ], + "browse": { + "limitSymbolsToIncludedHeaders": true, + "path": [ + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/include", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/libdeps/esp32dev/ArduinoJson/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/cores/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/variants/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", + "" + ] + }, + "defines": [ + "PLATFORMIO=60114", + "ARDUINO_ESP32_DEV", + "HAVE_CONFIG_H", + "MBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"", + "UNITY_INCLUDE_CONFIG_H", + "WITH_POSIX", + "_GNU_SOURCE", + "IDF_VER=\"v4.4.6-dirty\"", + "ESP_PLATFORM", + "_POSIX_READER_WRITER_LOCKS", + "ARDUINO_ARCH_ESP32", + "ESP32", + "F_CPU=240000000L", + "ARDUINO=10812", + "ARDUINO_VARIANT=\"esp32\"", + "ARDUINO_BOARD=\"Espressif ESP32 Dev Module\"", + "ARDUINO_PARTITION_default", + "" + ], + "cStandard": "gnu99", + "cppStandard": "gnu++11", + "compilerPath": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc.exe", + "compilerArgs": [ + "-mlongcalls", + "" + ] + } + ], + "version": 4 +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json new file mode 100644 index 0000000..09ba71d --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json @@ -0,0 +1,44 @@ +// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY +// +// PlatformIO Debugging Solution +// +// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html +// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html + +{ + "version": "0.2.0", + "configurations": [ + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug", + "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32dev", + "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", + "internalConsoleOptions": "openOnSessionStart", + "preLaunchTask": { + "type": "PlatformIO", + "task": "Pre-Debug" + } + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (skip Pre-Debug)", + "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32dev", + "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (without uploading)", + "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32dev", + "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", + "internalConsoleOptions": "openOnSessionStart", + "loadMode": "manual" + } + ] +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/README.rst b/collector_scripts/sensor_scripts/Brake_Sensor/README.rst new file mode 100644 index 0000000..48bf203 --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/README.rst @@ -0,0 +1,38 @@ +.. Copyright 2014-present PlatformIO + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +How to build PlatformIO based project +===================================== + +1. `Install PlatformIO Core `_ +2. Download `development platform with examples `_ +3. Extract ZIP archive +4. Run these commands: + +.. code-block:: bash + + # Change directory to example + > cd platform-espressif32/examples/arduino-wifiscan + + # Build project + > platformio run + + # Upload firmware + > platformio run --target upload + + # Build specific environment + > platformio run -e quantum + + # Upload firmware for the specific environment + > platformio run -e quantum --target upload + + # Clean build files + > platformio run --target clean diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini b/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini new file mode 100644 index 0000000..9d8e03c --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +monitor_speed = 115200 +lib_deps = + bblanchon/ArduinoJson@^6.21.2 diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp b/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp new file mode 100644 index 0000000..525995d --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +const int HALL_PIN = 32; + +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 8888; + +WiFiUDP udp; + +void setup() +{ + + pinMode(HALL_PIN, INPUT_PULLUP); + delay(1000); + + WiFi.hostname("Brake_ESP"); + + Serial.begin(115200); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + Serial.println("Connecting with WiFi"); + } + + Serial.println("Connection with WiFi successful"); + udp.begin(localUdpPort); +} + +void loop() +{ + + int sensorValue = analogRead(HALL_PIN); // read Sensor value + StaticJsonDocument<500> doc; + + String jsonStr; + doc["sensor"] = "Brake"; + doc["sensor_value"] = sensorValue; + + serializeJson(doc, jsonStr); + + // Read UDP messages + int packetSize = udp.parsePacket(); + if (packetSize) + { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + udp.beginPacket("192.168.0.101", 7777); + udp.print(jsonStr); + udp.endPacket(); + + delay(1000); +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/test/README b/collector_scripts/sensor_scripts/Brake_Sensor/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/collector_scripts/sensor_scripts/Brake_Sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore b/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json b/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini b/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini new file mode 100644 index 0000000..b62f6af --- /dev/null +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:upesy_wroom] +platform = espressif32 +board = upesy_wroom +framework = arduino +monitor_speed = 115200 +lib_deps = + bblanchon/ArduinoJson@^6.21.2 + arduino-libraries/BNO055@^1.2.1 diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp b/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp new file mode 100644 index 0000000..d934ed6 --- /dev/null +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp @@ -0,0 +1,84 @@ +#include + +#include +#include + +#include "BNO055_support.h" +#include + +#include + +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 8888; + +WiFiUDP udp; + +struct bno055_t myBNO; +struct bno055_euler myEulerData; // Structure to hold the Euler data +struct bno055_gyro myGyroData; // Structure to hold the Gyro data + +unsigned long lastTime = 0; + +void setup() +{ + // Initialize I2C communication + Wire.begin(); + + // Create a static buffer for framing BME280 sensor data with ESP32 chip ID. + + // Initialization of the BNO055 + BNO_Init(&myBNO); // Assigning the structure to hold information about the device + + // Configuration to NDoF mode + bno055_set_operation_mode(OPERATION_MODE_NDOF); + + delay(1); + + Serial.begin(115200); + WiFi.hostname("Gyro_ESP"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); // setting sending rate + Serial.println("Connecting with WiFi"); + } + + Serial.println("Connection with WiFi successful"); + + udp.begin(localUdpPort); +} + +void loop() +{ + bno055_read_euler_hrp(&myEulerData); // Update Euler data into the structure + StaticJsonDocument<500> doc; + + int packetSize = udp.parsePacket(); + + // Method for receiving data + if (packetSize) + { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + // Creating JSON for sensor data + String jsonStr; + doc["sensor"] = "BNO055"; + doc["euler_h"] = ((myEulerData.h) / 16.00); + doc["euler_r"] = ((myEulerData.r) / 16.00); + doc["euler_p"] = ((myEulerData.p) / 16.00); + + serializeJson(doc, jsonStr); + + udp.beginPacket("192.168.0.101", 8888); + udp.print(jsonStr); + udp.endPacket(); + + delay(10); +} diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/test/README b/collector_scripts/sensor_scripts/Gyro_Sensor/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore b/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json b/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/README.md b/collector_scripts/sensor_scripts/Roll_Sensor/README.md new file mode 100644 index 0000000..e69de29 diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini b/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini new file mode 100644 index 0000000..dc48bbc --- /dev/null +++ b/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini @@ -0,0 +1,15 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = bblanchon/ArduinoJson@^7.0.3 diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp b/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp new file mode 100644 index 0000000..f64894e --- /dev/null +++ b/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include + +const int HALL_PIN = 32; + +//const char* ssid = "raspi-webgui"; +//const char* password = "bikingismylife"; +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 6666; +const unsigned long udpSendInterval = 500; // Interval for sending UDP packets in milliseconds +unsigned long lastUdpSendTime = 0; // Variable to store the last time UDP packet was sent + +WiFiUDP udp; + +using namespace std; // Use the std namespace for ArduinoSTL + +// General control settings and flags +const int debugging = 0; +const int printResult = 1; +const int directionalMode = 1; + +// Control timings in ms +const int loopTime = 60; // time between value calculation and printing in average angle reading +const int iterationCount = 20; // how many angle readings to average in one loop +const int iterationPadding = 0; // time between individual value readings + +// Control settings for turn direction reading +const float turnSensitivityActivation = 6; // default: 22 - for turn direction +const float turnSensitivityDeactivation = 31; + +// AS5600 device specifics +const int deviceAddress = 0x36; +const int registerAddressHigh = 0x0E; // Register for high 4 bits +const int registerAddressLow = 0x0F; // Register for low 8 bits +const int maxSensorValue = 4095; +const int maxDegrees = 360; +const int SDA_PIN = 18; +const int SCL_PIN = 19; + +//reused variables +unsigned long loopStartTime; +unsigned long loopEndTime; +unsigned long executionTime; +float lastReadAngle = 0; +int directionBufferIndex = 0; +int directionBufferSize = 0; +uint64_t directionBuffer = 0; +int turnDirection = 0; +int lastTurnDirection = 0; +int nextBit = 0; +int angleTolerance = 0; + + +int readValues = 0; + +void AddToBuffer(int val) { + directionBuffer = (directionBuffer << 1) | (val & 1); +} + +int countOnes(uint64_t n) +{ + unsigned int c; // the total bits set in n + for (c = 0; n; n = n & (n-1)) + { + c++; + } + return c; +} + +void printBufferBits() { + int i; + // The number of bits in a uint64_t is 64 + for (i = 63; i >= 0; i--) { + // Check the i-th bit using bitwise AND + uint64_t bit = (directionBuffer >> i) & 1; + Serial.print((int)bit); + } + Serial.println(); // Print a new line after all bits +} + +void setup() { + Wire.begin(SDA_PIN, SCL_PIN); + Serial.begin(115200); + + WiFi.hostname("Roll_ESP"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + Serial.print("Connecting with WiFi with ssid: "); + Serial.print(ssid); + Serial.print(" and password: "); + Serial.println(password); + Serial.println(WiFi.status()); + } + + Serial.println("Connection with WiFi successful"); + udp.begin(localUdpPort); + + loopStartTime = millis(); +} + +void loop() { + if (directionalMode) { + getRotation(); + } else { + averageAngle(); + } + + // Check if it's time to send the UDP packet + unsigned long currentTime = millis(); + if (currentTime - lastUdpSendTime >= udpSendInterval) { + StaticJsonDocument<500> doc; + + String jsonStr; + doc["sensor"] = "AS5600"; + doc["sensor_value"] = turnDirection; + + serializeJson(doc, jsonStr); + + // Read UDP messages + int packetSize = udp.parsePacket(); + if (packetSize) { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + udp.beginPacket("192.168.0.101", 6666); + udp.print(jsonStr); + udp.endPacket(); + + // Update the last send time + lastUdpSendTime = currentTime; + } +} + +void averageAngle() { + loopStartTime = millis(); + float angleReadings[iterationCount]; + + for (int it = 0; it < iterationCount; ++it) { + float angle = readAngle(); + + // Check if readAngle encountered an error + if (isnan(angle)) { + Serial.print(" !Reading Error! "); + continue; // Skip the rest of the loop if there's an error + } + angleReadings[it] = angle; + if(iterationPadding > 0)delay(iterationPadding); + } + + int readValuesSize = sizeof(angleReadings) / sizeof(angleReadings[0]); + // Calculate the mean angle using the array of readings + float averagedAngle = meanAngle(angleReadings, readValuesSize); + + if(debugging){ + Serial.println(""); + Serial.print("Raw values: "); + Serial.println(readValuesSize); + printArray(angleReadings, readValuesSize); + } + if(printResult) { + Serial.print("Angle: "); + Serial.println(averagedAngle, 3); // Print with 3 decimal places + } + loopEndTime = millis(); + executionTime = loopEndTime - loopStartTime; + + if(debugging) { + Serial.print("execution time: "); + Serial.println(executionTime); + } + if(executionTime <= loopTime) { + delay((float)(loopTime - executionTime)); + } else { + if(debugging) { + Serial.println("Cant keep up! Execution time of " + (String)executionTime + "ms is " + (String) (executionTime - loopTime) + "ms over the budget!"); + } else { + Serial.println(" Device Slowdown!"); + } + } +} + +float readAngle() { + // Request the high byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressHigh); + int transmissionStatusHigh = Wire.endTransmission(); + + if (transmissionStatusHigh != 0) { + Serial.print("Error in I2C transmission (high byte). Status: "); + Serial.println(transmissionStatusHigh); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte highByte = Wire.read(); + + // Request the low byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressLow); + int transmissionStatusLow = Wire.endTransmission(); + + if (transmissionStatusLow != 0) { + Serial.print("Error in I2C transmission (low byte). Status: "); + Serial.println(transmissionStatusLow); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte lowByte = Wire.read(); + + // Combine the high and low bytes to form a 12-bit value + int sensorValue = (highByte << 8) | lowByte; + + // Map the sensor value to degrees + float degrees = (float(sensorValue) / maxSensorValue) * maxDegrees; + + // Check if the angle is exactly 360.0, and if so, set it to 0.0 + if (degrees == 360.0) { + degrees = 0.0; + } + readValues++; + return degrees; +} + +// Calculate the mean angle from -180 to 180 degrees +double meanAngle(const float angles[], int size) { + double x = 0.0; + double y = 0.0; + + for (int i = 0; i < size; ++i) { + x += cos(angles[i] * PI / 180); + y += sin(angles[i] * PI / 180); + } + + return (atan2(y, x) * 180 / PI) + 180; +} + +void printArray(const float arr[], int size) { + Serial.print("["); + for (int i = 0; i < size; ++i) { + Serial.print(arr[i], 3); // Print each element with 3 decimal places + if (i < size - 1) { + Serial.print(", "); + } + } + Serial.println("]"); +} + +void getRotation() { + float newAngle = readAngle(); + if(newAngle > lastReadAngle + angleTolerance) { + AddToBuffer(1); + } else if(newAngle < lastReadAngle - angleTolerance) { + AddToBuffer(0); + } else { + nextBit = (nextBit + 1) % 2; //Add 1 and 0 in equal amounts + AddToBuffer(nextBit); + } + + lastReadAngle = newAngle; + + int ones = countOnes(directionBuffer); + int zeroes = 64 - ones; + if(debugging) { + Serial.println("Zeroes count: " + (String)zeroes + ", Ones count: " + (String)ones + "."); + } + int sensitivity = (lastTurnDirection == -1 || lastTurnDirection == 1) ? turnSensitivityDeactivation : turnSensitivityActivation; + if(ones > 64 - sensitivity) turnDirection = 1; + else if(ones <= sensitivity) turnDirection = -1; + else turnDirection = 0; + + if(printResult && lastTurnDirection != turnDirection) { + lastTurnDirection = turnDirection; + if(turnDirection == 1) Serial.println("Turning clockwise..."); + else if(turnDirection == -1) Serial.println("Turning counter-clockwise..."); + else if(turnDirection == 0) Serial.println("Not turning..."); + } + if(millis() - loopStartTime > 10000){ + Serial.println("Rate of "+(String)(readValues / 10)+" values per second."); + loopStartTime = millis(); + readValues = 0; + } +} \ No newline at end of file diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/test/README b/collector_scripts/sensor_scripts/Roll_Sensor/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/collector_scripts/sensor_scripts/Roll_Sensor/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html From c8657c1fcb2d663b422cd23229067b5eb48e340c Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 15:46:27 +0200 Subject: [PATCH 09/65] change variable name (static uppercase) --- collector_scripts/elite_rizer.py | 47 ++++++++++++++++++-------------- collector_scripts/headwind.py | 27 ++++++++++++++---- collector_scripts/run.py | 42 ---------------------------- 3 files changed, 49 insertions(+), 67 deletions(-) delete mode 100644 collector_scripts/run.py diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 8855b64..3541682 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -2,14 +2,20 @@ from bleak import BleakScanner, BleakClient, exc import socket -device_name = "RIZER" -DEVICEID = "" +DEVICE_NAME = "RIZER" +DEVICE_UUID = "fc:12:65:28:cb:44" +DEVICE_ID = "" -service_uuid = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_TILT_UUID = "347b00207635408b89188ff3949ce592" SERVICE = "" -characteristic_steering_uuid = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_STEERING = "" +CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt + + +characteristics_steering = "" + class BluetoothCallback: @@ -56,23 +62,24 @@ async def read_steering(client, characteristic): async def scan_and_connect_rizer(): - global device_name + global DEVICE_NAME - global service_uuid + global SERVICE_UUID global SERVICE - global characteristic_steering_uuid - global CHARACTERISTIC_STEERING + global CHARACTERISTICS_STEEING_UUID + global characteristics_steering stop_event = asyncio.Event() # Scanning and printing for BLE devices def callback(device, advertising_data): - global DEVICEID + global DEVICE_ID # print(device) # print("Test rizer") - if(device.name == device_name): - DEVICEID = device + if(device.name == DEVICE_NAME): + + DEVICE_ID = device stop_event.set() # Stops the scanning event @@ -86,20 +93,20 @@ def callback(device, advertising_data): ''' await stop_event.wait() - if(DEVICEID != ""): + if(DEVICE_ID != ""): # Connecting to BLE Device client_is_connected = False while(client_is_connected == False): try: - async with BleakClient(DEVICEID, timeout=90) as client: + async with BleakClient(DEVICE_ID, timeout=90) as client: client_is_connected = True - print("Client connected to ", DEVICEID.name) + print("Client connected to ", DEVICE_ID.name) # return True # logger.info("Device ID ", device_id) for service in client.services: # print("service: ", service) - if (service.uuid == service_uuid): + if (service.uuid == SERVICE_UUID): SERVICE = service # print("[service uuid] ", SERVICE.uuid) @@ -107,15 +114,15 @@ def callback(device, advertising_data): # print("SERVICE", SERVICE) for characteristic in SERVICE.characteristics: - if("notify" in characteristic.properties and characteristic.uuid == characteristic_steering_uuid): - CHARACTERISTIC_STEERING = characteristic + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + characteristics_steering = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) while True: - await read_steering(client, CHARACTERISTIC_STEERING) + await read_steering(client, characteristics_steering) except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") + print(f"Failed to connect/discover services of {DEVICE_ID.name}: {e}") # Add additional error handling or logging as needed # raise diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 6c4244e..7be2a37 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -87,6 +87,7 @@ def callback(device, advertising_data): receiver.start_udp_listener() # print("FAN SPEED: ", receiver.get_fan_speed()) speed_value = receiver.get_fan_speed() + print("Fan Speed: ", speed_value) except Exception as e: print("Error: ", e) try: @@ -94,12 +95,27 @@ def callback(device, advertising_data): except Exception as e: print("Error: ", e) - ''' Test - receiver.start_udp_listener() + + ''' + try: + if speed_value > 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + elif speed_value <= 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) # Turn fan off + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Error turning fan on or off.") + try: - receiver.listen_for_udp_data() - except KeyboardInterrupt: - receiver.stop_udp_listener() + if speed_value > 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + ''' try: if 2 <= speed_value <= 100: @@ -113,6 +129,7 @@ def callback(device, advertising_data): print("Speed value should be between 1 and 100.") except ValueError: print("Invalid input. Please enter a number between 1 and 100.") + except exc.BleakError as e: print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") # Add additional error handling or logging as needed diff --git a/collector_scripts/run.py b/collector_scripts/run.py deleted file mode 100644 index 939189f..0000000 --- a/collector_scripts/run.py +++ /dev/null @@ -1,42 +0,0 @@ -import subprocess -import time -from p110_connect import connect_and_start_p100 - -python_scripts = ["direto_xr.py", "elite_rizer.py", "master_collector.py"] # , "headwind.py" -print("starting scripts...") -subprocesses = [] - -p100 = connect_and_start_p100() -# p100 = True -if p100: - # Loop through the other scripts and start them - for script in python_scripts: - process = subprocess.Popen(["python", script]) - subprocesses.append(process) - print(f"Started {script}") - time.sleep(15) - -try: - # Keep the script running until interrupted by the user - while True: - pass -except KeyboardInterrupt: - # Handle KeyboardInterrupt (Ctrl+C) to terminate subprocesses - print("\nTerminating subprocesses...") - for process in subprocesses: - process.terminate() - print("All scripts terminated.") - - - - - - - - - - - - -# Additional cleanup or logging if needed -print("All scripts completed.") From 5c111e46c59e80c8fb3ccad84dce264bd43554eb Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 15:51:54 +0200 Subject: [PATCH 10/65] added old scripts to the archive --- archive/automized_run_scripts/run.py | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 archive/automized_run_scripts/run.py diff --git a/archive/automized_run_scripts/run.py b/archive/automized_run_scripts/run.py new file mode 100644 index 0000000..939189f --- /dev/null +++ b/archive/automized_run_scripts/run.py @@ -0,0 +1,42 @@ +import subprocess +import time +from p110_connect import connect_and_start_p100 + +python_scripts = ["direto_xr.py", "elite_rizer.py", "master_collector.py"] # , "headwind.py" +print("starting scripts...") +subprocesses = [] + +p100 = connect_and_start_p100() +# p100 = True +if p100: + # Loop through the other scripts and start them + for script in python_scripts: + process = subprocess.Popen(["python", script]) + subprocesses.append(process) + print(f"Started {script}") + time.sleep(15) + +try: + # Keep the script running until interrupted by the user + while True: + pass +except KeyboardInterrupt: + # Handle KeyboardInterrupt (Ctrl+C) to terminate subprocesses + print("\nTerminating subprocesses...") + for process in subprocesses: + process.terminate() + print("All scripts terminated.") + + + + + + + + + + + + +# Additional cleanup or logging if needed +print("All scripts completed.") From 9a1ce724a0f28cdfe1900a986fc2dd55e1838d88 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 16:41:56 +0200 Subject: [PATCH 11/65] added new rizer file to test some stuff. -> deleted unessesarry BT scanner in file --- collector_scripts/connect_rizer.py | 110 +++++++++++++++++++++++++++++ collector_scripts/run_scripts.bat | 2 +- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 collector_scripts/connect_rizer.py diff --git a/collector_scripts/connect_rizer.py b/collector_scripts/connect_rizer.py new file mode 100644 index 0000000..490ca17 --- /dev/null +++ b/collector_scripts/connect_rizer.py @@ -0,0 +1,110 @@ +import asyncio +from bleak import BleakClient, exc +import socket + +DEVICE_NAME = "RIZER" +DEVICE_UUID = "fc:12:65:28:cb:44" + +SERVICE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_TILT_UUID = "347b00207635408b89188ff3949ce592" +SERVICE = "" + +CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt + + +characteristics_steering = "" + + + +class BluetoothCallback: + def __init__(self): + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC + # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi + self.udp_port = 2222 + + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + self.send_steering_data_udp(self.received_steering_data) + + + def send_steering_data_udp(self, steering_data): + # Create a UDP socket + # print(steering_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + +async def read_steering(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + # print("Test steering") + except Exception as e: + print("Error: ", e) + + + +async def scan_and_connect_rizer(): + global DEVICE_NAME + + global SERVICE_UUID + global SERVICE + + global CHARACTERISTICS_STEEING_UUID + global characteristics_steering + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + #print("Client connected to ", DEVICE_ID.name) + #print("Client connected to ", DEVICE_ID) + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_UUID): + SERVICE = service + # print("[service uuid] ", SERVICE.uuid) + + if (SERVICE != ""): + # print("SERVICE", SERVICE) + for characteristic in SERVICE.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + characteristics_steering = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + + + while True: + await read_steering(client, characteristics_steering) + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + +asyncio.run(scan_and_connect_rizer()) + + + + diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 0dc60a0..5d648f5 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -4,7 +4,7 @@ REM Set the Python script to be executed 5 seconds before others set pre_execution_script="p110_connect.py" REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py") +set python_scripts=("direto_xr.py", "connect_rizer.py", "headwind.py", "master_collector.py") REM Start the pre-execution script start "" python %pre_execution_script% From 299dd196ecb2ae9f4923820a119ad199e4de01f0 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 17:12:39 +0200 Subject: [PATCH 12/65] cleaned up --- collector_scripts/connect_rizer.py | 53 ++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/collector_scripts/connect_rizer.py b/collector_scripts/connect_rizer.py index 490ca17..a614a8d 100644 --- a/collector_scripts/connect_rizer.py +++ b/collector_scripts/connect_rizer.py @@ -1,19 +1,27 @@ import asyncio from bleak import BleakClient, exc import socket +import time DEVICE_NAME = "RIZER" DEVICE_UUID = "fc:12:65:28:cb:44" -SERVICE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering SERVICE_TILT_UUID = "347b00207635408b89188ff3949ce592" -SERVICE = "" + +steering_service = "" +tilt_service = "" + +stering_ready = 0 + + CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt characteristics_steering = "" +characteristics_tilt = "" @@ -56,18 +64,25 @@ async def read_steering(client, characteristic): await client.stop_notify(characteristic.uuid) # print("Test steering") except Exception as e: - print("Error: ", e) + print("Error: ", e) + +async def write_tilt(client, characteristic): + BluetoothCallback = BluetoothCallback() async def scan_and_connect_rizer(): global DEVICE_NAME - global SERVICE_UUID - global SERVICE + global SERVICE_STEERING_UUID + + global steering_service + global tilt_service global CHARACTERISTICS_STEEING_UUID global characteristics_steering + + global stering_ready # Connecting to BLE Device client_is_connected = False @@ -83,21 +98,23 @@ async def scan_and_connect_rizer(): for service in client.services: # print("service: ", service) - if (service.uuid == SERVICE_UUID): - SERVICE = service + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service # print("[service uuid] ", SERVICE.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + characteristics_steering = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - if (SERVICE != ""): - # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - characteristics_steering = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - - - while True: - await read_steering(client, characteristics_steering) + + while True: + await read_steering(client, characteristics_steering) + + except exc.BleakError as e: print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") # Add additional error handling or logging as needed From cf6f517de694938f0ed37660f16f9cbbd849f16b Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 17:49:20 +0200 Subject: [PATCH 13/65] overrited eliter_rizer.py with connect_rizer.py still working, better readable and with tilt service (tilt service is not writeable until now) --- collector_scripts/connect_rizer.py | 38 ++++++++++++++++++++---------- collector_scripts/elite_rizer.py | 4 ++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/collector_scripts/connect_rizer.py b/collector_scripts/connect_rizer.py index a614a8d..0fba4ac 100644 --- a/collector_scripts/connect_rizer.py +++ b/collector_scripts/connect_rizer.py @@ -99,21 +99,33 @@ async def scan_and_connect_rizer(): # print("service: ", service) if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - # print("[service uuid] ", SERVICE.uuid) + steering_service = service + # print("[service uuid] ", SERVICE.uuid) if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - characteristics_steering = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - - - while True: - await read_steering(client, characteristics_steering) - + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + characteristics_steering = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + characteristics_tilt = characteristic + + + print(stering_ready) + print("IF2") + while stering_ready == 1: + await read_steering(client, characteristics_steering) + except exc.BleakError as e: print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 3541682..165e041 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -117,8 +117,8 @@ def callback(device, advertising_data): if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): characteristics_steering = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - - + print("IF") + print("FOR") while True: await read_steering(client, characteristics_steering) except exc.BleakError as e: From 3de5390b0e8f9f6c01d17a0f9ad7610f6dd4aaf0 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 18:56:39 +0200 Subject: [PATCH 14/65] working on loop --- collector_scripts/connect_rizer.py | 72 +++++++++++++++++++----------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/collector_scripts/connect_rizer.py b/collector_scripts/connect_rizer.py index 0fba4ac..bc45e02 100644 --- a/collector_scripts/connect_rizer.py +++ b/collector_scripts/connect_rizer.py @@ -7,12 +7,16 @@ DEVICE_UUID = "fc:12:65:28:cb:44" SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b00207635408b89188ff3949ce592" +SERVICE_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" + +INCREASE_TILT_HEX = "060102" +DECREASE_TILT_HEX = "060402" steering_service = "" tilt_service = "" stering_ready = 0 +tilt_ready = 0 @@ -20,8 +24,8 @@ CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt -characteristics_steering = "" -characteristics_tilt = "" +steering_characteristics = "" +tilt_characteristics = "" @@ -67,9 +71,14 @@ async def read_steering(client, characteristic): print("Error: ", e) async def write_tilt(client, characteristic): - BluetoothCallback = BluetoothCallback() - - + bluetooth_callback = BluetoothCallback() + try: + #TODO Write bt stuff + print("BT stuff") + await client.write_gatt_char(tilt_characteristics, bytes.fromhex(INCREASE_TILT_HEX), response=True) + except Exception as e: + print("Error: ", e) + async def scan_and_connect_rizer(): global DEVICE_NAME @@ -80,9 +89,10 @@ async def scan_and_connect_rizer(): global tilt_service global CHARACTERISTICS_STEEING_UUID - global characteristics_steering + global steering_characteristics global stering_ready + global tilt_ready # Connecting to BLE Device client_is_connected = False @@ -100,31 +110,43 @@ async def scan_and_connect_rizer(): if (service.uuid == SERVICE_STEERING_UUID): steering_service = service - # print("[service uuid] ", SERVICE.uuid) + print("[service uuid] ", steering_service.uuid) - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - characteristics_steering = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") print(stering_ready) stering_ready = 1 - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - characteristics_tilt = characteristic + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: - - print(stering_ready) - print("IF2") - while stering_ready == 1: - await read_steering(client, characteristics_steering) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(stering_ready) + tilt_ready = 1 + + print("IF2") + print() + while (stering_ready == 1 and tilt_ready == 1): + print("forever!") + time.sleep(2) + + while (stering_ready == 1 and tilt_ready == 1): + await read_steering(client, steering_characteristics) + #await write_tilt(client, characteristics_tilt) + print("read and write!") + await write_tilt(client, tilt_characteristics) except exc.BleakError as e: From 1721295f8543e036b05857ef0598489b7279e09f Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 19:32:52 +0200 Subject: [PATCH 15/65] Error: Characteristic {char_specifier} was not found! read and write! still working on conncet_rizer.py CharacteristicUUID for tilt is probably wrong --- collector_scripts/connect_rizer.py | 19 ++---- collector_scripts/elite_rizer.py | 100 ++++++++++++++--------------- 2 files changed, 54 insertions(+), 65 deletions(-) diff --git a/collector_scripts/connect_rizer.py b/collector_scripts/connect_rizer.py index bc45e02..643c513 100644 --- a/collector_scripts/connect_rizer.py +++ b/collector_scripts/connect_rizer.py @@ -7,7 +7,8 @@ DEVICE_UUID = "fc:12:65:28:cb:44" SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" +SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + INCREASE_TILT_HEX = "060102" DECREASE_TILT_HEX = "060402" @@ -19,16 +20,14 @@ tilt_ready = 0 - CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt +CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt steering_characteristics = "" tilt_characteristics = "" - class BluetoothCallback: def __init__(self): self.received_steering_data = 0 # Initialize with None or any default value @@ -90,6 +89,7 @@ async def scan_and_connect_rizer(): global CHARACTERISTICS_STEEING_UUID global steering_characteristics + global tilt_characteristics global stering_ready global tilt_ready @@ -133,18 +133,13 @@ async def scan_and_connect_rizer(): if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): tilt_characteristics = characteristic - print(stering_ready) + print(tilt_ready) tilt_ready = 1 - print("IF2") - print() - while (stering_ready == 1 and tilt_ready == 1): - print("forever!") - time.sleep(2) - while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") await read_steering(client, steering_characteristics) - #await write_tilt(client, characteristics_tilt) + await write_tilt(client, tilt_characteristics) print("read and write!") await write_tilt(client, tilt_characteristics) diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 165e041..d93e4ff 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -1,20 +1,27 @@ import asyncio -from bleak import BleakScanner, BleakClient, exc +from bleak import BleakClient, exc import socket +import time DEVICE_NAME = "RIZER" DEVICE_UUID = "fc:12:65:28:cb:44" -DEVICE_ID = "" -SERVICE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering SERVICE_TILT_UUID = "347b00207635408b89188ff3949ce592" -SERVICE = "" + +steering_service = "" +tilt_service = "" + +stering_ready = 0 + + CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt characteristics_steering = "" +characteristics_tilt = "" @@ -57,62 +64,47 @@ async def read_steering(client, characteristic): await client.stop_notify(characteristic.uuid) # print("Test steering") except Exception as e: - print("Error: ", e) + print("Error: ", e) + +async def write_tilt(client, characteristic): + BluetoothCallback = BluetoothCallback() async def scan_and_connect_rizer(): global DEVICE_NAME - global SERVICE_UUID - global SERVICE + global SERVICE_STEERING_UUID + + global steering_service + global tilt_service global CHARACTERISTICS_STEEING_UUID global characteristics_steering - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICE_ID - # print(device) - # print("Test rizer") - if(device.name == DEVICE_NAME): - - DEVICE_ID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - ''' - try: - await stop_event.wait() - except KeyboardInterrupt: - print("Scanning stopped by user.") - scanner.stop() - ''' - await stop_event.wait() + global stering_ready - if(DEVICE_ID != ""): - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_ID, timeout=90) as client: - client_is_connected = True - print("Client connected to ", DEVICE_ID.name) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_UUID): - SERVICE = service - # print("[service uuid] ", SERVICE.uuid) - - if (SERVICE != ""): + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + #print("Client connected to ", DEVICE_ID.name) + #print("Client connected to ", DEVICE_ID) + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service + # print("[service uuid] ", SERVICE.uuid) + + if (steering_service != ""): # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: + for characteristic in steering_service.characteristics: if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): characteristics_steering = characteristic @@ -120,11 +112,13 @@ def callback(device, advertising_data): print("IF") print("FOR") while True: - await read_steering(client, characteristics_steering) - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_ID.name}: {e}") - # Add additional error handling or logging as needed - # raise + await read_steering(client, characteristics_steering) + + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise asyncio.run(scan_and_connect_rizer()) From cbbb396cc525c664ba04cf1d436e6f9b46247005 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 18 Apr 2024 19:48:41 +0200 Subject: [PATCH 16/65] elite_rizer.py is working. TODO cleanup --- collector_scripts/connect_rizer.py | 156 ----------------------------- collector_scripts/elite_rizer.py | 72 ++++++++----- collector_scripts/run_scripts.bat | 2 +- 3 files changed, 50 insertions(+), 180 deletions(-) delete mode 100644 collector_scripts/connect_rizer.py diff --git a/collector_scripts/connect_rizer.py b/collector_scripts/connect_rizer.py deleted file mode 100644 index 643c513..0000000 --- a/collector_scripts/connect_rizer.py +++ /dev/null @@ -1,156 +0,0 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time - -DEVICE_NAME = "RIZER" -DEVICE_UUID = "fc:12:65:28:cb:44" - -SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - - -INCREASE_TILT_HEX = "060102" -DECREASE_TILT_HEX = "060402" - -steering_service = "" -tilt_service = "" - -stering_ready = 0 -tilt_ready = 0 - - -CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - -steering_characteristics = "" -tilt_characteristics = "" - - -class BluetoothCallback: - def __init__(self): - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC - # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi - self.udp_port = 2222 - - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print(self.received_steering_data) - - self.send_steering_data_udp(self.received_steering_data) - - - def send_steering_data_udp(self, steering_data): - # Create a UDP socket - # print(steering_data) - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) - -async def read_steering(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - # print("Test steering") - except Exception as e: - print("Error: ", e) - -async def write_tilt(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - #TODO Write bt stuff - print("BT stuff") - await client.write_gatt_char(tilt_characteristics, bytes.fromhex(INCREASE_TILT_HEX), response=True) - except Exception as e: - print("Error: ", e) - - -async def scan_and_connect_rizer(): - global DEVICE_NAME - - global SERVICE_STEERING_UUID - - global steering_service - global tilt_service - - global CHARACTERISTICS_STEEING_UUID - global steering_characteristics - global tilt_characteristics - - global stering_ready - global tilt_ready - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - #print("Client connected to ", DEVICE_ID.name) - #print("Client connected to ", DEVICE_ID) - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(stering_ready) - stering_ready = 1 - - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - while (stering_ready == 1 and tilt_ready == 1): - print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print("read and write!") - await write_tilt(client, tilt_characteristics) - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - -asyncio.run(scan_and_connect_rizer()) - - - - diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index d93e4ff..44927a1 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -7,22 +7,25 @@ DEVICE_UUID = "fc:12:65:28:cb:44" SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b00207635408b89188ff3949ce592" +SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + +INCREASE_TILT_HEX = "060102" +DECREASE_TILT_HEX = "060402" steering_service = "" tilt_service = "" stering_ready = 0 - +tilt_ready = 0 CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b00017635408b89188ff3949ce592" # write tilt +CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt -characteristics_steering = "" -characteristics_tilt = "" - +steering_characteristics = "" +tilt_characteristics = "" class BluetoothCallback: @@ -67,9 +70,14 @@ async def read_steering(client, characteristic): print("Error: ", e) async def write_tilt(client, characteristic): - BluetoothCallback = BluetoothCallback() - - + bluetooth_callback = BluetoothCallback() + try: + #TODO Write bt stuff + print("BT stuff") + await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) + except Exception as e: + print("Error: ", e) + async def scan_and_connect_rizer(): global DEVICE_NAME @@ -80,9 +88,11 @@ async def scan_and_connect_rizer(): global tilt_service global CHARACTERISTICS_STEEING_UUID - global characteristics_steering + global steering_characteristics + global tilt_characteristics global stering_ready + global tilt_ready # Connecting to BLE Device client_is_connected = False @@ -99,29 +109,45 @@ async def scan_and_connect_rizer(): # print("service: ", service) if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - # print("[service uuid] ", SERVICE.uuid) + steering_service = service + print("[service uuid] ", steering_service.uuid) - if (steering_service != ""): + if (steering_service != ""): # print("SERVICE", SERVICE) for characteristic in steering_service.characteristics: if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - characteristics_steering = characteristic + steering_characteristics = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) print("IF") - print("FOR") - while True: - await read_steering(client, characteristics_steering) - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise + print(stering_ready) + stering_ready = 1 + + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) -asyncio.run(scan_and_connect_rizer()) + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + await read_steering(client, steering_characteristics) + await write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + print("read and write!") + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise +asyncio.run(scan_and_connect_rizer()) \ No newline at end of file diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 5d648f5..0dc60a0 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -4,7 +4,7 @@ REM Set the Python script to be executed 5 seconds before others set pre_execution_script="p110_connect.py" REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "connect_rizer.py", "headwind.py", "master_collector.py") +set python_scripts=("direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py") REM Start the pre-execution script start "" python %pre_execution_script% From c60745886279c160e0283c1ada0c091c5c5f1ccc Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 30 Apr 2024 11:34:34 +0000 Subject: [PATCH 17/65] deleted dead code in collecotr_scripts --- collector_scripts/master_collector.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 50cb483..041845b 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -95,15 +95,6 @@ def stop_udp_listener(self): if __name__ == "__main__": - ''' - try: - data_receiver.start_udp_listener() - except KeyboardInterrupt: - print("\nKeyboardInterrupt received. Stopping the loop.") - finally: - pass - # udp_unity_receive_socket.close() - ''' data_sender = DataSender() print("Master Collector script started...") @@ -173,11 +164,3 @@ def stop_udp_listener(self): # print("Roll_Value: ", roll_value) roll_value = roll_value["sensor_value"] data_sender.collect_roll(roll_value) - ''' - elif sock is udp_unity_receive_socket: - json_data = data.decode('utf-8') # Decode bytes to string - # value = json.loads(data.decode()) - unity_values = json.loads(json_data) - ble_fan_value = unity_values["bleFan"] - print("ble fan from unity: ", ble_fan_value) - ''' From b71819af85b1d1cd3645b9a9bb813cadb89c42ce Mon Sep 17 00:00:00 2001 From: PSenfft Date: Wed, 1 May 2024 11:57:31 +0000 Subject: [PATCH 18/65] added some comments --- collector_scripts/elite_rizer.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 44927a1..4ba29ac 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -2,6 +2,7 @@ from bleak import BleakClient, exc import socket import time +from master_collector import DataReceiver DEVICE_NAME = "RIZER" DEVICE_UUID = "fc:12:65:28:cb:44" @@ -19,6 +20,8 @@ stering_ready = 0 tilt_ready = 0 +stored_tilt_value = 0 + CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt @@ -75,8 +78,21 @@ async def write_tilt(client, characteristic): #TODO Write bt stuff print("BT stuff") await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) + stored_tilt_value += 0.5 except Exception as e: print("Error: ", e) + +# check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) +def get_new_tilt_value(): + try: + receiver.start_udp_listener() + tilt_value = receiver.get_tilt() + print("RIZER tilt: ", tilt_value) + if tilt_value != stored_tilt_value: + stored_tilt_value = tilt_value + + except Exception as e: + print("Error: ", e) async def scan_and_connect_rizer(): @@ -150,4 +166,6 @@ async def scan_and_connect_rizer(): # Add additional error handling or logging as needed # raise -asyncio.run(scan_and_connect_rizer()) \ No newline at end of file +asyncio.run(scan_and_connect_rizer()) + +'create UDP_Handler in own task' \ No newline at end of file From 1914fc4ae5c44e24b385ec167a5d487b97b403f1 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Wed, 1 May 2024 11:58:27 +0000 Subject: [PATCH 19/65] added diagrams --- .devcontainer/devcontainer.json | 1 + collector_scripts/master_collector.py | 7 +++++++ docs/rizer_classdiagram.wsd | 15 +++++++++++++++ docs/rizer_sequencediagram.wsd | 23 +++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 docs/rizer_classdiagram.wsd create mode 100644 docs/rizer_sequencediagram.wsd diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..76edfa6 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1 @@ +{"image":"mcr.microsoft.com/devcontainers/universal:2"} \ No newline at end of file diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 041845b..e9f91a7 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -57,11 +57,16 @@ def __init__(self): # self.udp_unity_receive_port = 12345 self.udp_unity_receive_socket = None self.ble_fan_speed = 0 + self.ble_tilt = 0 def get_fan_speed(self): print("Self ble fan speed: ", self.ble_fan_speed) return self.ble_fan_speed + + def get_tilt(self): + print("Self ble tilt: ", self.ble_tilt) + return self.ble_tilt def open_udp_socket(self): # Create a UDP socket @@ -82,8 +87,10 @@ def start_udp_listener(self): # value = json.loads(data.decode()) unity_values = json.loads(json_data) ble_fan_value = unity_values["bleFan"] + ble_tilt_value = unity_values["bleTilt"] # print("ble fan from unity: ", ble_fan_value) self.ble_fan_speed = ble_fan_value + self.ble_tilt = ble_tilt_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly except Exception as e: print(f"Error while receiving UDP data: {e}") diff --git a/docs/rizer_classdiagram.wsd b/docs/rizer_classdiagram.wsd new file mode 100644 index 0000000..c8589e5 --- /dev/null +++ b/docs/rizer_classdiagram.wsd @@ -0,0 +1,15 @@ +@startuml + +class Bar1 +class Bar2 +together { + class Together1 + class Together2 + class Together3 +} +Together1 - Together2 +Together2 - Together3 +Together2 -[hidden]--> Bar1 +Bar1 -[hidden]> Bar2 + +@enduml \ No newline at end of file diff --git a/docs/rizer_sequencediagram.wsd b/docs/rizer_sequencediagram.wsd new file mode 100644 index 0000000..e08beb5 --- /dev/null +++ b/docs/rizer_sequencediagram.wsd @@ -0,0 +1,23 @@ +@startuml +title "RIZER" + +participant Unity +participant Master_Collector +participant UDP_Handler +participant BluetoothCallback +participant RIZER + + + +UDP_Handler -> UDP_Handler: listen for tilt +Unity -> Master_Collector: send new tilt +Master_Collector -> UDP_Handler: send new tilt +BluetoothCallback -> RIZER: send tilt + +BluetoothCallback -> BluetoothCallback: listen for steering +RIZER -> BluetoothCallback: send steering +BluetoothCallback -> UDP_Handler: send steering +UDP_Handler -> Master_Collector: send steering +Master_Collector -> Unity: send steering + +@enduml \ No newline at end of file From 86c586b95ecdc8902f047e29057f5ae1e6900dfd Mon Sep 17 00:00:00 2001 From: PSenfft Date: Wed, 1 May 2024 12:11:24 +0000 Subject: [PATCH 20/65] updated sequencediagram --- docs/rizer_sequencediagram.wsd | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/rizer_sequencediagram.wsd b/docs/rizer_sequencediagram.wsd index e08beb5..0ec3a0e 100644 --- a/docs/rizer_sequencediagram.wsd +++ b/docs/rizer_sequencediagram.wsd @@ -8,14 +8,25 @@ participant BluetoothCallback participant RIZER +loop + UDP_Handler -> UDP_Handler: listen for tilt +end -UDP_Handler -> UDP_Handler: listen for tilt Unity -> Master_Collector: send new tilt Master_Collector -> UDP_Handler: send new tilt -BluetoothCallback -> RIZER: send tilt -BluetoothCallback -> BluetoothCallback: listen for steering -RIZER -> BluetoothCallback: send steering +UDP_Handler -> BluetoothCallback: computed tilt commands + +loop "tilt 0.5" + BluetoothCallback -> RIZER: send tilt +end + +loop + BluetoothCallback -> BluetoothCallback: listen for steering + RIZER -> BluetoothCallback: send steering +end + + BluetoothCallback -> UDP_Handler: send steering UDP_Handler -> Master_Collector: send steering Master_Collector -> Unity: send steering From 5d259cd23ddf20447fab060edf40c4c14145a649 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 2 May 2024 12:16:43 +0200 Subject: [PATCH 21/65] added some comments --- collector_scripts/elite_rizer.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 44927a1..53e375b 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -19,6 +19,8 @@ stering_ready = 0 tilt_ready = 0 +udp_tilt_data = 0 + CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt @@ -51,7 +53,7 @@ async def notify_steering_callback(self, sender, data): self.send_steering_data_udp(self.received_steering_data) - + #send steering data over udp def send_steering_data_udp(self, steering_data): # Create a UDP socket # print(steering_data) @@ -59,6 +61,11 @@ def send_steering_data_udp(self, steering_data): # Send speed_data udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + def listening_udp(self, udp_tilt_data): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) + print("Hello: ", udp_tilt_data) + async def read_steering(client, characteristic): bluetooth_callback = BluetoothCallback() try: @@ -150,4 +157,10 @@ async def scan_and_connect_rizer(): # Add additional error handling or logging as needed # raise -asyncio.run(scan_and_connect_rizer()) \ No newline at end of file +asyncio.run(scan_and_connect_rizer()) +# TODO +# connect rizer over BLE +# ascynchron listen udp +# asynchron listen steering +# write steering over udp +# write Rizer over BLE \ No newline at end of file From cf3640987d5effa04c664e520bac6380064f3370 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 2 May 2024 13:52:24 +0200 Subject: [PATCH 22/65] restrucure rizer file --- collector_scripts/elite_rizer.py | 2 - collector_scripts/elite_rizer2.py | 90 +++++++++++++++++++++++++++++++ docs/rizer_classdiagram.wsd | 21 ++++---- 3 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 collector_scripts/elite_rizer2.py diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 314eefd..647dd55 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -10,7 +10,6 @@ SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - INCREASE_TILT_HEX = "060102" DECREASE_TILT_HEX = "060402" @@ -22,7 +21,6 @@ stored_tilt_value = 0 - CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt diff --git a/collector_scripts/elite_rizer2.py b/collector_scripts/elite_rizer2.py new file mode 100644 index 0000000..e108958 --- /dev/null +++ b/collector_scripts/elite_rizer2.py @@ -0,0 +1,90 @@ +import asyncio +from bleak import BleakClient, exc +import socket +import time +from master_collector import DataReceiver + +#Global BLE variables +steering_service = "" +tilt_service = "" + +steering_characteristics = "" +tilt_characteristics = "" + +stering_ready = 0 +tilt_ready = 0 + +stored_tilt_value = 0 + + +async def scan_and_connect_rizer(): + + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" + + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering + SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + INCREASE_TILT_HEX = "060102" + DECREASE_TILT_HEX = "060402" + + CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering + CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + await read_steering(client, steering_characteristics) + await write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + print("read and write!") + + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + +asyncio.run(scan_and_connect_rizer()) \ No newline at end of file diff --git a/docs/rizer_classdiagram.wsd b/docs/rizer_classdiagram.wsd index c8589e5..931741c 100644 --- a/docs/rizer_classdiagram.wsd +++ b/docs/rizer_classdiagram.wsd @@ -1,15 +1,16 @@ @startuml -class Bar1 -class Bar2 -together { - class Together1 - class Together2 - class Together3 +class BluetoothCallback { + notify_steering_callback + read_steering + write_tilt + scan_and_connect_rizer +} + +class UDP_Handler{ + send_steering_data_udp + listening_udp + get_new_tilt_value } -Together1 - Together2 -Together2 - Together3 -Together2 -[hidden]--> Bar1 -Bar1 -[hidden]> Bar2 @enduml \ No newline at end of file From 85fdd61c22b371c25c2150f89a0ce562fb93bdc6 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 7 May 2024 15:40:35 +0200 Subject: [PATCH 23/65] working on elite_rizer code rewrite --- collector_scripts/elite_rizer.py | 10 +- collector_scripts/elite_rizer3.py | 166 ++++++++++++++++++++++++++++++ collector_scripts/run_scripts.bat | 2 +- 3 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 collector_scripts/elite_rizer3.py diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 647dd55..8973e22 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -21,7 +21,7 @@ stored_tilt_value = 0 -CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt @@ -106,12 +106,12 @@ async def scan_and_connect_rizer(): global steering_service global tilt_service - global CHARACTERISTICS_STEEING_UUID + global CHARACTERISTICS_STEERING_UUID global steering_characteristics global tilt_characteristics - global stering_ready - global tilt_ready + global stering_ready #connection to steering BLE service ready + global tilt_ready #connection to tilt BLE service ready # Connecting to BLE Device client_is_connected = False @@ -135,7 +135,7 @@ async def scan_and_connect_rizer(): # print("SERVICE", SERVICE) for characteristic in steering_service.characteristics: - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEERING_UUID): steering_characteristics = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) print("IF") diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py new file mode 100644 index 0000000..4db0376 --- /dev/null +++ b/collector_scripts/elite_rizer3.py @@ -0,0 +1,166 @@ +import asyncio +from bleak import BleakClient, exc +import socket +import time +from master_collector import DataReceiver + +tilt_received = 0 #received tilt data form UDP. Ready to send over BLE +steering_received = 0 #received steering data from RIZER. Ready to send over UDP + +class UDP_Handler: + def __init__(self): + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + self.udp_port = 2222 + + def main(self): + if (steering_received == 1): + self.send_steering_data_udp(self.steering_data) + try: + self.receiver.start_udp_listener() + tilt_value = UDP_Handler.receiver.get_tilt() + self.check_new_tilt + + except Exception as e: + print("Error: ", e) + + #send steering data over udp + def send_steering_data_udp(self, steering_data): + # Create a UDP socket + # print(steering_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + + def listening_udp(self, udp_tilt_data): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) + print("Hello: ", udp_tilt_data) + + # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) + def check_new_tilt(self): + print("RIZER tilt: ", self.tilt_value) + if UDP_Handler.tilt_value != stored_tilt_value: + stored_tilt_value = UDP_Handler.tilt_value + tilt_received = 1 + +class BLE_Handler: + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" + + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering + SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + INCREASE_TILT_HEX = "060102" + DECREASE_TILT_HEX = "060402" + + CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering + CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + steering_characteristics + tilt_characteristics + + steering_service + tilt_service + + async def read_and_ride_rizer(self, client): + while(True): + await self.read_steering(client, steering_characteristics) + await self.write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + + + async def read_steering(self, client, characteristic): + try: + await client.start_notify(characteristic, self.notify_steering_callback) + # await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + except Exception as e: + print("Error: ", e) + + async def write_tilt(self, client, characteristic): + try: + await client.write_gatt_char(self.CHARACTERISTIC_TILT_UUID, bytes.fromhex(self.INCREASE_TILT_HEX), response=True) + stored_tilt_value += 0.5 + tilt_received = 0 + except Exception as e: + print("Error: ", e) + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + udp.send_steering_data_udp(self.received_steering_data) + + + async def __init__(self): + global steering_characteristics + global tilt_characteristics + + global steering_service + global tilt_service + + global stering_ready #connection to steering BLE service ready + global tilt_ready #connection to tilt BLE service ready + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(self.DEVICE_UUID, timeout=90) as client: + client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (service.uuid == self.SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + if (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + asyncio.create_task(self.read_and_ride_rizer(client)) + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + +udp = UDP_Handler +ble = BLE_Handler + +asyncio.create_task(udp.main) +asyncio.create_task(ble.read_and_ride_rizer) + diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 0dc60a0..21e4632 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -4,7 +4,7 @@ REM Set the Python script to be executed 5 seconds before others set pre_execution_script="p110_connect.py" REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py") +set python_scripts=("direto_xr.py", "elite_rizer3.py", "headwind.py", "master_collector.py") REM Start the pre-execution script start "" python %pre_execution_script% From 4486cd82c2f43571d882a7f90172b418e1c3d480 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 7 May 2024 17:48:57 +0200 Subject: [PATCH 24/65] udp listener not working --- collector_scripts/elite_rizer.py | 1 + collector_scripts/elite_rizer3.py | 72 ++++++++----- collector_scripts/elite_rizer_old.py | 153 +++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 27 deletions(-) create mode 100644 collector_scripts/elite_rizer_old.py diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 8973e22..18d5fe0 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -87,6 +87,7 @@ async def write_tilt(client, characteristic): # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) def get_new_tilt_value(): + receiver = DataReceiver() try: receiver.start_udp_listener() tilt_value = receiver.get_tilt() diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 4db0376..3d75af1 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -6,28 +6,35 @@ tilt_received = 0 #received tilt data form UDP. Ready to send over BLE steering_received = 0 #received steering data from RIZER. Ready to send over UDP +tilt_value = 0 +current_tilt_value_on_razer = 0 class UDP_Handler: + global tilt_value + def __init__(self): self.received_steering_data = 0 # Initialize with None or any default value self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost self.udp_port = 2222 + print("udp handler started") - def main(self): - if (steering_received == 1): - self.send_steering_data_udp(self.steering_data) - try: - self.receiver.start_udp_listener() - tilt_value = UDP_Handler.receiver.get_tilt() - self.check_new_tilt + async def main(self): + receiver = DataReceiver() + while(True): + if (steering_received == 1): + self.send_steering_data_udp(self.steering_data) + try: + self.listening_udp() + tilt_value = receiver.get_tilt() + self.check_new_tilt(tilt_value) - except Exception as e: - print("Error: ", e) + except Exception as e: + print("Error: ", e) #send steering data over udp def send_steering_data_udp(self, steering_data): # Create a UDP socket - # print(steering_data) + print(steering_data) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: # Send speed_data udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) @@ -38,10 +45,13 @@ def listening_udp(self, udp_tilt_data): print("Hello: ", udp_tilt_data) # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) - def check_new_tilt(self): - print("RIZER tilt: ", self.tilt_value) - if UDP_Handler.tilt_value != stored_tilt_value: - stored_tilt_value = UDP_Handler.tilt_value + def check_new_tilt(self, udp_tilt_value): + print("check new tilt") + global tilt_received + global tilt_value + print("RIZER tilt: ", udp_tilt_value) + if tilt_value != udp_tilt_value: + tilt_value = udp_tilt_value tilt_received = 1 class BLE_Handler: @@ -58,16 +68,18 @@ class BLE_Handler: CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - steering_characteristics - tilt_characteristics + global steering_characteristics + global tilt_characteristics + + global steering_service + global tilt_service - steering_service - tilt_service + global current_tilt_value_on_razer async def read_and_ride_rizer(self, client): while(True): await self.read_steering(client, steering_characteristics) - await self.write_tilt(client, tilt_characteristics) + await self.write_tilt(client) print(tilt_characteristics) @@ -79,10 +91,11 @@ async def read_steering(self, client, characteristic): except Exception as e: print("Error: ", e) - async def write_tilt(self, client, characteristic): + async def write_tilt(self, client): + global tilt_received try: await client.write_gatt_char(self.CHARACTERISTIC_TILT_UUID, bytes.fromhex(self.INCREASE_TILT_HEX), response=True) - stored_tilt_value += 0.5 + current_tilt_value_on_razer += 0.5 tilt_received = 0 except Exception as e: print("Error: ", e) @@ -102,7 +115,7 @@ async def notify_steering_callback(self, sender, data): udp.send_steering_data_udp(self.received_steering_data) - async def __init__(self): + async def main(self): global steering_characteristics global tilt_characteristics @@ -151,16 +164,21 @@ async def __init__(self): if (stering_ready == 1 and tilt_ready == 1): print("all ready!") - asyncio.create_task(self.read_and_ride_rizer(client)) + self.read_and_ride_rizer() except exc.BleakError as e: print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") # Add additional error handling or logging as needed # raise -udp = UDP_Handler -ble = BLE_Handler +async def main(): + udp_handler_task = asyncio.create_task(udp.main()) + ble_handler_task = asyncio.create_task(ble.main()) -asyncio.create_task(udp.main) -asyncio.create_task(ble.read_and_ride_rizer) + await asyncio.gather(udp_handler_task, ble_handler_task) +# Creating instances of handlers +udp = UDP_Handler() +ble = BLE_Handler() +print("asyncio start") +asyncio.run(main()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer_old.py b/collector_scripts/elite_rizer_old.py new file mode 100644 index 0000000..44927a1 --- /dev/null +++ b/collector_scripts/elite_rizer_old.py @@ -0,0 +1,153 @@ +import asyncio +from bleak import BleakClient, exc +import socket +import time + +DEVICE_NAME = "RIZER" +DEVICE_UUID = "fc:12:65:28:cb:44" + +SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + +INCREASE_TILT_HEX = "060102" +DECREASE_TILT_HEX = "060402" + +steering_service = "" +tilt_service = "" + +stering_ready = 0 +tilt_ready = 0 + + +CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + +steering_characteristics = "" +tilt_characteristics = "" + + +class BluetoothCallback: + def __init__(self): + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC + # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi + self.udp_port = 2222 + + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + self.send_steering_data_udp(self.received_steering_data) + + + def send_steering_data_udp(self, steering_data): + # Create a UDP socket + # print(steering_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + +async def read_steering(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + # print("Test steering") + except Exception as e: + print("Error: ", e) + +async def write_tilt(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + #TODO Write bt stuff + print("BT stuff") + await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) + except Exception as e: + print("Error: ", e) + + +async def scan_and_connect_rizer(): + global DEVICE_NAME + + global SERVICE_STEERING_UUID + + global steering_service + global tilt_service + + global CHARACTERISTICS_STEEING_UUID + global steering_characteristics + global tilt_characteristics + + global stering_ready + global tilt_ready + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + #print("Client connected to ", DEVICE_ID.name) + #print("Client connected to ", DEVICE_ID) + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + await read_steering(client, steering_characteristics) + await write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + print("read and write!") + + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + +asyncio.run(scan_and_connect_rizer()) \ No newline at end of file From 01a1e3083022dc1ca8f16b7dff80112b621a5b9a Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 7 May 2024 18:03:40 +0200 Subject: [PATCH 25/65] still working on UDP listener --- collector_scripts/elite_rizer.py | 2 +- collector_scripts/elite_rizer3.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 18d5fe0..0fc487f 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -60,7 +60,7 @@ def send_steering_data_udp(self, steering_data): # Send speed_data udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) - def listening_udp(self, udp_tilt_data): + def listening_udp(self): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) print("Hello: ", udp_tilt_data) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 3d75af1..69d3641 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -20,11 +20,12 @@ def __init__(self): async def main(self): receiver = DataReceiver() + receiver.open_udp_socket() while(True): if (steering_received == 1): self.send_steering_data_udp(self.steering_data) try: - self.listening_udp() + receiver.start_udp_listener() tilt_value = receiver.get_tilt() self.check_new_tilt(tilt_value) From ede6b1cc752618a90e63e3c163ea2bd2265c9b7e Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 7 May 2024 18:52:00 +0200 Subject: [PATCH 26/65] seems udp is working. Test it with unity now --- collector_scripts/elite_rizer3.py | 28 +++++++++++++++++----------- collector_scripts/elite_rizer_old.py | 1 - 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 69d3641..0fbcf0c 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -17,17 +17,17 @@ def __init__(self): self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost self.udp_port = 2222 print("udp handler started") + async def main(self): receiver = DataReceiver() - receiver.open_udp_socket() while(True): - if (steering_received == 1): + if (steering_received == 1): #when steering value has chanched, send it to unity self.send_steering_data_udp(self.steering_data) try: - receiver.start_udp_listener() - tilt_value = receiver.get_tilt() - self.check_new_tilt(tilt_value) + #self.listening_udp maybe not needed + tilt_value = receiver.get_tilt() #read tilt from unity + self.check_new_tilt(tilt_value) #check if tilt value has changed or is still the same except Exception as e: print("Error: ", e) @@ -40,17 +40,17 @@ def send_steering_data_udp(self, steering_data): # Send speed_data udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) - def listening_udp(self, udp_tilt_data): + def listening_udp(self): + udp_tilt_data = 0 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) print("Hello: ", udp_tilt_data) # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) def check_new_tilt(self, udp_tilt_value): - print("check new tilt") global tilt_received global tilt_value - print("RIZER tilt: ", udp_tilt_value) + #print("RIZER tilt: ", udp_tilt_value) if tilt_value != udp_tilt_value: tilt_value = udp_tilt_value tilt_received = 1 @@ -79,15 +79,21 @@ class BLE_Handler: async def read_and_ride_rizer(self, client): while(True): - await self.read_steering(client, steering_characteristics) - await self.write_tilt(client) + #await self.read_steering(client, steering_characteristics) + self.read_steering(client, steering_characteristics) + print("read steering rizer") + if (tilt_received == 1): + await self.write_tilt(client) + tilt_received = 0 + print("tilt writed") print(tilt_characteristics) async def read_steering(self, client, characteristic): + print("read steering") try: await client.start_notify(characteristic, self.notify_steering_callback) - # await asyncio.sleep(10) # keeps the connection open for 10 seconds + await asyncio.sleep(1) # keeps the connection open for 10 seconds await client.stop_notify(characteristic.uuid) except Exception as e: print("Error: ", e) diff --git a/collector_scripts/elite_rizer_old.py b/collector_scripts/elite_rizer_old.py index 44927a1..5e5b58b 100644 --- a/collector_scripts/elite_rizer_old.py +++ b/collector_scripts/elite_rizer_old.py @@ -9,7 +9,6 @@ SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - INCREASE_TILT_HEX = "060102" DECREASE_TILT_HEX = "060402" From 29494952eae6299ac8a3be1c018315fd3fba9fd1 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 7 May 2024 19:17:54 +0200 Subject: [PATCH 27/65] works not parallel :/ --- collector_scripts/elite_rizer3.py | 14 ++++++++------ collector_scripts/master_collector.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 0fbcf0c..935ba16 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -79,8 +79,8 @@ class BLE_Handler: async def read_and_ride_rizer(self, client): while(True): - #await self.read_steering(client, steering_characteristics) - self.read_steering(client, steering_characteristics) + await self.read_steering(client, steering_characteristics) + #self.read_steering(client, steering_characteristics) print("read steering rizer") if (tilt_received == 1): await self.write_tilt(client) @@ -93,7 +93,7 @@ async def read_steering(self, client, characteristic): print("read steering") try: await client.start_notify(characteristic, self.notify_steering_callback) - await asyncio.sleep(1) # keeps the connection open for 10 seconds + await asyncio.sleep(10) # keeps the connection open for 10 seconds await client.stop_notify(characteristic.uuid) except Exception as e: print("Error: ", e) @@ -133,10 +133,11 @@ async def main(self): global tilt_ready #connection to tilt BLE service ready # Connecting to BLE Device + print("Connecting to BLE Device") client_is_connected = False while(client_is_connected == False): try: - async with BleakClient(self.DEVICE_UUID, timeout=90) as client: + with BleakClient(self.DEVICE_UUID, timeout=90) as client: client_is_connected = True print("Client connected to ", self.DEVICE_UUID) for service in client.services: @@ -179,10 +180,11 @@ async def main(self): # raise async def main(): - udp_handler_task = asyncio.create_task(udp.main()) ble_handler_task = asyncio.create_task(ble.main()) + udp_handler_task = asyncio.create_task(udp.main()) - await asyncio.gather(udp_handler_task, ble_handler_task) + await ble_handler_task + await udp_handler_task # Creating instances of handlers udp = UDP_Handler() diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index e9f91a7..7d1dad5 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -65,7 +65,7 @@ def get_fan_speed(self): return self.ble_fan_speed def get_tilt(self): - print("Self ble tilt: ", self.ble_tilt) + #print("Self ble tilt: ", self.ble_tilt) return self.ble_tilt def open_udp_socket(self): From 67cead4f7f8639d42f36e0f1e7d243422c384a97 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 7 May 2024 19:18:36 +0200 Subject: [PATCH 28/65] works not parallel :/ --- collector_scripts/elite_rizer3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 935ba16..ae645c2 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -137,7 +137,7 @@ async def main(self): client_is_connected = False while(client_is_connected == False): try: - with BleakClient(self.DEVICE_UUID, timeout=90) as client: + async with BleakClient(self.DEVICE_UUID, timeout=90) as client: client_is_connected = True print("Client connected to ", self.DEVICE_UUID) for service in client.services: From da6d6987ce1c05995799d0e5c948b303f9e62b34 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 15:19:15 +0200 Subject: [PATCH 29/65] still working on multithread tasks --- .vscode/launch.json | 16 +++++++++++++++ collector_scripts/elite_rizer3.py | 33 +++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0bfa3c2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": "false" + } + ] +} \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index ae645c2..a5e7d54 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -23,7 +23,7 @@ async def main(self): receiver = DataReceiver() while(True): if (steering_received == 1): #when steering value has chanched, send it to unity - self.send_steering_data_udp(self.steering_data) + await self.send_steering_data_udp(self.steering_data) try: #self.listening_udp maybe not needed tilt_value = receiver.get_tilt() #read tilt from unity @@ -75,9 +75,12 @@ class BLE_Handler: global steering_service global tilt_service + global BleakClient + global current_tilt_value_on_razer async def read_and_ride_rizer(self, client): + print("read and write") while(True): await self.read_steering(client, steering_characteristics) #self.read_steering(client, steering_characteristics) @@ -122,22 +125,28 @@ async def notify_steering_callback(self, sender, data): udp.send_steering_data_udp(self.received_steering_data) - async def main(self): + async def ble_handler_init(self): global steering_characteristics global tilt_characteristics global steering_service global tilt_service - global stering_ready #connection to steering BLE service ready + global steering_ready #connection to steering BLE service ready global tilt_ready #connection to tilt BLE service ready + + global bleakClient # Connecting to BLE Device print("Connecting to BLE Device") client_is_connected = False + steering_ready = 0 + tilt_ready = 0 + tilt_received = 0 while(client_is_connected == False): try: async with BleakClient(self.DEVICE_UUID, timeout=90) as client: + client_is_connected = True print("Client connected to ", self.DEVICE_UUID) for service in client.services: @@ -154,8 +163,8 @@ async def main(self): # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) print("IF") - print(stering_ready) - stering_ready = 1 + print(steering_ready) + steering_ready = 1 if (service.uuid == self.SERVICE_TILT_UUID): tilt_service = service @@ -170,9 +179,10 @@ async def main(self): print(tilt_ready) tilt_ready = 1 - if (stering_ready == 1 and tilt_ready == 1): + if (steering_ready == 1 and tilt_ready == 1): print("all ready!") - self.read_and_ride_rizer() + await self.read_and_ride_rizer(client) + except exc.BleakError as e: print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") @@ -180,11 +190,14 @@ async def main(self): # raise async def main(): - ble_handler_task = asyncio.create_task(ble.main()) - udp_handler_task = asyncio.create_task(udp.main()) + ble_handler_task = asyncio.create_task(ble.ble_handler_init()) + print("ble main start") + #udp_handler_task = asyncio.create_task(udp.main()) + #print("udp main start") await ble_handler_task - await udp_handler_task + + #await udp_handler_task # Creating instances of handlers udp = UDP_Handler() From aedc945a98ec316cd537842489c2d741b855221a Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 16:26:46 +0200 Subject: [PATCH 30/65] still working on multithread --- collector_scripts/elite_rizer3.py | 121 +++++++++++++++++------------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index a5e7d54..1abad50 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -4,13 +4,15 @@ import time from master_collector import DataReceiver -tilt_received = 0 #received tilt data form UDP. Ready to send over BLE -steering_received = 0 #received steering data from RIZER. Ready to send over UDP -tilt_value = 0 -current_tilt_value_on_razer = 0 +#global variables +tilt_received #received tilt data form UDP. Ready to send over BLE +steering_received #received steering data from RIZER. Ready to send over UDP +tilt_value +current_tilt_value_on_razer class UDP_Handler: global tilt_value + global tilt_received def __init__(self): self.received_steering_data = 0 # Initialize with None or any default value @@ -32,6 +34,12 @@ async def main(self): except Exception as e: print("Error: ", e) + def set_tilt_received(received): + tilt_received = received + + def get_til_received(): + return tilt_received + #send steering data over udp def send_steering_data_udp(self, steering_data): # Create a UDP socket @@ -75,24 +83,26 @@ class BLE_Handler: global steering_service global tilt_service - global BleakClient + global client global current_tilt_value_on_razer - async def read_and_ride_rizer(self, client): + async def read_and_ride_rizer(self): + global tilt_received + global tilt_characteristics print("read and write") while(True): - await self.read_steering(client, steering_characteristics) + await self.read_steering(self.steering_characteristics) #self.read_steering(client, steering_characteristics) print("read steering rizer") if (tilt_received == 1): - await self.write_tilt(client) + await self.write_tilt(self.bleakClient) tilt_received = 0 print("tilt writed") - print(tilt_characteristics) + print(self.tilt_characteristics) - async def read_steering(self, client, characteristic): + async def read_steering(self, characteristic): print("read steering") try: await client.start_notify(characteristic, self.notify_steering_callback) @@ -125,7 +135,7 @@ async def notify_steering_callback(self, sender, data): udp.send_steering_data_udp(self.received_steering_data) - async def ble_handler_init(self): + def __init__(self): global steering_characteristics global tilt_characteristics @@ -135,53 +145,56 @@ async def ble_handler_init(self): global steering_ready #connection to steering BLE service ready global tilt_ready #connection to tilt BLE service ready - global bleakClient + global client # Connecting to BLE Device print("Connecting to BLE Device") client_is_connected = False - steering_ready = 0 - tilt_ready = 0 - tilt_received = 0 + self.steering_ready = 0 + self.tilt_ready = 0 + self.tilt_received = 0 + self.steering_characteristics = None + self.tilt_characteristics = 0 + self.client = None + while(client_is_connected == False): try: - async with BleakClient(self.DEVICE_UUID, timeout=90) as client: - - client_is_connected = True - print("Client connected to ", self.DEVICE_UUID) - for service in client.services: - if (service.uuid == self.SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(steering_ready) - steering_ready = 1 - - if (service.uuid == self.SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - if (steering_ready == 1 and tilt_ready == 1): - print("all ready!") - await self.read_and_ride_rizer(client) + client = BleakClient(self.DEVICE_UUID, timeout=90) + #with BleakClient(self.DEVICE_UUID, timeout=90) as client: + client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(steering_ready) + steering_ready = 1 + + if (service.uuid == self.SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + if (steering_ready == 1 and tilt_ready == 1): + print("all ready!") except exc.BleakError as e: @@ -190,8 +203,8 @@ async def ble_handler_init(self): # raise async def main(): - ble_handler_task = asyncio.create_task(ble.ble_handler_init()) - print("ble main start") + ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) + print("ble main started") #udp_handler_task = asyncio.create_task(udp.main()) #print("udp main start") From d939f91d01778fffccc3571c4eef4eee132ecc4f Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 14 May 2024 14:45:39 +0000 Subject: [PATCH 31/65] working on multithread --- collector_scripts/elite_rizer3.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 1abad50..8e29d1d 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -5,24 +5,29 @@ from master_collector import DataReceiver #global variables -tilt_received #received tilt data form UDP. Ready to send over BLE -steering_received #received steering data from RIZER. Ready to send over UDP -tilt_value -current_tilt_value_on_razer +tilt_received = 0 #received tilt data form UDP. Ready to send over BLE +steering_received = 0 #received steering data from RIZER. Ready to send over UDP +tilt_value = None +current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE + class UDP_Handler: - global tilt_value - global tilt_received def __init__(self): - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - self.udp_port = 2222 - print("udp handler started") + global tilt_value + global tilt_received + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + self.udp_port = 2222 + print("udp handler started") async def main(self): + global tilt_value + global steering_received receiver = DataReceiver() + self.steering_data = None + steering_received = None while(True): if (steering_received == 1): #when steering value has chanched, send it to unity await self.send_steering_data_udp(self.steering_data) From c6e9b0dcf1c7993a2450eeac1691d670c20c6cc8 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 17:00:32 +0200 Subject: [PATCH 32/65] working therad --- collector_scripts/elite_rizer3.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 8e29d1d..0b6870f 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -9,6 +9,7 @@ steering_received = 0 #received steering data from RIZER. Ready to send over UDP tilt_value = None current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE +client = None class UDP_Handler: @@ -88,20 +89,19 @@ class BLE_Handler: global steering_service global tilt_service - global client - global current_tilt_value_on_razer async def read_and_ride_rizer(self): global tilt_received global tilt_characteristics + global client print("read and write") while(True): await self.read_steering(self.steering_characteristics) #self.read_steering(client, steering_characteristics) print("read steering rizer") if (tilt_received == 1): - await self.write_tilt(self.bleakClient) + await self.write_tilt(client) tilt_received = 0 print("tilt writed") print(self.tilt_characteristics) @@ -116,7 +116,8 @@ async def read_steering(self, characteristic): except Exception as e: print("Error: ", e) - async def write_tilt(self, client): + async def write_tilt(self): + global client global tilt_received try: await client.write_gatt_char(self.CHARACTERISTIC_TILT_UUID, bytes.fromhex(self.INCREASE_TILT_HEX), response=True) @@ -160,7 +161,6 @@ def __init__(self): self.tilt_received = 0 self.steering_characteristics = None self.tilt_characteristics = 0 - self.client = None while(client_is_connected == False): try: From 52428bc22cfb09732f608325a59695bf57579ec5 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 14 May 2024 15:03:03 +0000 Subject: [PATCH 33/65] working on multithread --- collector_scripts/elite_rizer3.py | 69 ++++++++++++++++--------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 0b6870f..9697057 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -166,40 +166,41 @@ def __init__(self): try: client = BleakClient(self.DEVICE_UUID, timeout=90) #with BleakClient(self.DEVICE_UUID, timeout=90) as client: - client_is_connected = True - print("Client connected to ", self.DEVICE_UUID) - for service in client.services: - if (service.uuid == self.SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): - self.steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(steering_ready) - steering_ready = 1 - - if (service.uuid == self.SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - if (steering_ready == 1 and tilt_ready == 1): - print("all ready!") + with client: + client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(steering_ready) + steering_ready = 1 + + if (service.uuid == self.SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + if (steering_ready == 1 and tilt_ready == 1): + print("all ready!") except exc.BleakError as e: From c3703acd807d5aac26bc53a932f36532a406a2b2 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 17:16:08 +0200 Subject: [PATCH 34/65] still working --- collector_scripts/elite_rizer3.py | 83 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 9697057..d446ec6 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -141,7 +141,7 @@ async def notify_steering_callback(self, sender, data): udp.send_steering_data_udp(self.received_steering_data) - def __init__(self): + async def __init__(self): global steering_characteristics global tilt_characteristics @@ -166,45 +166,48 @@ def __init__(self): try: client = BleakClient(self.DEVICE_UUID, timeout=90) #with BleakClient(self.DEVICE_UUID, timeout=90) as client: - with client: - client_is_connected = True - print("Client connected to ", self.DEVICE_UUID) - for service in client.services: - if (service.uuid == self.SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): - self.steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(steering_ready) - steering_ready = 1 - - if (service.uuid == self.SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - if (steering_ready == 1 and tilt_ready == 1): - print("all ready!") - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + await client.connect() + client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(steering_ready) + steering_ready = 1 + + if (service.uuid == self.SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + if (steering_ready == 1 and tilt_ready == 1): + print("all ready!") + + finally: + await client.disconnect + print("disconnect") + + #except exc.BleakError as e: + #print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") # Add additional error handling or logging as needed # raise From f5d9bb5eb5e735b280f1bfb8e4de52d07c35abf3 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 14 May 2024 15:19:32 +0000 Subject: [PATCH 35/65] working on multithread --- collector_scripts/elite_rizer3.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index d446ec6..298389f 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -161,7 +161,9 @@ async def __init__(self): self.tilt_received = 0 self.steering_characteristics = None self.tilt_characteristics = 0 - + + async def async_init(self): + while(client_is_connected == False): try: client = BleakClient(self.DEVICE_UUID, timeout=90) @@ -217,7 +219,9 @@ async def main(): #udp_handler_task = asyncio.create_task(udp.main()) #print("udp main start") - await ble_handler_task + await ble.async_init() + await ble_handler_task() + #await udp_handler_task From 3647ab143c9261add70c7f2f53f6ad6b9be3b086 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 14 May 2024 15:21:28 +0000 Subject: [PATCH 36/65] working on multithread --- collector_scripts/elite_rizer3.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 298389f..d7af319 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -218,8 +218,6 @@ async def main(): print("ble main started") #udp_handler_task = asyncio.create_task(udp.main()) #print("udp main start") - - await ble.async_init() await ble_handler_task() @@ -228,5 +226,9 @@ async def main(): # Creating instances of handlers udp = UDP_Handler() ble = BLE_Handler() + +# Call async_init right after the instance is created +asyncio.run(ble.async_init()) + print("asyncio start") asyncio.run(main()) \ No newline at end of file From 0f4ac4a2e85c57f1a067501d5773cc1e4a9a5018 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 14 May 2024 16:01:52 +0000 Subject: [PATCH 37/65] still errors --- collector_scripts/elite_rizer3.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index d7af319..26683da 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -141,7 +141,7 @@ async def notify_steering_callback(self, sender, data): udp.send_steering_data_udp(self.received_steering_data) - async def __init__(self): + def __init__(self): global steering_characteristics global tilt_characteristics @@ -151,7 +151,7 @@ async def __init__(self): global steering_ready #connection to steering BLE service ready global tilt_ready #connection to tilt BLE service ready - global client + global client_is_connected # Connecting to BLE Device print("Connecting to BLE Device") @@ -163,12 +163,12 @@ async def __init__(self): self.tilt_characteristics = 0 async def async_init(self): - + global client_is_connected while(client_is_connected == False): try: client = BleakClient(self.DEVICE_UUID, timeout=90) #with BleakClient(self.DEVICE_UUID, timeout=90) as client: - await client.connect() + client.connect() client_is_connected = True print("Client connected to ", self.DEVICE_UUID) for service in client.services: @@ -204,14 +204,11 @@ async def async_init(self): if (steering_ready == 1 and tilt_ready == 1): print("all ready!") - finally: - await client.disconnect - print("disconnect") - - #except exc.BleakError as e: - #print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + #Add additional error handling or logging as needed + #raise async def main(): ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) From 0882f05f22a1abf5fb7e5fa0b1315f6f67c9dbe2 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Tue, 14 May 2024 16:06:27 +0000 Subject: [PATCH 38/65] maybe working now --- collector_scripts/elite_rizer3.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 26683da..1d5342f 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -168,7 +168,11 @@ async def async_init(self): try: client = BleakClient(self.DEVICE_UUID, timeout=90) #with BleakClient(self.DEVICE_UUID, timeout=90) as client: - client.connect() + try: + await client.connect() + except Exception as e: + print("Error: ", e) + client_is_connected = True print("Client connected to ", self.DEVICE_UUID) for service in client.services: From 7e9c1a3eeb3af5705b61124f993bf0a3fed317a2 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 18:55:58 +0200 Subject: [PATCH 39/65] no errrors, not working --- collector_scripts/elite_rizer3.py | 61 ++++++++++++++++--------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index d7af319..fe5620c 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -141,17 +141,17 @@ async def notify_steering_callback(self, sender, data): udp.send_steering_data_udp(self.received_steering_data) - async def __init__(self): + def __init__(self): global steering_characteristics global tilt_characteristics global steering_service global tilt_service - global steering_ready #connection to steering BLE service ready - global tilt_ready #connection to tilt BLE service ready + #global steering_ready #connection to steering BLE service ready + #global tilt_ready #connection to tilt BLE service ready - global client + global client_is_connected # Connecting to BLE Device print("Connecting to BLE Device") @@ -163,7 +163,8 @@ async def __init__(self): self.tilt_characteristics = 0 async def async_init(self): - + global client_is_connected + print("start async init") while(client_is_connected == False): try: client = BleakClient(self.DEVICE_UUID, timeout=90) @@ -173,62 +174,62 @@ async def async_init(self): print("Client connected to ", self.DEVICE_UUID) for service in client.services: if (service.uuid == self.SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) + self.steering_service = service + print("[service uuid] ", self.steering_service.uuid) - if (steering_service != ""): + if (self.steering_service != ""): # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: + for characteristic in self.steering_service.characteristics: if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): self.steering_characteristics = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) print("IF") - print(steering_ready) - steering_ready = 1 + print(self.steering_ready) + self.steering_ready = 1 if (service.uuid == self.SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) + self.tilt_service = service + print("[service uuid] ", self.tilt_service.uuid) - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: + if (self.tilt_service != ""): + for characteristic in self.tilt_service.characteristics: print("characteristics UUID: ", characteristic.uuid) if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 + self.tilt_characteristics = characteristic + self.tilt_ready = 1 - if (steering_ready == 1 and tilt_ready == 1): + if (self.steering_ready == 1 and self.tilt_ready == 1): print("all ready!") - finally: - await client.disconnect - print("disconnect") + #finally: + #await client.disconnect + #print("disconnect") - #except exc.BleakError as e: - #print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") # Add additional error handling or logging as needed # raise async def main(): + ble_async_init_task = asyncio.create_task(ble.async_init()) + await ble_async_init_task ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) print("ble main started") - #udp_handler_task = asyncio.create_task(udp.main()) - #print("udp main start") + udp_handler_task = asyncio.create_task(udp.main()) + print("udp main start") await ble_handler_task() - - - #await udp_handler_task + await udp_handler_task # Creating instances of handlers udp = UDP_Handler() ble = BLE_Handler() # Call async_init right after the instance is created -asyncio.run(ble.async_init()) +#asyncio.run(ble.async_init()) + print("asyncio start") asyncio.run(main()) \ No newline at end of file From c0431d8c12a9b7e81116981ab12ced10acb7536b Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 21:02:34 +0200 Subject: [PATCH 40/65] seems it works. some errors in to send steerign to UDP calss --- collector_scripts/elite_rizer3.py | 60 +++++++++++++++++-------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index fe5620c..6b6f628 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -17,6 +17,9 @@ class UDP_Handler: def __init__(self): global tilt_value global tilt_received + + tilt_value = 0 + tilt_received = 0 self.received_steering_data = 0 # Initialize with None or any default value self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost self.udp_port = 2222 @@ -30,6 +33,8 @@ async def main(self): self.steering_data = None steering_received = None while(True): + await asyncio.sleep(1) + print("udp main") if (steering_received == 1): #when steering value has chanched, send it to unity await self.send_steering_data_udp(self.steering_data) try: @@ -64,7 +69,7 @@ def listening_udp(self): def check_new_tilt(self, udp_tilt_value): global tilt_received global tilt_value - #print("RIZER tilt: ", udp_tilt_value) + print("RIZER tilt: ", udp_tilt_value) if tilt_value != udp_tilt_value: tilt_value = udp_tilt_value tilt_received = 1 @@ -89,30 +94,33 @@ class BLE_Handler: global steering_service global tilt_service - global current_tilt_value_on_razer + global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) async def read_and_ride_rizer(self): global tilt_received - global tilt_characteristics global client print("read and write") while(True): - await self.read_steering(self.steering_characteristics) - #self.read_steering(client, steering_characteristics) - print("read steering rizer") - if (tilt_received == 1): - await self.write_tilt(client) - tilt_received = 0 - print("tilt writed") - print(self.tilt_characteristics) - - - async def read_steering(self, characteristic): + if (self.init_ack == True): + await self.read_steering() + #self.read_steering(client, steering_characteristics) + print("read steering rizer") + if (tilt_received == 1): + await self.write_tilt(client) + tilt_received = 0 + print("tilt writed") + else: + print("wait init ack") + + + async def read_steering(self): + await asyncio.sleep(1) print("read steering") try: - await client.start_notify(characteristic, self.notify_steering_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) + print("should reading...") + await client.start_notify(self.steering_characteristics, self.notify_steering_callback()) + await asyncio.sleep(1) # keeps the connection open for 10 seconds + await client.stop_notify(self.steering_characteristics.uuid) except Exception as e: print("Error: ", e) @@ -126,8 +134,9 @@ async def write_tilt(self): except Exception as e: print("Error: ", e) - async def notify_steering_callback(self, sender, data): - data = bytearray(data) + def notify_steering_callback(self): + print("notify steering") + data = bytearray(123) steering = 0.0 if data[3] == 65: @@ -161,14 +170,15 @@ def __init__(self): self.tilt_received = 0 self.steering_characteristics = None self.tilt_characteristics = 0 + self.init_ack = False async def async_init(self): + global client global client_is_connected print("start async init") - while(client_is_connected == False): + while not client_is_connected: try: client = BleakClient(self.DEVICE_UUID, timeout=90) - #with BleakClient(self.DEVICE_UUID, timeout=90) as client: await client.connect() client_is_connected = True print("Client connected to ", self.DEVICE_UUID) @@ -203,10 +213,7 @@ async def async_init(self): if (self.steering_ready == 1 and self.tilt_ready == 1): print("all ready!") - - #finally: - #await client.disconnect - #print("disconnect") + self.init_ack = True except exc.BleakError as e: print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") @@ -216,11 +223,12 @@ async def async_init(self): async def main(): ble_async_init_task = asyncio.create_task(ble.async_init()) await ble_async_init_task + ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) print("ble main started") udp_handler_task = asyncio.create_task(udp.main()) print("udp main start") - await ble_handler_task() + await ble_handler_task await udp_handler_task # Creating instances of handlers From 37da74c897eaa3e17b1e5a9d2b424b1419e9aeb8 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 14 May 2024 21:31:05 +0200 Subject: [PATCH 41/65] error at start_notify --- collector_scripts/elite_rizer3.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 9f53cc8..e805d7e 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -114,12 +114,18 @@ async def read_and_ride_rizer(self): async def read_steering(self): + data = bytearray(8) + sender = 0 await asyncio.sleep(1) print("read steering") try: print("should reading...") - await client.start_notify(self.steering_characteristics, self.notify_steering_callback()) + await client.start_notify(self.steering_characteristics, self.notify_steering_callback(sender, data)) + # Access the notification data using data argument + print(f"Steering data: {sender}") + print(f"Steering data: {data}") await asyncio.sleep(1) # keeps the connection open for 10 seconds + print("test here") await client.stop_notify(self.steering_characteristics.uuid) except Exception as e: print("Error: ", e) @@ -134,9 +140,9 @@ async def write_tilt(self): except Exception as e: print("Error: ", e) - def notify_steering_callback(self): + def notify_steering_callback(data): print("notify steering") - data = bytearray(123) + data = bytearray(data) steering = 0.0 if data[3] == 65: @@ -146,11 +152,9 @@ def notify_steering_callback(self): self.received_steering_data = steering print(self.received_steering_data) - udp.send_steering_data_udp(self.received_steering_data) - def __init__(self): def __init__(self): global steering_characteristics global tilt_characteristics From 145775aeaa45de767939999d8602c129c407fc1d Mon Sep 17 00:00:00 2001 From: PSenfft Date: Wed, 15 May 2024 11:40:09 +0200 Subject: [PATCH 42/65] Update hardware_idears.md fixed spelling mistakes --- docs/ hardware_idears.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ hardware_idears.md b/docs/ hardware_idears.md index 24ffef8..e484490 100644 --- a/docs/ hardware_idears.md +++ b/docs/ hardware_idears.md @@ -1,3 +1,3 @@ -- Food sensor while stand still Structure-borne sound transducer maybe with light barrier or pressure sensor +- Foot sensor while stand still Structure-borne sound transducer maybe with light barrier or pressure sensor - Structure-borne sound transducer for undergound feedback and crashes (rockhorn) - Brake feedback (maybe with pressure sensor) From 83165d1c6a44a81160ac18694e9a483cef6fadb1 Mon Sep 17 00:00:00 2001 From: PSenfft Date: Wed, 15 May 2024 11:52:07 +0200 Subject: [PATCH 43/65] Update rizer_sequencediagram.wsd --- docs/rizer_sequencediagram.wsd | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/rizer_sequencediagram.wsd b/docs/rizer_sequencediagram.wsd index 0ec3a0e..712409a 100644 --- a/docs/rizer_sequencediagram.wsd +++ b/docs/rizer_sequencediagram.wsd @@ -1,4 +1,4 @@ -@startuml +@startuml test title "RIZER" participant Unity @@ -8,7 +8,7 @@ participant BluetoothCallback participant RIZER -loop +par UDP_Handler -> UDP_Handler: listen for tilt end @@ -17,11 +17,12 @@ Master_Collector -> UDP_Handler: send new tilt UDP_Handler -> BluetoothCallback: computed tilt commands -loop "tilt 0.5" - BluetoothCallback -> RIZER: send tilt -end +par "tilt 0.5" + + alt + BluetoothCallback -> RIZER: send tilt + end -loop BluetoothCallback -> BluetoothCallback: listen for steering RIZER -> BluetoothCallback: send steering end @@ -31,4 +32,4 @@ BluetoothCallback -> UDP_Handler: send steering UDP_Handler -> Master_Collector: send steering Master_Collector -> Unity: send steering -@enduml \ No newline at end of file +@enduml From 373df25a5209ea9533775edca6efbff2570e9f83 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 28 May 2024 16:08:35 +0200 Subject: [PATCH 44/65] solved bug --- collector_scripts/elite_rizer3.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index e805d7e..b4edf7b 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -119,13 +119,11 @@ async def read_steering(self): await asyncio.sleep(1) print("read steering") try: - print("should reading...") - await client.start_notify(self.steering_characteristics, self.notify_steering_callback(sender, data)) + await client.start_notify(self.steering_characteristics, self.notify_steering_callback) # Access the notification data using data argument - print(f"Steering data: {sender}") - print(f"Steering data: {data}") + #print(f"Steering data: {sender}") + #print(f"Steering data: {data}") await asyncio.sleep(1) # keeps the connection open for 10 seconds - print("test here") await client.stop_notify(self.steering_characteristics.uuid) except Exception as e: print("Error: ", e) @@ -140,11 +138,10 @@ async def write_tilt(self): except Exception as e: print("Error: ", e) - def notify_steering_callback(data): + async def notify_steering_callback(self, sender, data): print("notify steering") data = bytearray(data) steering = 0.0 - if data[3] == 65: steering = 1.0 elif data[3] == 193: @@ -185,6 +182,7 @@ async def async_init(self): while not client_is_connected: try: client = BleakClient(self.DEVICE_UUID, timeout=90) + print("try to connect") await client.connect() client_is_connected = True print("Client connected to ", self.DEVICE_UUID) From 926f69197707d874a4150691a647262eb5526a15 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 28 May 2024 16:50:57 +0200 Subject: [PATCH 45/65] changed name from tilt do incline --- collector_scripts/elite_rizer3.py | 91 ++++++++++++++------------- collector_scripts/master_collector.py | 12 ++-- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index b4edf7b..d233745 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -5,9 +5,9 @@ from master_collector import DataReceiver #global variables -tilt_received = 0 #received tilt data form UDP. Ready to send over BLE +incline_received = 0 #received tilt data form UDP. Ready to send over BLE steering_received = 0 #received steering data from RIZER. Ready to send over UDP -tilt_value = None +incline_value = None current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE client = None @@ -15,11 +15,11 @@ class UDP_Handler: def __init__(self): - global tilt_value - global tilt_received + global incline_value + global incline_received - tilt_value = 0 - tilt_received = 0 + incline_value = 0 + incline_received = 0 self.received_steering_data = 0 # Initialize with None or any default value self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost self.udp_port = 2222 @@ -27,7 +27,7 @@ def __init__(self): async def main(self): - global tilt_value + global incline_value global steering_received receiver = DataReceiver() self.steering_data = None @@ -39,17 +39,18 @@ async def main(self): await self.send_steering_data_udp(self.steering_data) try: #self.listening_udp maybe not needed - tilt_value = receiver.get_tilt() #read tilt from unity - self.check_new_tilt(tilt_value) #check if tilt value has changed or is still the same + incline_value = receiver.get_incline() #read tilt from unity + print(incline_value) + self.check_new_incline(incline_value) #check if tilt value has changed or is still the same except Exception as e: print("Error: ", e) - def set_tilt_received(received): - tilt_received = received + def set_incline_received(self, received): + self.incline_received = received - def get_til_received(): - return tilt_received + def get_incline_received(self): + return self.incline_received #send steering data over udp def send_steering_data_udp(self, steering_data): @@ -60,19 +61,19 @@ def send_steering_data_udp(self, steering_data): udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) def listening_udp(self): - udp_tilt_data = 0 + udp_incline_data = 0 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) - print("Hello: ", udp_tilt_data) + udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip, self.udp_port)) + print("Hello: ", udp_incline_data) # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) - def check_new_tilt(self, udp_tilt_value): - global tilt_received - global tilt_value - print("RIZER tilt: ", udp_tilt_value) - if tilt_value != udp_tilt_value: - tilt_value = udp_tilt_value - tilt_received = 1 + def check_new_incline(self, udp_incline_value): + global incline_received + global incline_value + print("RIZER incline: ", udp_incline_value) + if incline_value != udp_incline_value: + incline_value = udp_incline_value + incline_received = 1 class BLE_Handler: #BLE constant @@ -80,13 +81,13 @@ class BLE_Handler: DEVICE_UUID = "fc:12:65:28:cb:44" SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering - SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - INCREASE_TILT_HEX = "060102" + INCREASE_INCLINE_HEX = "060102" DECREASE_TILT_HEX = "060402" CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt global steering_characteristics global tilt_characteristics @@ -97,7 +98,7 @@ class BLE_Handler: global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) async def read_and_ride_rizer(self): - global tilt_received + global incline_received global client print("read and write") while(True): @@ -105,9 +106,9 @@ async def read_and_ride_rizer(self): await self.read_steering() #self.read_steering(client, steering_characteristics) print("read steering rizer") - if (tilt_received == 1): - await self.write_tilt(client) - tilt_received = 0 + if (incline_received == 1): + await self.write_incline(client) + incline_received = 0 print("tilt writed") else: print("wait init ack") @@ -128,13 +129,13 @@ async def read_steering(self): except Exception as e: print("Error: ", e) - async def write_tilt(self): + async def write_incline(self): global client - global tilt_received + global incline_received try: - await client.write_gatt_char(self.CHARACTERISTIC_TILT_UUID, bytes.fromhex(self.INCREASE_TILT_HEX), response=True) + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) current_tilt_value_on_razer += 0.5 - tilt_received = 0 + incline_received = 0 except Exception as e: print("Error: ", e) @@ -169,10 +170,10 @@ def __init__(self): print("Connecting to BLE Device") client_is_connected = False self.steering_ready = 0 - self.tilt_ready = 0 + self.incline_ready = 0 self.tilt_received = 0 self.steering_characteristics = None - self.tilt_characteristics = 0 + self.incline_characteristics = 0 self.init_ack = False async def async_init(self): @@ -203,19 +204,19 @@ async def async_init(self): print(self.steering_ready) self.steering_ready = 1 - if (service.uuid == self.SERVICE_TILT_UUID): - self.tilt_service = service - print("[service uuid] ", self.tilt_service.uuid) + if (service.uuid == self.SERVICE_INCLINE_UUID): + self.incline_service = service + print("[service uuid] ", self.incline_service.uuid) - if (self.tilt_service != ""): - for characteristic in self.tilt_service.characteristics: + if (self.incline_service != ""): + for characteristic in self.incline_service.characteristics: print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_TILT_UUID): - self.tilt_characteristics = characteristic - self.tilt_ready = 1 + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): + self.incline_characteristics = characteristic + self.incline_ready = 1 - if (self.steering_ready == 1 and self.tilt_ready == 1): + if (self.steering_ready == 1 and self.incline_ready == 1): print("all ready!") self.init_ack = True diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 7d1dad5..5d3d1f1 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -57,16 +57,16 @@ def __init__(self): # self.udp_unity_receive_port = 12345 self.udp_unity_receive_socket = None self.ble_fan_speed = 0 - self.ble_tilt = 0 + self.ble_incline = 0 def get_fan_speed(self): print("Self ble fan speed: ", self.ble_fan_speed) return self.ble_fan_speed - def get_tilt(self): - #print("Self ble tilt: ", self.ble_tilt) - return self.ble_tilt + def get_incline(self): + print("Self ble incline: ", self.ble_incline) + return self.ble_incline def open_udp_socket(self): # Create a UDP socket @@ -87,10 +87,10 @@ def start_udp_listener(self): # value = json.loads(data.decode()) unity_values = json.loads(json_data) ble_fan_value = unity_values["bleFan"] - ble_tilt_value = unity_values["bleTilt"] + ble_incline_value = unity_values["bleIncline"] # print("ble fan from unity: ", ble_fan_value) self.ble_fan_speed = ble_fan_value - self.ble_tilt = ble_tilt_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly + self.ble_incline = ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly except Exception as e: print(f"Error while receiving UDP data: {e}") From eb90d277c77b29b73621d8f142205a0c8221674a Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Thu, 6 Jun 2024 14:49:17 +0200 Subject: [PATCH 46/65] working on json stuff --- collector_scripts/elite_rizer3.py | 87 ++++++++++++++------------- collector_scripts/master_collector.py | 4 +- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index d233745..01fd515 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -10,6 +10,8 @@ incline_value = None current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE client = None +asyncio_sleep = 3 +steering_ready_to_send = 0 class UDP_Handler: @@ -29,22 +31,28 @@ def __init__(self): async def main(self): global incline_value global steering_received + global asyncio_sleep receiver = DataReceiver() self.steering_data = None steering_received = None + receiver.open_udp_socket() + await receiver.start_udp_listener() while(True): - await asyncio.sleep(1) + await asyncio.sleep(asyncio_sleep) print("udp main") - if (steering_received == 1): #when steering value has chanched, send it to unity + if (steering_received == 1): #when steering value has chanched, send it to unity await self.send_steering_data_udp(self.steering_data) try: - #self.listening_udp maybe not needed + incline_value = receiver.get_incline() #read tilt from unity - print(incline_value) + print("incline from UDP (b c): ", incline_value) self.check_new_incline(incline_value) #check if tilt value has changed or is still the same except Exception as e: print("Error: ", e) + + print("udp loop finish") + def set_incline_received(self, received): self.incline_received = received @@ -55,7 +63,7 @@ def get_incline_received(self): #send steering data over udp def send_steering_data_udp(self, steering_data): # Create a UDP socket - print(steering_data) + #print("send steering data: ", steering_data) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: # Send speed_data udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) @@ -87,7 +95,7 @@ class BLE_Handler: DECREASE_TILT_HEX = "060402" CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt global steering_characteristics global tilt_characteristics @@ -96,20 +104,43 @@ class BLE_Handler: global tilt_service global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) + + def __init__(self): + global steering_characteristics + global tilt_characteristics + + global steering_service + global tilt_service + + #global steering_ready #connection to steering BLE service ready + #global tilt_ready #connection to tilt BLE service ready + + global client_is_connected + global client_is_connected + + # Connecting to BLE Device + print("Connecting to BLE Device") + client_is_connected = False + self.steering_ready = 0 + self.incline_ready = 0 + self.tilt_received = 0 + self.steering_characteristics = None + self.incline_characteristics = 0 + self.init_ack = False + #main function for BLE Handler async def read_and_ride_rizer(self): global incline_received global client print("read and write") while(True): + await asyncio.sleep(asyncio_sleep) if (self.init_ack == True): await self.read_steering() #self.read_steering(client, steering_characteristics) print("read steering rizer") if (incline_received == 1): await self.write_incline(client) - incline_received = 0 - print("tilt writed") else: print("wait init ack") @@ -117,30 +148,29 @@ async def read_and_ride_rizer(self): async def read_steering(self): data = bytearray(8) sender = 0 - await asyncio.sleep(1) - print("read steering") + await asyncio.sleep(asyncio_sleep) try: await client.start_notify(self.steering_characteristics, self.notify_steering_callback) # Access the notification data using data argument #print(f"Steering data: {sender}") #print(f"Steering data: {data}") - await asyncio.sleep(1) # keeps the connection open for 10 seconds + await asyncio.sleep(0.5) # keeps the connection open for 10 seconds await client.stop_notify(self.steering_characteristics.uuid) except Exception as e: print("Error: ", e) - async def write_incline(self): + async def write_incline(self): #TODO + - inclne global client global incline_received try: await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) current_tilt_value_on_razer += 0.5 incline_received = 0 + print("tilt writed") except Exception as e: print("Error: ", e) async def notify_steering_callback(self, sender, data): - print("notify steering") data = bytearray(data) steering = 0.0 if data[3] == 65: @@ -149,33 +179,9 @@ async def notify_steering_callback(self, sender, data): steering = -1.0 self.received_steering_data = steering - print(self.received_steering_data) + print("readed steering: ", self.received_steering_data) udp.send_steering_data_udp(self.received_steering_data) - - def __init__(self): - global steering_characteristics - global tilt_characteristics - - global steering_service - global tilt_service - - #global steering_ready #connection to steering BLE service ready - #global tilt_ready #connection to tilt BLE service ready - - global client_is_connected - global client_is_connected - - # Connecting to BLE Device - print("Connecting to BLE Device") - client_is_connected = False - self.steering_ready = 0 - self.incline_ready = 0 - self.tilt_received = 0 - self.steering_characteristics = None - self.incline_characteristics = 0 - self.init_ack = False - async def async_init(self): global client global client_is_connected @@ -199,9 +205,8 @@ async def async_init(self): if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): self.steering_characteristics = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - print(self.steering_ready) + #print("steering ready", self.steering_ready) self.steering_ready = 1 if (service.uuid == self.SERVICE_INCLINE_UUID): @@ -243,6 +248,4 @@ async def main(): # Call async_init right after the instance is created #asyncio.run(ble.async_init()) - -print("asyncio start") asyncio.run(main()) \ No newline at end of file diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 5d3d1f1..9381a82 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -10,6 +10,7 @@ def __init__(self): self.brake_value = 0 self.bno_value = 0 self.roll_value = 0 + self.incline_value = 0 self.udp_unity_send_ip = "127.0.0.2" # IP of the computer running Unity (just the localhost ip if the script is running on the same computer than the simulation) # self.udp_unity_send_ip = "10.30.77.221" # IP of the computer running Unity self.udp_unity_send_port = 1337 @@ -89,6 +90,7 @@ def start_udp_listener(self): ble_fan_value = unity_values["bleFan"] ble_incline_value = unity_values["bleIncline"] # print("ble fan from unity: ", ble_fan_value) + print("incline from unity: ", ble_incline_value) self.ble_fan_speed = ble_fan_value self.ble_incline = ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly except Exception as e: @@ -170,4 +172,4 @@ def stop_udp_listener(self): roll_value = json.loads(data.decode()) # print("Roll_Value: ", roll_value) roll_value = roll_value["sensor_value"] - data_sender.collect_roll(roll_value) + data_sender.collect_roll(roll_value) \ No newline at end of file From ac0d4addf186638f90b6902a3993639cdf80cba8 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 10 Jun 2024 16:34:17 +0200 Subject: [PATCH 47/65] still problem to read from DataReceiver --- collector_scripts/elite_rizer3.py | 52 +++++++++++++++------------ collector_scripts/headwind.py | 1 + collector_scripts/master_collector.py | 28 +++++++++++---- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 01fd515..5a44c80 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -5,16 +5,18 @@ from master_collector import DataReceiver #global variables -incline_received = 0 #received tilt data form UDP. Ready to send over BLE -steering_received = 0 #received steering data from RIZER. Ready to send over UDP -incline_value = None -current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE +global incline_received #received tilt data form UDP. Ready to send over BLE +global steering_received #received steering data from RIZER. Ready to send over UDP +global incline_value +current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE client = None asyncio_sleep = 3 steering_ready_to_send = 0 class UDP_Handler: + global incline_value + #global receiver def __init__(self): global incline_value @@ -26,25 +28,26 @@ def __init__(self): self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost self.udp_port = 2222 print("udp handler started") + #self.receiver = DataReceiver() async def main(self): global incline_value global steering_received global asyncio_sleep - receiver = DataReceiver() self.steering_data = None steering_received = None - receiver.open_udp_socket() - await receiver.start_udp_listener() while(True): await asyncio.sleep(asyncio_sleep) print("udp main") if (steering_received == 1): #when steering value has chanched, send it to unity + print("send steering data") await self.send_steering_data_udp(self.steering_data) try: - - incline_value = receiver.get_incline() #read tilt from unity + #self.listening_udp(self) + print("datareceiver get incline") + #incline_value = DataReceiver().get_incline() #read tilt from unity + #print("fan speed (b c)", self.receiver.get_fan_speed()) print("incline from UDP (b c): ", incline_value) self.check_new_incline(incline_value) #check if tilt value has changed or is still the same @@ -53,6 +56,11 @@ async def main(self): print("udp loop finish") + # async def udp_handler_listen(self): + # print("open udp socket") + # self.receiver.open_udp_socket() + + def set_incline_received(self, received): self.incline_received = received @@ -76,12 +84,10 @@ def listening_udp(self): # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) def check_new_incline(self, udp_incline_value): - global incline_received - global incline_value print("RIZER incline: ", udp_incline_value) - if incline_value != udp_incline_value: - incline_value = udp_incline_value - incline_received = 1 + if self.incline_value != udp_incline_value: + self.incline_value = udp_incline_value + self.incline_received = 1 class BLE_Handler: #BLE constant @@ -115,12 +121,12 @@ def __init__(self): #global steering_ready #connection to steering BLE service ready #global tilt_ready #connection to tilt BLE service ready - global client_is_connected - global client_is_connected + #global client_is_connected + #global client_is_connected # Connecting to BLE Device print("Connecting to BLE Device") - client_is_connected = False + self.client_is_connected = False self.steering_ready = 0 self.incline_ready = 0 self.tilt_received = 0 @@ -135,6 +141,7 @@ async def read_and_ride_rizer(self): print("read and write") while(True): await asyncio.sleep(asyncio_sleep) + print("udp handler sleep") if (self.init_ack == True): await self.read_steering() #self.read_steering(client, steering_characteristics) @@ -161,11 +168,10 @@ async def read_steering(self): async def write_incline(self): #TODO + - inclne global client - global incline_received try: await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) current_tilt_value_on_razer += 0.5 - incline_received = 0 + self.incline_received = 0 print("tilt writed") except Exception as e: print("Error: ", e) @@ -184,14 +190,13 @@ async def notify_steering_callback(self, sender, data): async def async_init(self): global client - global client_is_connected print("start async init") - while not client_is_connected: + while not self.client_is_connected: try: client = BleakClient(self.DEVICE_UUID, timeout=90) print("try to connect") await client.connect() - client_is_connected = True + self.client_is_connected = True print("Client connected to ", self.DEVICE_UUID) for service in client.services: if (service.uuid == self.SERVICE_STEERING_UUID): @@ -238,8 +243,11 @@ async def main(): print("ble main started") udp_handler_task = asyncio.create_task(udp.main()) print("udp main start") +# udp_listener_task = asyncio.create_task(udp.udp_handler_listen()) await ble_handler_task await udp_handler_task + # print("start udp listener") + # await udp_listener_task # Creating instances of handlers udp = UDP_Handler() diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 7be2a37..c5da445 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -87,6 +87,7 @@ def callback(device, advertising_data): receiver.start_udp_listener() # print("FAN SPEED: ", receiver.get_fan_speed()) speed_value = receiver.get_fan_speed() + print("incline: ", receiver.get_incline()) print("Fan Speed: ", speed_value) except Exception as e: print("Error: ", e) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 9381a82..7a62f83 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -10,7 +10,6 @@ def __init__(self): self.brake_value = 0 self.bno_value = 0 self.roll_value = 0 - self.incline_value = 0 self.udp_unity_send_ip = "127.0.0.2" # IP of the computer running Unity (just the localhost ip if the script is running on the same computer than the simulation) # self.udp_unity_send_ip = "10.30.77.221" # IP of the computer running Unity self.udp_unity_send_port = 1337 @@ -53,21 +52,36 @@ def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, r # This class might be to be located in the headwind script class DataReceiver: + global ble_fan_speed + global ble_incline + global resistance + def __init__(self): + global ble_fan_speed + global ble_incline + global ble_resistance # self.udp_unity_receive_ip = "127.0.0.1" # self.udp_unity_receive_port = 12345 self.udp_unity_receive_socket = None - self.ble_fan_speed = 0 - self.ble_incline = 0 + ble_fan_speed = 0 + ble_incline = 0 + ble_resistance = 0 def get_fan_speed(self): - print("Self ble fan speed: ", self.ble_fan_speed) - return self.ble_fan_speed + global ble_fan_speed + print("Self ble fan speed: ", ble_fan_speed) + return ble_fan_speed def get_incline(self): - print("Self ble incline: ", self.ble_incline) - return self.ble_incline + global ble_incline + print("Self ble incline: ", ble_incline) + return ble_incline + + def get_resistance(self): + global ble_resistance + print("Self ble incline: ", ble_resistance) + return ble_resistance def open_udp_socket(self): # Create a UDP socket From ea228fd19fd8e427ad08f6b1c14529b73b5cc601 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 10 Jun 2024 19:26:42 +0200 Subject: [PATCH 48/65] values from dataReceiver are inconsistent :/ --- collector_scripts/dataReceiverSingleton.py | 12 ++++++ collector_scripts/direto_xr.py | 1 + collector_scripts/elite_rizer3.py | 21 ++++++----- collector_scripts/headwind.py | 29 +++++++++++---- collector_scripts/master_collector.py | 43 +++++++++++----------- collector_scripts/p110_connect.py | 2 +- collector_scripts/run_scripts.bat | 2 + 7 files changed, 70 insertions(+), 40 deletions(-) create mode 100644 collector_scripts/dataReceiverSingleton.py diff --git a/collector_scripts/dataReceiverSingleton.py b/collector_scripts/dataReceiverSingleton.py new file mode 100644 index 0000000..a8db28d --- /dev/null +++ b/collector_scripts/dataReceiverSingleton.py @@ -0,0 +1,12 @@ +from master_collector import DataReceiver + +class DataReceiverSingleton: + _instance = None + _receiver = None # Neue Variable zur Speicherung der DataReceiver-Instanz + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + if cls._receiver is None: + cls._receiver = DataReceiver() # Erstelle die DataReceiver-Instanz beim ersten Aufruf + return cls._instance \ No newline at end of file diff --git a/collector_scripts/direto_xr.py b/collector_scripts/direto_xr.py index 4a06e82..16c13fa 100644 --- a/collector_scripts/direto_xr.py +++ b/collector_scripts/direto_xr.py @@ -3,6 +3,7 @@ import struct import sys import socket +from dataReceiverSingleton import DataReceiverSingleton device_name = "DIRETO XR" # Replace with the name of your desired BLE device diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 5a44c80..e805d26 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -2,7 +2,7 @@ from bleak import BleakClient, exc import socket import time -from master_collector import DataReceiver +from dataReceiverSingleton import DataReceiverSingleton #global variables global incline_received #received tilt data form UDP. Ready to send over BLE @@ -15,8 +15,6 @@ class UDP_Handler: - global incline_value - #global receiver def __init__(self): global incline_value @@ -28,7 +26,6 @@ def __init__(self): self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost self.udp_port = 2222 print("udp handler started") - #self.receiver = DataReceiver() async def main(self): @@ -37,6 +34,7 @@ async def main(self): global asyncio_sleep self.steering_data = None steering_received = None + receiver = DataReceiverSingleton() while(True): await asyncio.sleep(asyncio_sleep) print("udp main") @@ -44,9 +42,12 @@ async def main(self): print("send steering data") await self.send_steering_data_udp(self.steering_data) try: - #self.listening_udp(self) - print("datareceiver get incline") - #incline_value = DataReceiver().get_incline() #read tilt from unity + #self.receiver._receiver.start_udp_listener() + print("datareceiver get incline", receiver._receiver.get_incline()) + print("datareceiver get fan", receiver._receiver.get_fan_speed()) + + #self.receiver._receiver.stop_udp_listener() + incline_value = receiver._receiver.get_incline() #read tilt from unity #print("fan speed (b c)", self.receiver.get_fan_speed()) print("incline from UDP (b c): ", incline_value) self.check_new_incline(incline_value) #check if tilt value has changed or is still the same @@ -84,9 +85,10 @@ def listening_udp(self): # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) def check_new_incline(self, udp_incline_value): + global incline_value print("RIZER incline: ", udp_incline_value) - if self.incline_value != udp_incline_value: - self.incline_value = udp_incline_value + if incline_value != udp_incline_value: + incline_value = udp_incline_value self.incline_received = 1 class BLE_Handler: @@ -144,7 +146,6 @@ async def read_and_ride_rizer(self): print("udp handler sleep") if (self.init_ack == True): await self.read_steering() - #self.read_steering(client, steering_characteristics) print("read steering rizer") if (incline_received == 1): await self.write_incline(client) diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index c5da445..ddd1361 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -1,8 +1,8 @@ import asyncio from bleak import BleakScanner, BleakClient, exc -from master_collector import DataReceiver +from dataReceiverSingleton import DataReceiverSingleton -class BluetoothCallback: +class BluetoothCallback(): def __init__(self): self.received_data = 0 # Initialize with None or any default value @@ -78,16 +78,19 @@ def callback(device, advertising_data): if(characteristic.uuid == characteristic_uuid): CHARACTERISTIC = characteristic - receiver = DataReceiver() + receiver = DataReceiverSingleton() + bluetooth_callback = BluetoothCallback() - receiver.open_udp_socket() + receiver._receiver.open_udp_socket() while True: try: - receiver.start_udp_listener() + print("headwind: try") + receiver._receiver.start_udp_listener() # print("FAN SPEED: ", receiver.get_fan_speed()) - speed_value = receiver.get_fan_speed() - print("incline: ", receiver.get_incline()) + speed_value = receiver._receiver.get_fan_speed() + print("incline: ", receiver._receiver.get_incline()) + print("Fan Speed: ", receiver._receiver.get_fan_speed()) print("Fan Speed: ", speed_value) except Exception as e: print("Error: ", e) @@ -136,4 +139,14 @@ def callback(device, advertising_data): # Add additional error handling or logging as needed # raise -asyncio.run(scan_and_connect_headwind()) \ No newline at end of file +try: + asyncio.run(scan_and_connect_headwind()) +except BaseException: + import sys + print(sys.exc_info()[0]) + import traceback + print(traceback.format_exc()) +finally: + print("Press Enter to continue ...") + input() + diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 7a62f83..418323f 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -54,34 +54,32 @@ def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, r class DataReceiver: global ble_fan_speed global ble_incline - global resistance + global ble_resistance def __init__(self): - global ble_fan_speed - global ble_incline - global ble_resistance + # self.udp_unity_receive_ip = "127.0.0.1" # self.udp_unity_receive_port = 12345 self.udp_unity_receive_socket = None - ble_fan_speed = 0 - ble_incline = 0 - ble_resistance = 0 + self.ble_fan_speed = 0 + self.ble_incline = 0 + self.ble_resistance = 0 def get_fan_speed(self): - global ble_fan_speed - print("Self ble fan speed: ", ble_fan_speed) - return ble_fan_speed + #global ble_fan_speed + print("Self ble fan speed: ", self.ble_fan_speed) + return self.ble_fan_speed def get_incline(self): - global ble_incline - print("Self ble incline: ", ble_incline) - return ble_incline + #global ble_incline + print("Self ble incline: ", self.ble_incline) + return self.ble_incline def get_resistance(self): - global ble_resistance - print("Self ble incline: ", ble_resistance) - return ble_resistance + #global ble_resistance + print("Self ble incline: ", self.ble_resistance) + return self.ble_resistance def open_udp_socket(self): # Create a UDP socket @@ -94,19 +92,22 @@ def open_udp_socket(self): print("Listening for UDP data...") def start_udp_listener(self): + # Infinite loop to continuously receive data try: data, addr = self.udp_unity_receive_socket.recvfrom(1024) # Buffer size is 1024 bytes + #self.udp_unity_receive_socket.setblocking(False) + json_data = data.decode('utf-8') # Decode bytes to string # value = json.loads(data.decode()) unity_values = json.loads(json_data) - ble_fan_value = unity_values["bleFan"] - ble_incline_value = unity_values["bleIncline"] + self.ble_fan_value = unity_values["bleFan"] + self.ble_incline_value = unity_values["bleIncline"] # print("ble fan from unity: ", ble_fan_value) - print("incline from unity: ", ble_incline_value) - self.ble_fan_speed = ble_fan_value - self.ble_incline = ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly + print("incline from unity: ", self.ble_incline_value) + self.ble_fan_speed = self.ble_fan_value + self.ble_incline = self.ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly except Exception as e: print(f"Error while receiving UDP data: {e}") diff --git a/collector_scripts/p110_connect.py b/collector_scripts/p110_connect.py index d4016e9..3a5f36d 100644 --- a/collector_scripts/p110_connect.py +++ b/collector_scripts/p110_connect.py @@ -22,7 +22,7 @@ def connect_and_start_p100(): # p100.turnOn() except Exception: pass - time.sleep(5) + time.sleep(6) print("Turn P100 on...") p100.turnOn() time.sleep(10) diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 21e4632..535df2f 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -3,6 +3,8 @@ REM Set the Python script to be executed 5 seconds before others set pre_execution_script="p110_connect.py" +ping 127.0.0.1 -n 20 > nul REM pings to delay the script + REM Set the list of Python scripts to start set python_scripts=("direto_xr.py", "elite_rizer3.py", "headwind.py", "master_collector.py") From 117d9a82a0ef48b804d97de6d4b9905e4db1c20b Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 10 Jun 2024 21:19:22 +0200 Subject: [PATCH 49/65] working on singleton *** --- collector_scripts/dataReceiverSingleton.py | 16 +++---- collector_scripts/elite_rizer3.py | 12 +++--- collector_scripts/headwind.py | 15 +++---- collector_scripts/master_collector.py | 49 +++++++++++++++------- collector_scripts/p110_connect.py | 2 +- collector_scripts/run_scripts.bat | 2 +- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/collector_scripts/dataReceiverSingleton.py b/collector_scripts/dataReceiverSingleton.py index a8db28d..9ac6cdb 100644 --- a/collector_scripts/dataReceiverSingleton.py +++ b/collector_scripts/dataReceiverSingleton.py @@ -1,12 +1,12 @@ from master_collector import DataReceiver class DataReceiverSingleton: - _instance = None - _receiver = None # Neue Variable zur Speicherung der DataReceiver-Instanz + _instance = None - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - if cls._receiver is None: - cls._receiver = DataReceiver() # Erstelle die DataReceiver-Instanz beim ersten Aufruf - return cls._instance \ No newline at end of file + def __new__(cls): + if cls._instance is None: + cls._instance = super(DataReceiverSingleton, cls).__new__(cls) + return cls._instance + + def __init__(self): + print("Instance created") \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index e805d26..7561b7b 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -2,7 +2,7 @@ from bleak import BleakClient, exc import socket import time -from dataReceiverSingleton import DataReceiverSingleton +from master_collector import DataReceiverSingleton #global variables global incline_received #received tilt data form UDP. Ready to send over BLE @@ -34,7 +34,9 @@ async def main(self): global asyncio_sleep self.steering_data = None steering_received = None - receiver = DataReceiverSingleton() + receiver = DataReceiverSingleton.get_instance() + print("rizer id: ", id(receiver)) + print("where id??") while(True): await asyncio.sleep(asyncio_sleep) print("udp main") @@ -43,11 +45,11 @@ async def main(self): await self.send_steering_data_udp(self.steering_data) try: #self.receiver._receiver.start_udp_listener() - print("datareceiver get incline", receiver._receiver.get_incline()) - print("datareceiver get fan", receiver._receiver.get_fan_speed()) + print("datareceiver get incline", receiver._instance.get_incline()) + print("datareceiver get fan", receiver._instance.get_fan_speed()) #self.receiver._receiver.stop_udp_listener() - incline_value = receiver._receiver.get_incline() #read tilt from unity + incline_value = receiver._instance.get_incline() #read tilt from unity #print("fan speed (b c)", self.receiver.get_fan_speed()) print("incline from UDP (b c): ", incline_value) self.check_new_incline(incline_value) #check if tilt value has changed or is still the same diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index ddd1361..40d7c8c 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -1,6 +1,6 @@ import asyncio from bleak import BleakScanner, BleakClient, exc -from dataReceiverSingleton import DataReceiverSingleton +from master_collector import DataReceiverSingleton class BluetoothCallback(): def __init__(self): @@ -78,19 +78,20 @@ def callback(device, advertising_data): if(characteristic.uuid == characteristic_uuid): CHARACTERISTIC = characteristic - receiver = DataReceiverSingleton() + receiver = DataReceiverSingleton.get_instance() + print("rizer id: ", id(receiver)) bluetooth_callback = BluetoothCallback() - receiver._receiver.open_udp_socket() + receiver._instance.open_udp_socket() while True: try: print("headwind: try") - receiver._receiver.start_udp_listener() + receiver._instance.start_udp_listener() # print("FAN SPEED: ", receiver.get_fan_speed()) - speed_value = receiver._receiver.get_fan_speed() - print("incline: ", receiver._receiver.get_incline()) - print("Fan Speed: ", receiver._receiver.get_fan_speed()) + speed_value = receiver._instance.get_fan_speed() + print("incline: ", receiver._instance.get_incline()) + print("Fan Speed: ", receiver._instance.get_fan_speed()) print("Fan Speed: ", speed_value) except Exception as e: print("Error: ", e) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 418323f..ed8c0e6 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -51,30 +51,51 @@ def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, r # This class might be to be located in the headwind script -class DataReceiver: +class DataReceiverSingleton: + _instance = None + global ble_fan_speed global ble_incline global ble_resistance - def __init__(self): - - # self.udp_unity_receive_ip = "127.0.0.1" + # self.udp_unity_receive_ip = "127.0.0.1" # self.udp_unity_receive_port = 12345 - self.udp_unity_receive_socket = None - self.ble_fan_speed = 0 - self.ble_incline = 0 - self.ble_resistance = 0 + + + @classmethod + def get_instance(cls): + if cls._instance == None: + cls._instance = cls.__new__(cls) + global udp_unity_receive_socket + global ble_fan_speed + global ble_incline + global ble_resistance + + udp_unity_receive_socket = None + ble_fan_speed = 0 + ble_incline = 0 + ble_resistance = 0 + return cls._instance + + def log(self, ex: Exception): + print(ex) + def log(self, message: str): + print(message) + + def __init__(self): + raise RuntimeError("This is a Singleton, invoke get instance() instead.") + print("Master collector instance created") def get_fan_speed(self): - #global ble_fan_speed - print("Self ble fan speed: ", self.ble_fan_speed) - return self.ble_fan_speed + global ble_fan_speed + print("Self ble fan speed: ", ble_fan_speed) + return ble_fan_speed def get_incline(self): - #global ble_incline - print("Self ble incline: ", self.ble_incline) - return self.ble_incline + global ble_incline + print("Self ble incline: ", ble_incline) + return ble_incline def get_resistance(self): #global ble_resistance diff --git a/collector_scripts/p110_connect.py b/collector_scripts/p110_connect.py index 3a5f36d..d4016e9 100644 --- a/collector_scripts/p110_connect.py +++ b/collector_scripts/p110_connect.py @@ -22,7 +22,7 @@ def connect_and_start_p100(): # p100.turnOn() except Exception: pass - time.sleep(6) + time.sleep(5) print("Turn P100 on...") p100.turnOn() time.sleep(10) diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 535df2f..bed6c28 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -3,7 +3,7 @@ REM Set the Python script to be executed 5 seconds before others set pre_execution_script="p110_connect.py" -ping 127.0.0.1 -n 20 > nul REM pings to delay the script +ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 REM Set the list of Python scripts to start set python_scripts=("direto_xr.py", "elite_rizer3.py", "headwind.py", "master_collector.py") From a6da9f71e81eab9fbd7825f833d94802f8e6eae0 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Wed, 19 Jun 2024 16:42:28 +0200 Subject: [PATCH 50/65] working on udp destribution --- collector_scripts/dataReceiverSingleton.py | 12 --- collector_scripts/elite_rizer3.py | 100 ++++++++++++------ collector_scripts/headwind.py | 17 ++-- collector_scripts/master_collector.py | 113 +++++++++++++-------- collector_scripts/run_scripts.bat | 3 +- 5 files changed, 151 insertions(+), 94 deletions(-) delete mode 100644 collector_scripts/dataReceiverSingleton.py diff --git a/collector_scripts/dataReceiverSingleton.py b/collector_scripts/dataReceiverSingleton.py deleted file mode 100644 index 9ac6cdb..0000000 --- a/collector_scripts/dataReceiverSingleton.py +++ /dev/null @@ -1,12 +0,0 @@ -from master_collector import DataReceiver - -class DataReceiverSingleton: - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super(DataReceiverSingleton, cls).__new__(cls) - return cls._instance - - def __init__(self): - print("Instance created") \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 7561b7b..d56bfb5 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -2,13 +2,13 @@ from bleak import BleakClient, exc import socket import time -from master_collector import DataReceiverSingleton +from master_collector import DataReceiver #global variables global incline_received #received tilt data form UDP. Ready to send over BLE global steering_received #received steering data from RIZER. Ready to send over UDP global incline_value -current_tilt_value_on_razer = None #received tilt data form UDP. Ready to send over BLE +global current_tilt_value_on_razer # current incline from RAZER to compute 0.5 steps fo reach desired incline client = None asyncio_sleep = 3 steering_ready_to_send = 0 @@ -19,12 +19,16 @@ class UDP_Handler: def __init__(self): global incline_value global incline_received + global isRunningBT + global isRunningUDP incline_value = 0 incline_received = 0 - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip_to_master_collector = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + self.udp_ip_from_master_collector = "127.0.0.3" self.udp_port = 2222 + self.receive_from_collector_port = 2223 print("udp handler started") @@ -32,32 +36,33 @@ async def main(self): global incline_value global steering_received global asyncio_sleep + global isRunningUDP + global isRunningBT + + isRunningUDP = True self.steering_data = None steering_received = None - receiver = DataReceiverSingleton.get_instance() - print("rizer id: ", id(receiver)) - print("where id??") + #print("rizer id: ", id(receiver)) while(True): await asyncio.sleep(asyncio_sleep) print("udp main") + print("start listener") + self.receive_incline_data_udp() if (steering_received == 1): #when steering value has chanched, send it to unity print("send steering data") await self.send_steering_data_udp(self.steering_data) try: - #self.receiver._receiver.start_udp_listener() - print("datareceiver get incline", receiver._instance.get_incline()) - print("datareceiver get fan", receiver._instance.get_fan_speed()) - #self.receiver._receiver.stop_udp_listener() - incline_value = receiver._instance.get_incline() #read tilt from unity #print("fan speed (b c)", self.receiver.get_fan_speed()) print("incline from UDP (b c): ", incline_value) - self.check_new_incline(incline_value) #check if tilt value has changed or is still the same + #self.check_new_incline(incline_value) #check if tilt value has changed or is still the same except Exception as e: print("Error: ", e) print("udp loop finish") + isRunningUDP = False + isRunningBT = True # async def udp_handler_listen(self): # print("open udp socket") @@ -77,21 +82,27 @@ def send_steering_data_udp(self, steering_data): #print("send steering data: ", steering_data) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) - def listening_udp(self): + def receive_incline_data_udp(self): udp_incline_data = 0 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip, self.udp_port)) + udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip_to_master_collector, self.receive_from_collector_port)) print("Hello: ", udp_incline_data) + # def listening_udp(self): + # udp_incline_data = 0 + # with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip, self.udp_port)) + # print("Hello: ", udp_incline_data) # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) def check_new_incline(self, udp_incline_value): global incline_value + global incline_received print("RIZER incline: ", udp_incline_value) if incline_value != udp_incline_value: incline_value = udp_incline_value - self.incline_received = 1 + incline_received = 1 class BLE_Handler: #BLE constant @@ -102,7 +113,7 @@ class BLE_Handler: SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" INCREASE_INCLINE_HEX = "060102" - DECREASE_TILT_HEX = "060402" + DECREASE_INCLINE_HEX = "060402" CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt @@ -113,6 +124,9 @@ class BLE_Handler: global steering_service global tilt_service + global isRunningUDP + global isRunningBT + global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) def __init__(self): @@ -122,6 +136,8 @@ def __init__(self): global steering_service global tilt_service + global current_tilt_value_on_razer + #global steering_ready #connection to steering BLE service ready #global tilt_ready #connection to tilt BLE service ready @@ -137,11 +153,15 @@ def __init__(self): self.steering_characteristics = None self.incline_characteristics = 0 self.init_ack = False + + current_tilt_value_on_razer = 0 #main function for BLE Handler async def read_and_ride_rizer(self): global incline_received global client + global isRunningBT + global isRunningUDP print("read and write") while(True): await asyncio.sleep(asyncio_sleep) @@ -150,9 +170,12 @@ async def read_and_ride_rizer(self): await self.read_steering() print("read steering rizer") if (incline_received == 1): - await self.write_incline(client) + print("write incline") + await self.write_incline(self) else: print("wait init ack") + isRunningBT = False + isRunningUDP = True async def read_steering(self): @@ -169,15 +192,32 @@ async def read_steering(self): except Exception as e: print("Error: ", e) - async def write_incline(self): #TODO + - inclne + async def write_incline(self): global client - try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += 0.5 - self.incline_received = 0 - print("tilt writed") - except Exception as e: - print("Error: ", e) + global current_tilt_value_on_razer + global incline_value + incline_different = abs(current_tilt_value_on_razer, incline_value) #absolute different of old and new incline value + print("incline_different", incline_different) + + if(current_tilt_value_on_razer + incline_value > 0): + for x in range (incline_different): + try: + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + current_tilt_value_on_razer += 1 + self.incline_received = 0 + print("tilt writed") + except Exception as e: + print("Error: ", e) + else: + for x in range (incline_different): + try: + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + current_tilt_value_on_razer += -1 + self.incline_received = 0 + print("tilt writed, x ", x) + except Exception as e: + print("Error: ", e) + async def notify_steering_callback(self, sender, data): data = bytearray(data) @@ -242,13 +282,13 @@ async def main(): ble_async_init_task = asyncio.create_task(ble.async_init()) await ble_async_init_task - ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) + #ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) print("ble main started") udp_handler_task = asyncio.create_task(udp.main()) print("udp main start") # udp_listener_task = asyncio.create_task(udp.udp_handler_listen()) - await ble_handler_task - await udp_handler_task + #await ble_handler_task + await udp_handler_task #TODO # print("start udp listener") # await udp_listener_task diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 40d7c8c..2648004 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -1,6 +1,6 @@ import asyncio from bleak import BleakScanner, BleakClient, exc -from master_collector import DataReceiverSingleton +from master_collector import DataReceiver class BluetoothCallback(): def __init__(self): @@ -78,21 +78,22 @@ def callback(device, advertising_data): if(characteristic.uuid == characteristic_uuid): CHARACTERISTIC = characteristic - receiver = DataReceiverSingleton.get_instance() - print("rizer id: ", id(receiver)) + #receiver = DataReceiver() + #print("rizer id: ", id(receiver)) bluetooth_callback = BluetoothCallback() - receiver._instance.open_udp_socket() + #receiver.open_udp_socket() while True: try: print("headwind: try") - receiver._instance.start_udp_listener() + #receiver.start_udp_listener() # print("FAN SPEED: ", receiver.get_fan_speed()) - speed_value = receiver._instance.get_fan_speed() - print("incline: ", receiver._instance.get_incline()) - print("Fan Speed: ", receiver._instance.get_fan_speed()) + speed_value = DataReceiver.ble_fan_speed + print("incline: ", DataReceiver.get_ble_incline()) + print("Fan Speed: ", DataReceiver.get_ble_fan_speed()) print("Fan Speed: ", speed_value) + print("get ble incline headwind", DataReceiver.get_ble_incline()) except Exception as e: print("Error: ", e) try: diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index ed8c0e6..47c44a9 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -2,6 +2,7 @@ import socket import select import json +import time class DataSender: def __init__(self): @@ -51,57 +52,48 @@ def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, r # This class might be to be located in the headwind script -class DataReceiverSingleton: - _instance = None - - global ble_fan_speed - global ble_incline - global ble_resistance - - # self.udp_unity_receive_ip = "127.0.0.1" +class DataReceiver: + def __init__(self): + self.ble_fan_speed = 0 + self.ble_incline = 40 + self.ble_resistance = 0 + self.send_to_actuator_ip = "127.0.0.3" + self.send_to_rizer_port = 2223 + self.send_to_headwind_port = 2224 + self.udp_unity_receive_socket = None + self.main_loop() + + # self.udp_unity_receive_ip = "127.0.0.1" # self.udp_unity_receive_port = 12345 - - - @classmethod - def get_instance(cls): - if cls._instance == None: - cls._instance = cls.__new__(cls) - global udp_unity_receive_socket - global ble_fan_speed - global ble_incline - global ble_resistance - - udp_unity_receive_socket = None - ble_fan_speed = 0 - ble_incline = 0 - ble_resistance = 0 - return cls._instance - - def log(self, ex: Exception): - print(ex) - def log(self, message: str): - print(message) + def set_ble_fan_speed(self, fan_speed): + self.ble_fan_speed = fan_speed - def __init__(self): - raise RuntimeError("This is a Singleton, invoke get instance() instead.") - print("Master collector instance created") - - def get_fan_speed(self): - global ble_fan_speed - print("Self ble fan speed: ", ble_fan_speed) - return ble_fan_speed + def set_ble_incline(self, incline_data): + self.ble_incline = incline_data + def get_fan_speed(self): + #global ble_fan_speed + print("Self ble fan speed: ", self.ble_fan_speed) + return self.ble_fan_speed + def get_incline(self): - global ble_incline - print("Self ble incline: ", ble_incline) - return ble_incline + print("Self ble incline: ", self.ble_incline) + return self.ble_incline def get_resistance(self): #global ble_resistance print("Self ble incline: ", self.ble_resistance) return self.ble_resistance - + + def main_loop(self): + print("start main loop master collector") + for x in range -20, 40: + self.send_udp_data_to_rizer(x) + time.sleep(2) + + + def open_udp_socket(self): # Create a UDP socket udp_unity_receive_ip = "127.0.0.1" @@ -116,6 +108,7 @@ def start_udp_listener(self): # Infinite loop to continuously receive data try: + data_receiver = DataReceiver() data, addr = self.udp_unity_receive_socket.recvfrom(1024) # Buffer size is 1024 bytes #self.udp_unity_receive_socket.setblocking(False) @@ -127,10 +120,44 @@ def start_udp_listener(self): self.ble_incline_value = unity_values["bleIncline"] # print("ble fan from unity: ", ble_fan_value) print("incline from unity: ", self.ble_incline_value) - self.ble_fan_speed = self.ble_fan_value - self.ble_incline = self.ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly + data_receiver.set_ble_fan_speed(self.ble_fan_value) + data_receiver.set_ble_incline(self.ble_incline_value) + print("incline mastercollector", data_receiver.get_incline()) + #self.ble_incline = self.ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly except Exception as e: print(f"Error while receiving UDP data: {e}") + + def send_udp_data_to_rizer(self, incline_data): + # Create a dictionary with the required parameters + data = { + "rizerIncline": float(incline_data), + } + print(data) + # Convert dictionary to JSON string + json_data = json.dumps(data) + + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send JSON data + udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_rizer_port)) + + + def send_udp_data_to_headwind(self, fan_speed): + + + # Create a dictionary with the required parameters + data = { + "fanSpeed": float(fan_speed), + } + print(data) + # Convert dictionary to JSON string + json_data = json.dumps(data) + + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send JSON data + udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_headwind_port)) + def stop_udp_listener(self): if self.udp_unity_receive_socket: diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index bed6c28..74ec50c 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -6,7 +6,8 @@ set pre_execution_script="p110_connect.py" ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "elite_rizer3.py", "headwind.py", "master_collector.py") +set python_scripts=("elite_rizer3.py", "master_collector.py") +REM "direto_xr.py", "headwind.py", REM Start the pre-execution script start "" python %pre_execution_script% From cc6be91cab1ec5a3923c3015793066efbef2ef5b Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 23 Jun 2024 17:24:28 +0200 Subject: [PATCH 51/65] added direto sender --- .gitignore | 414 +++---- .vscode/launch.json | 30 +- README.md | 36 +- archive/automized_run_scripts/run.py | 84 +- archive/automized_run_scripts/run_scripts.bat | 58 +- archive/automized_run_scripts/run_scripts.py | 100 +- archive/automized_run_scripts/run_scripts.sh | 54 +- archive/bno/read_bno.py | 48 +- archive/brake/read_brake.py | 50 +- archive/collector_scripts/data_collector.py | 34 +- archive/direto/read_speed.py | 308 ++--- archive/direto/write_resistance.py | 154 +-- archive/docker/Dockerfile | 78 +- archive/docker/docker-compose.yml | 18 +- archive/docker/entrypoint.sh | 14 +- .../headwind/archive_old_write_headwind.py | 446 ++++---- .../archive_write_headwind_with_methods.py | 188 ++-- archive/p100/starting_p100.py | 24 +- archive/rizer/read_steering.py | 190 ++-- archive/rizer/write_height.py | 194 ++-- collector_scripts/direto_xr.py | 342 +++--- collector_scripts/elite_rizer.py | 348 +++--- collector_scripts/elite_rizer2.py | 178 +-- collector_scripts/elite_rizer3.py | 598 +++++----- collector_scripts/elite_rizer_old.py | 302 ++--- collector_scripts/headwind.py | 308 ++--- collector_scripts/master_collector.py | 488 ++++---- collector_scripts/p110_connect.py | 70 +- collector_scripts/run_scripts.bat | 64 +- .../ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino | 604 +++++----- .../sensor_scripts/Brake_Sensor/.gitignore | 2 +- .../sensor_scripts/Brake_Sensor/.travis.yml | 134 +-- .../.vscode/c_cpp_properties.json | 1002 ++++++++--------- .../Brake_Sensor/.vscode/extensions.json | 20 +- .../Brake_Sensor/.vscode/launch.json | 88 +- .../sensor_scripts/Brake_Sensor/README.rst | 76 +- .../Brake_Sensor/platformio.ini | 34 +- .../sensor_scripts/Brake_Sensor/src/main.cpp | 126 +-- .../sensor_scripts/Brake_Sensor/test/README | 22 +- .../sensor_scripts/Gyro_Sensor/.gitignore | 10 +- .../Gyro_Sensor/.vscode/extensions.json | 20 +- .../sensor_scripts/Gyro_Sensor/platformio.ini | 36 +- .../sensor_scripts/Gyro_Sensor/src/main.cpp | 168 +-- .../sensor_scripts/Gyro_Sensor/test/README | 22 +- .../sensor_scripts/Roll_Sensor/.gitignore | 10 +- .../Roll_Sensor/.vscode/extensions.json | 20 +- .../sensor_scripts/Roll_Sensor/platformio.ini | 30 +- .../sensor_scripts/Roll_Sensor/src/main.cpp | 598 +++++----- .../sensor_scripts/Roll_Sensor/test/README | 22 +- docs/ hardware_idears.md | 6 +- docs/rizer_classdiagram.wsd | 30 +- docs/rizer_sequencediagram.wsd | 70 +- requirements.txt | 14 +- 53 files changed, 4197 insertions(+), 4187 deletions(-) diff --git a/.gitignore b/.gitignore index 8f8e9d9..4135575 100644 --- a/.gitignore +++ b/.gitignore @@ -1,207 +1,207 @@ -# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,venv -# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,venv - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -### Python Patch ### -# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration -poetry.toml - -# ruff -.ruff_cache/ - -# LSP config files -pyrightconfig.json - -### venv ### -# Virtualenv -# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ -[Bb]in -[Ii]nclude -[Ll]ib -[Ll]ib64 -[Ll]ocal -[Ss]cripts -pyvenv.cfg -pip-selfcheck.json - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,venv +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,venv +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,venv + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### venv ### +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +pip-selfcheck.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,venv diff --git a/.vscode/launch.json b/.vscode/launch.json index 0bfa3c2..89a8106 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,16 +1,16 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python Debugger: Current File", - "type": "debugpy", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal", - "justMyCode": "false" - } - ] +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": "false" + } + ] } \ No newline at end of file diff --git a/README.md b/README.md index 7ec9cfc..2d87b3b 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -## Create virtual python environment -```python -m venv .env``` - -## Run the virtual environment -```source ./.env/bin/activate``` - -## Install dependencies -```pip install -r requirements.txt``` - -## Control the docker container -```docker exec -it raspi-container /bin/bash``` - -## Enable bleak logging on Linux -Insert this command into the same console where you want to start the bleak script after to set the BLEAK_LOGGING environment variable. -```export BLEAK_LOGGING=1``` - -## Notes -The script write_height.py in the archive folder does not work right now. More reverse Engineering to write on the Elite Rizer is required. +## Create virtual python environment +```python -m venv .env``` + +## Run the virtual environment +```source ./.env/bin/activate``` + +## Install dependencies +```pip install -r requirements.txt``` + +## Control the docker container +```docker exec -it raspi-container /bin/bash``` + +## Enable bleak logging on Linux +Insert this command into the same console where you want to start the bleak script after to set the BLEAK_LOGGING environment variable. +```export BLEAK_LOGGING=1``` + +## Notes +The script write_height.py in the archive folder does not work right now. More reverse Engineering to write on the Elite Rizer is required. diff --git a/archive/automized_run_scripts/run.py b/archive/automized_run_scripts/run.py index 939189f..05496c5 100644 --- a/archive/automized_run_scripts/run.py +++ b/archive/automized_run_scripts/run.py @@ -1,42 +1,42 @@ -import subprocess -import time -from p110_connect import connect_and_start_p100 - -python_scripts = ["direto_xr.py", "elite_rizer.py", "master_collector.py"] # , "headwind.py" -print("starting scripts...") -subprocesses = [] - -p100 = connect_and_start_p100() -# p100 = True -if p100: - # Loop through the other scripts and start them - for script in python_scripts: - process = subprocess.Popen(["python", script]) - subprocesses.append(process) - print(f"Started {script}") - time.sleep(15) - -try: - # Keep the script running until interrupted by the user - while True: - pass -except KeyboardInterrupt: - # Handle KeyboardInterrupt (Ctrl+C) to terminate subprocesses - print("\nTerminating subprocesses...") - for process in subprocesses: - process.terminate() - print("All scripts terminated.") - - - - - - - - - - - - -# Additional cleanup or logging if needed -print("All scripts completed.") +import subprocess +import time +from p110_connect import connect_and_start_p100 + +python_scripts = ["direto_xr.py", "elite_rizer.py", "master_collector.py"] # , "headwind.py" +print("starting scripts...") +subprocesses = [] + +p100 = connect_and_start_p100() +# p100 = True +if p100: + # Loop through the other scripts and start them + for script in python_scripts: + process = subprocess.Popen(["python", script]) + subprocesses.append(process) + print(f"Started {script}") + time.sleep(15) + +try: + # Keep the script running until interrupted by the user + while True: + pass +except KeyboardInterrupt: + # Handle KeyboardInterrupt (Ctrl+C) to terminate subprocesses + print("\nTerminating subprocesses...") + for process in subprocesses: + process.terminate() + print("All scripts terminated.") + + + + + + + + + + + + +# Additional cleanup or logging if needed +print("All scripts completed.") diff --git a/archive/automized_run_scripts/run_scripts.bat b/archive/automized_run_scripts/run_scripts.bat index 43f4645..b48f45b 100644 --- a/archive/automized_run_scripts/run_scripts.bat +++ b/archive/automized_run_scripts/run_scripts.bat @@ -1,30 +1,30 @@ -@echo off - -REM Set the Python script to be executed 5 seconds before others -set pre_execution_script="p110_connect.py" - -REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py") -REM set python_scripts=("master_collector.py") - -REM Start the pre-execution script -start "" python %pre_execution_script% - -REM Wait for the pre-execution script to complete -:wait_for_pre_execution -ping 127.0.0.1 -n 6 > nul -tasklist | find "python.exe" | findstr "%pre_execution_script%" > nul -if errorlevel 1 goto pre_execution_completed -goto wait_for_pre_execution - -:pre_execution_completed -REM Wait for an additional 5 seconds -timeout 5 >NUL - -REM Loop through the other scripts and start them -for %%i in %python_scripts% do ( - start "" python %%i - echo Started %%i -) - +@echo off + +REM Set the Python script to be executed 5 seconds before others +set pre_execution_script="p110_connect.py" + +REM Set the list of Python scripts to start +set python_scripts=("direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py") +REM set python_scripts=("master_collector.py") + +REM Start the pre-execution script +start "" python %pre_execution_script% + +REM Wait for the pre-execution script to complete +:wait_for_pre_execution +ping 127.0.0.1 -n 6 > nul +tasklist | find "python.exe" | findstr "%pre_execution_script%" > nul +if errorlevel 1 goto pre_execution_completed +goto wait_for_pre_execution + +:pre_execution_completed +REM Wait for an additional 5 seconds +timeout 5 >NUL + +REM Loop through the other scripts and start them +for %%i in %python_scripts% do ( + start "" python %%i + echo Started %%i +) + echo All scripts started successfully. \ No newline at end of file diff --git a/archive/automized_run_scripts/run_scripts.py b/archive/automized_run_scripts/run_scripts.py index 70984c3..ddf4a10 100644 --- a/archive/automized_run_scripts/run_scripts.py +++ b/archive/automized_run_scripts/run_scripts.py @@ -1,50 +1,50 @@ -import subprocess -from p110_connect import connect_and_start_p100 -from direto_xr import scan_and_connect_direto -from elite_rizer import scan_and_connect_rizer -from headwind import scan_and_connect_headwind -# from master_collector import handle_data -import asyncio -import time - -async def headwind(): - await scan_and_connect_headwind() - -async def direto(): - await asyncio.sleep(10) - await scan_and_connect_direto() - -async def rizer(): - await asyncio.sleep(15) - await scan_and_connect_rizer() - -# async def master_collector(): - # await asyncio.sleep(20) - # await handle_data() - # subprocess.run(["python", "master_collector.py"]) - - -async def main(): - - headwind_task = asyncio.create_task(headwind()) - direto_task = asyncio.create_task(direto()) - rizer_task = asyncio.create_task(rizer()) - # collector_task = asyncio.create_task(master_collector()) - - # Wait for all tasks to complete - await asyncio.gather(headwind_task, direto_task, rizer_task) # , collector_task - -# python_scripts = ["direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py"] -print("starting...") -p100 = connect_and_start_p100() -if(p100 == True): - if __name__ == "__main__": - asyncio.run(main()) - -print("All scripts started successfully.") - - - - - - +import subprocess +from p110_connect import connect_and_start_p100 +from direto_xr import scan_and_connect_direto +from elite_rizer import scan_and_connect_rizer +from headwind import scan_and_connect_headwind +# from master_collector import handle_data +import asyncio +import time + +async def headwind(): + await scan_and_connect_headwind() + +async def direto(): + await asyncio.sleep(10) + await scan_and_connect_direto() + +async def rizer(): + await asyncio.sleep(15) + await scan_and_connect_rizer() + +# async def master_collector(): + # await asyncio.sleep(20) + # await handle_data() + # subprocess.run(["python", "master_collector.py"]) + + +async def main(): + + headwind_task = asyncio.create_task(headwind()) + direto_task = asyncio.create_task(direto()) + rizer_task = asyncio.create_task(rizer()) + # collector_task = asyncio.create_task(master_collector()) + + # Wait for all tasks to complete + await asyncio.gather(headwind_task, direto_task, rizer_task) # , collector_task + +# python_scripts = ["direto_xr.py", "elite_rizer.py", "headwind.py", "master_collector.py"] +print("starting...") +p100 = connect_and_start_p100() +if(p100 == True): + if __name__ == "__main__": + asyncio.run(main()) + +print("All scripts started successfully.") + + + + + + diff --git a/archive/automized_run_scripts/run_scripts.sh b/archive/automized_run_scripts/run_scripts.sh index 7445304..7795a94 100755 --- a/archive/automized_run_scripts/run_scripts.sh +++ b/archive/automized_run_scripts/run_scripts.sh @@ -1,27 +1,27 @@ -#!/bin/bash - -# Set the Python script to be executed 5 seconds before others -pre_execution_script="p110_connect.py" - -# Set the list of Python scripts to start -python_scripts=("direto_xr.py" "elite_rizer.py" "headwind.py" "master_collector.py") -# python_scripts=("master_collector.py") - -# Start the pre-execution script -python "$pre_execution_script" & - -# Wait for the pre-execution script to complete -while ps | grep -v grep | grep -q "python $pre_execution_script"; do - sleep 15 -done - -# Wait for an additional 5 seconds -sleep 10 - -# Loop through the other scripts and start them -for script in "${python_scripts[@]}"; do - python "$script" & - echo "Started $script" -done - -echo "All scripts started successfully." +#!/bin/bash + +# Set the Python script to be executed 5 seconds before others +pre_execution_script="p110_connect.py" + +# Set the list of Python scripts to start +python_scripts=("direto_xr.py" "elite_rizer.py" "headwind.py" "master_collector.py") +# python_scripts=("master_collector.py") + +# Start the pre-execution script +python "$pre_execution_script" & + +# Wait for the pre-execution script to complete +while ps | grep -v grep | grep -q "python $pre_execution_script"; do + sleep 15 +done + +# Wait for an additional 5 seconds +sleep 10 + +# Loop through the other scripts and start them +for script in "${python_scripts[@]}"; do + python "$script" & + echo "Started $script" +done + +echo "All scripts started successfully." diff --git a/archive/bno/read_bno.py b/archive/bno/read_bno.py index 0c3184e..a744fe5 100644 --- a/archive/bno/read_bno.py +++ b/archive/bno/read_bno.py @@ -1,25 +1,25 @@ -import socket -import json - -UDP_IP = "192.168.9.185" # Listen to all incoming UDP packets -UDP_PORT = 8888 # Same port as used in the ESP32 script - -udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -udp_socket.bind((UDP_IP, UDP_PORT)) - -print(f"Listening for UDP packets on {UDP_IP}:{UDP_PORT}...") - -while True: - data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - received_data = data.decode() - - try: - # Attempt to parse the received JSON data - json_data = json.loads(received_data) - print("Received JSON data:") - print(json.dumps(json_data, indent=4)) # Print the JSON data nicely formatted - except json.JSONDecodeError as e: - print("Received data is not in JSON format.") - print(f"Received data: {received_data}") - +import socket +import json + +UDP_IP = "192.168.9.185" # Listen to all incoming UDP packets +UDP_PORT = 8888 # Same port as used in the ESP32 script + +udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +udp_socket.bind((UDP_IP, UDP_PORT)) + +print(f"Listening for UDP packets on {UDP_IP}:{UDP_PORT}...") + +while True: + data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + received_data = data.decode() + + try: + # Attempt to parse the received JSON data + json_data = json.loads(received_data) + print("Received JSON data:") + print(json.dumps(json_data, indent=4)) # Print the JSON data nicely formatted + except json.JSONDecodeError as e: + print("Received data is not in JSON format.") + print(f"Received data: {received_data}") + print(f"Received data from {addr}") \ No newline at end of file diff --git a/archive/brake/read_brake.py b/archive/brake/read_brake.py index b20fa1d..2f21aa9 100644 --- a/archive/brake/read_brake.py +++ b/archive/brake/read_brake.py @@ -1,25 +1,25 @@ -import socket -import json - -UDP_IP = "192.168.9.185" # Listen to all incoming UDP packets -UDP_PORT = 7777 # Same port as used in the Arduino script - -udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -udp_socket.bind((UDP_IP, UDP_PORT)) - -print(f"Listening for UDP packets on {UDP_IP}:{UDP_PORT}...") - -while True: - data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - received_data = data.decode() - - try: - # Attempt to parse the received JSON data - json_data = json.loads(received_data) - print("Received JSON data:") - print(json.dumps(json_data, indent=4)) # Print the JSON data nicely formatted - except json.JSONDecodeError as e: - print("Received data is not in JSON format.") - print(f"Received data: {received_data}") - - print(f"Received data from {addr}") +import socket +import json + +UDP_IP = "192.168.9.185" # Listen to all incoming UDP packets +UDP_PORT = 7777 # Same port as used in the Arduino script + +udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +udp_socket.bind((UDP_IP, UDP_PORT)) + +print(f"Listening for UDP packets on {UDP_IP}:{UDP_PORT}...") + +while True: + data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + received_data = data.decode() + + try: + # Attempt to parse the received JSON data + json_data = json.loads(received_data) + print("Received JSON data:") + print(json.dumps(json_data, indent=4)) # Print the JSON data nicely formatted + except json.JSONDecodeError as e: + print("Received data is not in JSON format.") + print(f"Received data: {received_data}") + + print(f"Received data from {addr}") diff --git a/archive/collector_scripts/data_collector.py b/archive/collector_scripts/data_collector.py index 66a4182..e64039a 100644 --- a/archive/collector_scripts/data_collector.py +++ b/archive/collector_scripts/data_collector.py @@ -1,18 +1,18 @@ -class DataCollector: - def __init__(self): - self.speed_value = 0 - self.steering_value = 0 - self.brake_value = 0 - self.bno_value = 0 - - def collect_speed(self, speed): - self.speed_value = speed - - def collect_steering(self, steering): - self.steering_value = steering - - def collect_brake(self, brake): - self.brake_value = brake - - def collect_bno(self, bno): +class DataCollector: + def __init__(self): + self.speed_value = 0 + self.steering_value = 0 + self.brake_value = 0 + self.bno_value = 0 + + def collect_speed(self, speed): + self.speed_value = speed + + def collect_steering(self, steering): + self.steering_value = steering + + def collect_brake(self, brake): + self.brake_value = brake + + def collect_bno(self, bno): self.bno_value = bno \ No newline at end of file diff --git a/archive/direto/read_speed.py b/archive/direto/read_speed.py index f90019b..da56d8d 100644 --- a/archive/direto/read_speed.py +++ b/archive/direto/read_speed.py @@ -1,154 +1,154 @@ -import asyncio -from bleak import BleakScanner, BleakClient -import struct - -class BluetoothCallback: - def __init__(self): - self.received_data = 0 # Initialize with None or any default value - - async def notify_speed_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] - output = result * 0.01 - normalized_output = normalize_speed_value(output, 0.0, 3.5) - - if output < 0: - output = abs(output) - print(normalized_output) - - self.received_data = normalized_output - - -device_name = "DIRETO XR" -DEVICEID = "" - -service_uuid = "00001826-0000-1000-8000-00805f9b34fb" # Direto - Fitness Machine Service -SERVICE = "" - -characteristic_uuid = "00002ad2-0000-1000-8000-00805f9b34fb" # Direto -CHARACTERISTIC = "" - -value_to_write = "" -old_value = "" - -def normalize_speed_value(value, min_val, max_val): - range_val = max_val - min_val - normalized_value = (value - min_val) / range_val - return normalized_value - -''' -async def on_notification(sender, data): - # collection = db["sensor_values"] # Sammlungsname // outsourcen zur api - # data = data[0] - # value = data.decode('utf-8') - - # Daten in die MongoDB schreiben - # entry = {"value": value} - # integer_value = int.from_bytes(data, byteorder='big') - #collection.insert_one(entry) - # print(f"Data written to MongoDB: {entry}") - # print(f"Data written to MongoDB: {data}") - if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] - output = result * 0.01 - normalized_output = normalize_speed_value(output, 0.0, 3.5) - - if output < 0: - output = abs(output) - print(normalized_output) -''' -''' - if (value[6] != value_to_write): - value_to_write = value[6] - if(entry_id == None): - write_in_db(value_to_write) - entry_id = await get_id() - #print("Entry ID: "+entry_id) - #message = value_to_write - print("Test", value_to_write) - # is_first_entry = False - else: - #message = value_to_write - print(value_to_write) - # await update_in_db(entry_id, value_to_write) - write_in_db(value_to_write) - logger.info("Updateing for id: %r ....With Value %r", entry_id, value_to_write) - #print("Updateing for id: ", entry_id, "....With Value ", value_to_write) - # print("old value: ", old_value) - - - old_value = value_to_write - logger.info( - " [Characteristic] %s (%s), Value: %r", - characteristic, - ",".join(characteristic.properties) , - # value[0], - ) -''' - -async def scan_and_connect(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_uuid - global CHARACTERISTIC - - global value_to_write - global old_value - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - await stop_event.wait() - - if(DEVICEID != ""): - # Connecting to BLE Device - async with BleakClient(DEVICEID, timeout=120) as client: - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service.properties) - - if (service.uuid == service_uuid): - SERVICE = service - print("[service uuid] ", SERVICE.uuid) - - if (SERVICE != ""): - # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: - # if ("notify" in characteristic.properties): - print("[Characteristic] %s", characteristic, characteristic.properties) - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - print("CHARACTERISTIC: ", characteristic, characteristic.properties) - if ("notify" in characteristic.properties): - bluetooth_callback = BluetoothCallback() - - while True: - try: - await client.start_notify(characteristic.uuid, bluetooth_callback.notify_speed_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - - except Exception as e: - print("Error: ", e) -asyncio.run(scan_and_connect()) - - - +import asyncio +from bleak import BleakScanner, BleakClient +import struct + +class BluetoothCallback: + def __init__(self): + self.received_data = 0 # Initialize with None or any default value + + async def notify_speed_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] + output = result * 0.01 + normalized_output = normalize_speed_value(output, 0.0, 3.5) + + if output < 0: + output = abs(output) + print(normalized_output) + + self.received_data = normalized_output + + +device_name = "DIRETO XR" +DEVICEID = "" + +service_uuid = "00001826-0000-1000-8000-00805f9b34fb" # Direto - Fitness Machine Service +SERVICE = "" + +characteristic_uuid = "00002ad2-0000-1000-8000-00805f9b34fb" # Direto +CHARACTERISTIC = "" + +value_to_write = "" +old_value = "" + +def normalize_speed_value(value, min_val, max_val): + range_val = max_val - min_val + normalized_value = (value - min_val) / range_val + return normalized_value + +''' +async def on_notification(sender, data): + # collection = db["sensor_values"] # Sammlungsname // outsourcen zur api + # data = data[0] + # value = data.decode('utf-8') + + # Daten in die MongoDB schreiben + # entry = {"value": value} + # integer_value = int.from_bytes(data, byteorder='big') + #collection.insert_one(entry) + # print(f"Data written to MongoDB: {entry}") + # print(f"Data written to MongoDB: {data}") + if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] + output = result * 0.01 + normalized_output = normalize_speed_value(output, 0.0, 3.5) + + if output < 0: + output = abs(output) + print(normalized_output) +''' +''' + if (value[6] != value_to_write): + value_to_write = value[6] + if(entry_id == None): + write_in_db(value_to_write) + entry_id = await get_id() + #print("Entry ID: "+entry_id) + #message = value_to_write + print("Test", value_to_write) + # is_first_entry = False + else: + #message = value_to_write + print(value_to_write) + # await update_in_db(entry_id, value_to_write) + write_in_db(value_to_write) + logger.info("Updateing for id: %r ....With Value %r", entry_id, value_to_write) + #print("Updateing for id: ", entry_id, "....With Value ", value_to_write) + # print("old value: ", old_value) + + + old_value = value_to_write + logger.info( + " [Characteristic] %s (%s), Value: %r", + characteristic, + ",".join(characteristic.properties) , + # value[0], + ) +''' + +async def scan_and_connect(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_uuid + global CHARACTERISTIC + + global value_to_write + global old_value + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + await stop_event.wait() + + if(DEVICEID != ""): + # Connecting to BLE Device + async with BleakClient(DEVICEID, timeout=120) as client: + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service.properties) + + if (service.uuid == service_uuid): + SERVICE = service + print("[service uuid] ", SERVICE.uuid) + + if (SERVICE != ""): + # print("SERVICE", SERVICE) + for characteristic in SERVICE.characteristics: + # if ("notify" in characteristic.properties): + print("[Characteristic] %s", characteristic, characteristic.properties) + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + print("CHARACTERISTIC: ", characteristic, characteristic.properties) + if ("notify" in characteristic.properties): + bluetooth_callback = BluetoothCallback() + + while True: + try: + await client.start_notify(characteristic.uuid, bluetooth_callback.notify_speed_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + + except Exception as e: + print("Error: ", e) +asyncio.run(scan_and_connect()) + + + diff --git a/archive/direto/write_resistance.py b/archive/direto/write_resistance.py index 9bb88f0..b4cce53 100644 --- a/archive/direto/write_resistance.py +++ b/archive/direto/write_resistance.py @@ -1,78 +1,78 @@ -import asyncio -from bleak import BleakScanner, BleakClient - -class BluetoothCallback: - def __init__(self): - self.received_data = 0 # Initialize with None or any default value - - async def notify_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - print(data) - - -device_name = "DIRETO XR" # Replace with the name of your desired BLE device -DEVICE = "" - -service_uuid = "00001826-0000-1000-8000-00805f9b34fb" # Direto XR -SERVICE = "" - -characteristic_uuid = "00002ad9-0000-1000-8000-00805f9b34fb" # Direto XR -CHARACTERISTIC = "" - -async def scan_and_connect(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_uuid - global CHARACTERISTIC - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - await stop_event.wait() - - if(DEVICEID != ""): - # Connecting to BLE Device - async with BleakClient(DEVICEID, timeout=60) as client: - print("Device ID ", DEVICEID) - for service in client.services: - - if (service.uuid == service_uuid): - SERVICE = service - - if (SERVICE != ""): - for characteristic in SERVICE.characteristics: - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - - bluetooth_callback = BluetoothCallback() - while True: - try: - await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid - except Exception as e: - print("Error: ", e) - resistance_input = input("Enter a value between 1-100 to set the resistance level. (or 'x' to exit): ") - resistance_value = int(resistance_input) - try: - if 1 <= resistance_value <= 100: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, resistance_value])) - elif resistance_input.lower() == 'x': - await client.stop_notify(CHARACTERISTIC) # characteristic.uuid - await asyncio.sleep(1) - break - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - +import asyncio +from bleak import BleakScanner, BleakClient + +class BluetoothCallback: + def __init__(self): + self.received_data = 0 # Initialize with None or any default value + + async def notify_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + print(data) + + +device_name = "DIRETO XR" # Replace with the name of your desired BLE device +DEVICE = "" + +service_uuid = "00001826-0000-1000-8000-00805f9b34fb" # Direto XR +SERVICE = "" + +characteristic_uuid = "00002ad9-0000-1000-8000-00805f9b34fb" # Direto XR +CHARACTERISTIC = "" + +async def scan_and_connect(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_uuid + global CHARACTERISTIC + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + await stop_event.wait() + + if(DEVICEID != ""): + # Connecting to BLE Device + async with BleakClient(DEVICEID, timeout=60) as client: + print("Device ID ", DEVICEID) + for service in client.services: + + if (service.uuid == service_uuid): + SERVICE = service + + if (SERVICE != ""): + for characteristic in SERVICE.characteristics: + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + + bluetooth_callback = BluetoothCallback() + while True: + try: + await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid + except Exception as e: + print("Error: ", e) + resistance_input = input("Enter a value between 1-100 to set the resistance level. (or 'x' to exit): ") + resistance_value = int(resistance_input) + try: + if 1 <= resistance_value <= 100: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, resistance_value])) + elif resistance_input.lower() == 'x': + await client.stop_notify(CHARACTERISTIC) # characteristic.uuid + await asyncio.sleep(1) + break + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + asyncio.run(scan_and_connect()) \ No newline at end of file diff --git a/archive/docker/Dockerfile b/archive/docker/Dockerfile index 5e0a781..345de4c 100644 --- a/archive/docker/Dockerfile +++ b/archive/docker/Dockerfile @@ -1,40 +1,40 @@ -# Use the latest Ubuntu LTS release as the base image -FROM ubuntu:20.04 - -# Set the timezone environment variable -ENV TZ=Europe/Berlin - -# Create the /etc/timezone file with the timezone data and link /etc/localtime to the correct timezone -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -# Update package lists and install essential packages -RUN apt-get update -y && \ - apt-get install -y \ - python3 \ - python3-pip \ - libglib2.0-dev \ - dbus \ - bluez \ - bluetooth \ - openssh-server && \ - mkdir /var/run/sshd \ - && apt-get clean - -# Set the working directory in the container -WORKDIR /python_scripts - -# COPY entrypoint.sh . - -# Copy the rest of the application code to the container -COPY ./scripts/ /python_scripts/ - -# Install the API dependencies -RUN pip3 install -r requirements.txt - -# Expose the port the container will run on -EXPOSE 22 - -CMD ["./entrypoint.sh"] - -# Command to start the ssh +# Use the latest Ubuntu LTS release as the base image +FROM ubuntu:20.04 + +# Set the timezone environment variable +ENV TZ=Europe/Berlin + +# Create the /etc/timezone file with the timezone data and link /etc/localtime to the correct timezone +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Update package lists and install essential packages +RUN apt-get update -y && \ + apt-get install -y \ + python3 \ + python3-pip \ + libglib2.0-dev \ + dbus \ + bluez \ + bluetooth \ + openssh-server && \ + mkdir /var/run/sshd \ + && apt-get clean + +# Set the working directory in the container +WORKDIR /python_scripts + +# COPY entrypoint.sh . + +# Copy the rest of the application code to the container +COPY ./scripts/ /python_scripts/ + +# Install the API dependencies +RUN pip3 install -r requirements.txt + +# Expose the port the container will run on +EXPOSE 22 + +CMD ["./entrypoint.sh"] + +# Command to start the ssh # CMD ["/usr/sbin/sshd", "-D"] \ No newline at end of file diff --git a/archive/docker/docker-compose.yml b/archive/docker/docker-compose.yml index 804ad0e..1b1c092 100644 --- a/archive/docker/docker-compose.yml +++ b/archive/docker/docker-compose.yml @@ -1,10 +1,10 @@ -version: '3' -services: - raspi-container: - build: . - container_name: raspi-container - network_mode: host - privileged: true - tty: true # Need to be used as long there are no scripts or other services running in the container - # ports: +version: '3' +services: + raspi-container: + build: . + container_name: raspi-container + network_mode: host + privileged: true + tty: true # Need to be used as long there are no scripts or other services running in the container + # ports: # - "2222:22" \ No newline at end of file diff --git a/archive/docker/entrypoint.sh b/archive/docker/entrypoint.sh index 2ac7c80..b2bacaa 100644 --- a/archive/docker/entrypoint.sh +++ b/archive/docker/entrypoint.sh @@ -1,8 +1,8 @@ -#!/bin/bash - -# start services -service dbus start -service bluetooth start - -# Start sshd service +#!/bin/bash + +# start services +service dbus start +service bluetooth start + +# Start sshd service # /usr/sbin/sshd -D \ No newline at end of file diff --git a/archive/headwind/archive_old_write_headwind.py b/archive/headwind/archive_old_write_headwind.py index 5ab71ae..879c869 100644 --- a/archive/headwind/archive_old_write_headwind.py +++ b/archive/headwind/archive_old_write_headwind.py @@ -1,223 +1,223 @@ -import asyncio -from bleak import BleakScanner, BleakClient -import struct - - -class BluetoothCallback: - def __init__(self): - self.received_data = 0 # Initialize with None or any default value - - async def notify_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - print(data) - - ''' - if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] - output = result * 0.01 - - if output < 0: - output = abs(output) - print(output) - - self.received_data = output - ''' - - -device_name = "HEADWIND BC55" # Replace with the name of your desired BLE device -DEVICE = "" - -service_uuid = "a026ee0c-0a7d-4ab3-97fa-f1500f9feb8b" -SERVICE = "" - -characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" -CHARACTERISTIC = "" - -async def scan_and_connect(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_uuid - global CHARACTERISTIC - - global value_to_write - global old_value - - global is_first_entry - global run_read_loop - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - await stop_event.wait() - - if(DEVICEID != ""): - # Connecting to BLE Device - # print("DEVICEID: ",DEVICEID.address) - async with BleakClient(DEVICEID, timeout=60) as client: - print("Device ID ", DEVICEID) - for service in client.services: - # print("service: ", service) - - if (service.uuid == service_uuid): - SERVICE = service - # print("[service uuid] ", SERVICE.uuid) - - if (SERVICE != ""): - # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: - # if ("notify" in characteristic.properties): - # print("[Characteristic] %s", characteristic, characteristic.properties) - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - # print("CHARACTERISTIC: ", characteristic, characteristic.properties) - - if ("write-without-response" in characteristic.properties): - # while True: - print("Type CHARACTERISTIC: ", type(CHARACTERISTIC)) - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - print("client connection: ", client.is_connected) - # await client.disconnect() - # print("client connection: ", client.is_connected) - # await asyncio.sleep(10) # keeps the connection open for 10 seconds - # print("TIMER") - # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 100])) - ''' - if ("notify" in characteristic.properties): - - bluetooth_callback = BluetoothCallback() - while True: - try: - - # value = await client.read_gatt_char(characteristic.uuid) - # print("Die CHARARARA iSt: ", CHARACTERISTIC, CHARACTERISTIC.properties) - # await client.start_notify(characteristic.uuid, on_notification) - await client.start_notify(characteristic.uuid, bluetooth_callback.notify_callback) - print("client connection: ", client.is_connected) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - - except Exception as e: - print("Error: ", e) - # run_read_loop = False - - ''' - - # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 50])) # working if the fan is turned on and the script starting again - while True: - print("client connection: ", client.is_connected) - await client.disconnect() - print("client connection: ", client.is_connected) - speed_input = input("Enter a value between 1-100 to set the fan speed. Enter 0 to turn off the fan (or 'x' to exit): ") - if speed_input.lower() == 'x': - break - try: - speed_value = int(speed_input) - if 1 <= speed_value <= 100: - # Convert speed value to byte and write to characteristic - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - elif speed_value == 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - # bluetooth_callback = BluetoothCallback() - - - - - -asyncio.run(scan_and_connect()) - - -async def connect_to_device(DEVICEID, SERVICE): - if(DEVICEID != ""): - # Connecting to BLE Device - # print("DEVICEID: ",DEVICEID.address) - async with BleakClient(DEVICEID, timeout=60) as client: - print("Device ID ", DEVICEID) - for service in client.services: - # print("service: ", service) - - if (service.uuid == service_uuid): - SERVICE = service - # print("[service uuid] ", SERVICE.uuid) - - if (SERVICE != ""): - # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: - # if ("notify" in characteristic.properties): - # print("[Characteristic] %s", characteristic, characteristic.properties) - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - # print("CHARACTERISTIC: ", characteristic, characteristic.properties) - - if ("write-without-response" in characteristic.properties): - # while True: - print("Type CHARACTERISTIC: ", type(CHARACTERISTIC)) - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - print("client connection: ", client.is_connected) - # await client.disconnect() - # print("client connection: ", client.is_connected) - # await asyncio.sleep(10) # keeps the connection open for 10 seconds - # print("TIMER") - # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 100])) - ''' - if ("notify" in characteristic.properties): - - bluetooth_callback = BluetoothCallback() - while True: - try: - - # value = await client.read_gatt_char(characteristic.uuid) - # print("Die CHARARARA iSt: ", CHARACTERISTIC, CHARACTERISTIC.properties) - # await client.start_notify(characteristic.uuid, on_notification) - await client.start_notify(characteristic.uuid, bluetooth_callback.notify_callback) - print("client connection: ", client.is_connected) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - - except Exception as e: - print("Error: ", e) - # run_read_loop = False - - ''' - - # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 50])) # working if the fan is turned on and the script starting again - while True: - print("client connection: ", client.is_connected) - await client.disconnect() - print("client connection: ", client.is_connected) - speed_input = input("Enter a value between 1-100 to set the fan speed. Enter 0 to turn off the fan (or 'x' to exit): ") - if speed_input.lower() == 'x': - break - try: - speed_value = int(speed_input) - if 1 <= speed_value <= 100: - # Convert speed value to byte and write to characteristic - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - elif speed_value == 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - # bluetooth_callback = BluetoothCallback() +import asyncio +from bleak import BleakScanner, BleakClient +import struct + + +class BluetoothCallback: + def __init__(self): + self.received_data = 0 # Initialize with None or any default value + + async def notify_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + print(data) + + ''' + if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] + output = result * 0.01 + + if output < 0: + output = abs(output) + print(output) + + self.received_data = output + ''' + + +device_name = "HEADWIND BC55" # Replace with the name of your desired BLE device +DEVICE = "" + +service_uuid = "a026ee0c-0a7d-4ab3-97fa-f1500f9feb8b" +SERVICE = "" + +characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" +CHARACTERISTIC = "" + +async def scan_and_connect(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_uuid + global CHARACTERISTIC + + global value_to_write + global old_value + + global is_first_entry + global run_read_loop + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + await stop_event.wait() + + if(DEVICEID != ""): + # Connecting to BLE Device + # print("DEVICEID: ",DEVICEID.address) + async with BleakClient(DEVICEID, timeout=60) as client: + print("Device ID ", DEVICEID) + for service in client.services: + # print("service: ", service) + + if (service.uuid == service_uuid): + SERVICE = service + # print("[service uuid] ", SERVICE.uuid) + + if (SERVICE != ""): + # print("SERVICE", SERVICE) + for characteristic in SERVICE.characteristics: + # if ("notify" in characteristic.properties): + # print("[Characteristic] %s", characteristic, characteristic.properties) + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + # print("CHARACTERISTIC: ", characteristic, characteristic.properties) + + if ("write-without-response" in characteristic.properties): + # while True: + print("Type CHARACTERISTIC: ", type(CHARACTERISTIC)) + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + print("client connection: ", client.is_connected) + # await client.disconnect() + # print("client connection: ", client.is_connected) + # await asyncio.sleep(10) # keeps the connection open for 10 seconds + # print("TIMER") + # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 100])) + ''' + if ("notify" in characteristic.properties): + + bluetooth_callback = BluetoothCallback() + while True: + try: + + # value = await client.read_gatt_char(characteristic.uuid) + # print("Die CHARARARA iSt: ", CHARACTERISTIC, CHARACTERISTIC.properties) + # await client.start_notify(characteristic.uuid, on_notification) + await client.start_notify(characteristic.uuid, bluetooth_callback.notify_callback) + print("client connection: ", client.is_connected) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + + except Exception as e: + print("Error: ", e) + # run_read_loop = False + + ''' + + # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 50])) # working if the fan is turned on and the script starting again + while True: + print("client connection: ", client.is_connected) + await client.disconnect() + print("client connection: ", client.is_connected) + speed_input = input("Enter a value between 1-100 to set the fan speed. Enter 0 to turn off the fan (or 'x' to exit): ") + if speed_input.lower() == 'x': + break + try: + speed_value = int(speed_input) + if 1 <= speed_value <= 100: + # Convert speed value to byte and write to characteristic + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + elif speed_value == 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + # bluetooth_callback = BluetoothCallback() + + + + + +asyncio.run(scan_and_connect()) + + +async def connect_to_device(DEVICEID, SERVICE): + if(DEVICEID != ""): + # Connecting to BLE Device + # print("DEVICEID: ",DEVICEID.address) + async with BleakClient(DEVICEID, timeout=60) as client: + print("Device ID ", DEVICEID) + for service in client.services: + # print("service: ", service) + + if (service.uuid == service_uuid): + SERVICE = service + # print("[service uuid] ", SERVICE.uuid) + + if (SERVICE != ""): + # print("SERVICE", SERVICE) + for characteristic in SERVICE.characteristics: + # if ("notify" in characteristic.properties): + # print("[Characteristic] %s", characteristic, characteristic.properties) + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + # print("CHARACTERISTIC: ", characteristic, characteristic.properties) + + if ("write-without-response" in characteristic.properties): + # while True: + print("Type CHARACTERISTIC: ", type(CHARACTERISTIC)) + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + print("client connection: ", client.is_connected) + # await client.disconnect() + # print("client connection: ", client.is_connected) + # await asyncio.sleep(10) # keeps the connection open for 10 seconds + # print("TIMER") + # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 100])) + ''' + if ("notify" in characteristic.properties): + + bluetooth_callback = BluetoothCallback() + while True: + try: + + # value = await client.read_gatt_char(characteristic.uuid) + # print("Die CHARARARA iSt: ", CHARACTERISTIC, CHARACTERISTIC.properties) + # await client.start_notify(characteristic.uuid, on_notification) + await client.start_notify(characteristic.uuid, bluetooth_callback.notify_callback) + print("client connection: ", client.is_connected) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + + except Exception as e: + print("Error: ", e) + # run_read_loop = False + + ''' + + # await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, 50])) # working if the fan is turned on and the script starting again + while True: + print("client connection: ", client.is_connected) + await client.disconnect() + print("client connection: ", client.is_connected) + speed_input = input("Enter a value between 1-100 to set the fan speed. Enter 0 to turn off the fan (or 'x' to exit): ") + if speed_input.lower() == 'x': + break + try: + speed_value = int(speed_input) + if 1 <= speed_value <= 100: + # Convert speed value to byte and write to characteristic + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + elif speed_value == 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + # bluetooth_callback = BluetoothCallback() diff --git a/archive/headwind/archive_write_headwind_with_methods.py b/archive/headwind/archive_write_headwind_with_methods.py index c364ee6..2ec7ba5 100644 --- a/archive/headwind/archive_write_headwind_with_methods.py +++ b/archive/headwind/archive_write_headwind_with_methods.py @@ -1,95 +1,95 @@ -import asyncio -from bleak import BleakScanner, BleakClient -import struct - -device_name = "HEADWIND BC55" # Replace with the name of your desired BLE device -DEVICE = "" - -service_uuid = "a026ee0c-0a7d-4ab3-97fa-f1500f9feb8b" -# SERVICE = "" - -characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" -# CHARACTERISTIC = "" - -async def scan_and_connect(): - global device_name - - global service_uuid - # global SERVICE - - global characteristic_uuid - # global CHARACTERISTIC - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - await stop_event.wait() - - if(DEVICEID != ""): - while True: - speed_input = input("Enter a value between 2-100 to set the fan speed. Enter 1 to turn on the fan. Enter 0 to turn off the fan (or 'x' to exit): ") - if speed_input.lower() == 'x': - break - try: - speed_value = int(speed_input) - if 0 <= speed_value <= 100: - # Convert speed value to byte and write to characteristic - await connect_to_device(DEVICEID, service_uuid, characteristic_uuid, speed_value) - else: - print("Speed value should be between 0 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 0 and 100.") - -async def connect_to_device(DEVICEID, service_uuid, characteristic_uuid, fan_speed): # fan_speed is an integer between 0-100, 0 is off, 1 is on, and every other value adjust the fan_speed - SERVICE = "" - CHARACTERISTIC = "" - - try: - async with BleakClient(DEVICEID, timeout=60) as client: - print("Device ID ", DEVICEID) - for service in client.services: - # print("service: ", service) - - if (service.uuid == service_uuid): - SERVICE = service - # print("[service uuid] ", SERVICE.uuid) - - if (SERVICE != ""): - # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: - # if ("notify" in characteristic.properties): - # print("[Characteristic] %s", characteristic, characteristic.properties) - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - - - # if ("write-without-response" in characteristic.properties): - try: - if(fan_speed == 1): - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - # print("client connection: ", client.is_connected) - elif(fan_speed == 0): - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) - - elif(2 <= fan_speed <= 100 ): - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, fan_speed])) - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - except: - print("Client connection status: ",client.is_connected(),". Device connection failed, reconnecting...") - - +import asyncio +from bleak import BleakScanner, BleakClient +import struct + +device_name = "HEADWIND BC55" # Replace with the name of your desired BLE device +DEVICE = "" + +service_uuid = "a026ee0c-0a7d-4ab3-97fa-f1500f9feb8b" +# SERVICE = "" + +characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" +# CHARACTERISTIC = "" + +async def scan_and_connect(): + global device_name + + global service_uuid + # global SERVICE + + global characteristic_uuid + # global CHARACTERISTIC + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + await stop_event.wait() + + if(DEVICEID != ""): + while True: + speed_input = input("Enter a value between 2-100 to set the fan speed. Enter 1 to turn on the fan. Enter 0 to turn off the fan (or 'x' to exit): ") + if speed_input.lower() == 'x': + break + try: + speed_value = int(speed_input) + if 0 <= speed_value <= 100: + # Convert speed value to byte and write to characteristic + await connect_to_device(DEVICEID, service_uuid, characteristic_uuid, speed_value) + else: + print("Speed value should be between 0 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 0 and 100.") + +async def connect_to_device(DEVICEID, service_uuid, characteristic_uuid, fan_speed): # fan_speed is an integer between 0-100, 0 is off, 1 is on, and every other value adjust the fan_speed + SERVICE = "" + CHARACTERISTIC = "" + + try: + async with BleakClient(DEVICEID, timeout=60) as client: + print("Device ID ", DEVICEID) + for service in client.services: + # print("service: ", service) + + if (service.uuid == service_uuid): + SERVICE = service + # print("[service uuid] ", SERVICE.uuid) + + if (SERVICE != ""): + # print("SERVICE", SERVICE) + for characteristic in SERVICE.characteristics: + # if ("notify" in characteristic.properties): + # print("[Characteristic] %s", characteristic, characteristic.properties) + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + + + # if ("write-without-response" in characteristic.properties): + try: + if(fan_speed == 1): + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + # print("client connection: ", client.is_connected) + elif(fan_speed == 0): + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) + + elif(2 <= fan_speed <= 100 ): + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, fan_speed])) + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + except: + print("Client connection status: ",client.is_connected(),". Device connection failed, reconnecting...") + + asyncio.run(scan_and_connect()) \ No newline at end of file diff --git a/archive/p100/starting_p100.py b/archive/p100/starting_p100.py index 07d8175..c8be7fd 100644 --- a/archive/p100/starting_p100.py +++ b/archive/p100/starting_p100.py @@ -1,12 +1,12 @@ -from PyP100 import PyP100 -import time - -''' -Restarting the tapo p100 plug via python to start the bicycle simulator -''' -p100 = PyP100.P100("192.168.9.173", "unitylab.hhn3@gmail.com", "Unitylab") -# p100.handshake() # deprecated -# p100.login() # deprecated -p100.turnOff() -time.sleep(5) -p100.turnOn() +from PyP100 import PyP100 +import time + +''' +Restarting the tapo p100 plug via python to start the bicycle simulator +''' +p100 = PyP100.P100("192.168.9.173", "unitylab.hhn3@gmail.com", "Unitylab") +# p100.handshake() # deprecated +# p100.login() # deprecated +p100.turnOff() +time.sleep(5) +p100.turnOn() diff --git a/archive/rizer/read_steering.py b/archive/rizer/read_steering.py index 94aec88..1517925 100644 --- a/archive/rizer/read_steering.py +++ b/archive/rizer/read_steering.py @@ -1,95 +1,95 @@ -import asyncio -from bleak import BleakScanner, BleakClient - -class BluetoothCallback: - def __init__(self): - self.received_data = 0 # Initialize with None or any default value - - async def notify_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_data = steering - print(steering) - - -device_name = "RIZER" -DEVICEID = "" - -service_uuid = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE = "" - -characteristic_uuid = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC = "" - -value_to_write = "" -old_value = "" - -async def scan_and_connect(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_uuid - global CHARACTERISTIC - - global value_to_write - global old_value - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - await stop_event.wait() - - if(DEVICEID != ""): - # Connecting to BLE Device - async with BleakClient(DEVICEID, timeout=120) as client: - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service.properties) - - if (service.uuid == service_uuid): - SERVICE = service - print("[service uuid] ", SERVICE.uuid) - - if (SERVICE != ""): - # print("SERVICE", SERVICE) - for characteristic in SERVICE.characteristics: - # if ("notify" in characteristic.properties): - print("[Characteristic] %s", characteristic, characteristic.properties) - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - print("CHARACTERISTIC: ", characteristic, characteristic.properties) - if ("notify" in characteristic.properties): - bluetooth_callback = BluetoothCallback() - - while True: - try: - await client.start_notify(characteristic.uuid, bluetooth_callback.notify_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - - except Exception as e: - print("Error: ", e) - - -asyncio.run(scan_and_connect()) - - - +import asyncio +from bleak import BleakScanner, BleakClient + +class BluetoothCallback: + def __init__(self): + self.received_data = 0 # Initialize with None or any default value + + async def notify_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_data = steering + print(steering) + + +device_name = "RIZER" +DEVICEID = "" + +service_uuid = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE = "" + +characteristic_uuid = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTIC = "" + +value_to_write = "" +old_value = "" + +async def scan_and_connect(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_uuid + global CHARACTERISTIC + + global value_to_write + global old_value + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + await stop_event.wait() + + if(DEVICEID != ""): + # Connecting to BLE Device + async with BleakClient(DEVICEID, timeout=120) as client: + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service.properties) + + if (service.uuid == service_uuid): + SERVICE = service + print("[service uuid] ", SERVICE.uuid) + + if (SERVICE != ""): + # print("SERVICE", SERVICE) + for characteristic in SERVICE.characteristics: + # if ("notify" in characteristic.properties): + print("[Characteristic] %s", characteristic, characteristic.properties) + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + print("CHARACTERISTIC: ", characteristic, characteristic.properties) + if ("notify" in characteristic.properties): + bluetooth_callback = BluetoothCallback() + + while True: + try: + await client.start_notify(characteristic.uuid, bluetooth_callback.notify_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + + except Exception as e: + print("Error: ", e) + + +asyncio.run(scan_and_connect()) + + + diff --git a/archive/rizer/write_height.py b/archive/rizer/write_height.py index 94eb503..7e64f47 100644 --- a/archive/rizer/write_height.py +++ b/archive/rizer/write_height.py @@ -1,98 +1,98 @@ -import asyncio -from bleak import BleakScanner, BleakClient - -class BluetoothCallback: - def __init__(self): - self.received_data = 0 # Initialize with None or any default value - - async def notify_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - print(data) - - -device_name = "RIZER" # Replace with the name of your desired BLE device -DEVICE = "" - -service_uuid = "00001800-0000-1000-8000-00805f9b34fb" -SERVICE = "" - -characteristic_uuid = "00002a04-0000-1000-8000-00805f9b34fb" -CHARACTERISTIC = "" - -async def scan_and_connect(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_uuid - global CHARACTERISTIC - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - await stop_event.wait() - - if(DEVICEID != ""): - # Connecting to BLE Device - async with BleakClient(DEVICEID, timeout=60) as client: - # print("Device ID ", DEVICEID) - - for service in client.services: - print("service: ", service) - for characteristic in service.characteristics: - print("Characteristics: ",characteristic) - - #### To edit - currently cant write to the characteristic - ''' - for service in client.services: - # print("service: ", service) - if (service.uuid == service_uuid): - SERVICE = service - - if (SERVICE != ""): - for characteristic in SERVICE.characteristics: - print(characteristic) - - ### - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - - bluetooth_callback = BluetoothCallback() - while True: - try: - await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid - except Exception as e: - print("Error: ", e) - - speed_input = input("Enter a value between 2-100 to set the fan speed. Enter 1 to turn on the fan. Enter 0 to turn off the fan (or 'x' to exit): ") - speed_value = int(speed_input) - if speed_input.lower() == 'x': - await client.stop_notify(CHARACTERISTIC) # characteristic.uuid - await asyncio.sleep(1) - break - try: - if 2 <= speed_value <= 100: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - elif speed_value == 1: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - elif speed_value == 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - ''' - - +import asyncio +from bleak import BleakScanner, BleakClient + +class BluetoothCallback: + def __init__(self): + self.received_data = 0 # Initialize with None or any default value + + async def notify_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + print(data) + + +device_name = "RIZER" # Replace with the name of your desired BLE device +DEVICE = "" + +service_uuid = "00001800-0000-1000-8000-00805f9b34fb" +SERVICE = "" + +characteristic_uuid = "00002a04-0000-1000-8000-00805f9b34fb" +CHARACTERISTIC = "" + +async def scan_and_connect(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_uuid + global CHARACTERISTIC + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + await stop_event.wait() + + if(DEVICEID != ""): + # Connecting to BLE Device + async with BleakClient(DEVICEID, timeout=60) as client: + # print("Device ID ", DEVICEID) + + for service in client.services: + print("service: ", service) + for characteristic in service.characteristics: + print("Characteristics: ",characteristic) + + #### To edit - currently cant write to the characteristic + ''' + for service in client.services: + # print("service: ", service) + if (service.uuid == service_uuid): + SERVICE = service + + if (SERVICE != ""): + for characteristic in SERVICE.characteristics: + print(characteristic) + + ### + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + + bluetooth_callback = BluetoothCallback() + while True: + try: + await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid + except Exception as e: + print("Error: ", e) + + speed_input = input("Enter a value between 2-100 to set the fan speed. Enter 1 to turn on the fan. Enter 0 to turn off the fan (or 'x' to exit): ") + speed_value = int(speed_input) + if speed_input.lower() == 'x': + await client.stop_notify(CHARACTERISTIC) # characteristic.uuid + await asyncio.sleep(1) + break + try: + if 2 <= speed_value <= 100: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + elif speed_value == 1: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + elif speed_value == 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + ''' + + asyncio.run(scan_and_connect()) \ No newline at end of file diff --git a/collector_scripts/direto_xr.py b/collector_scripts/direto_xr.py index 16c13fa..f081223 100644 --- a/collector_scripts/direto_xr.py +++ b/collector_scripts/direto_xr.py @@ -1,172 +1,172 @@ -import asyncio -from bleak import BleakScanner, BleakClient, exc -import struct -import sys -import socket -from dataReceiverSingleton import DataReceiverSingleton - - -device_name = "DIRETO XR" # Replace with the name of your desired BLE device -DEVICE = "" - -service_uuid = "00001826-0000-1000-8000-00805f9b34fb" # Direto XR -SERVICE = "" - -characteristic_resistance_uuid = "00002ad9-0000-1000-8000-00805f9b34fb" # Write Resistance -characteristic_speed_uuid = "00002ad2-0000-1000-8000-00805f9b34fb" # Read Speed - - -CHARACTERISTIC_RESISTANCE = "" -CHARACTERISTIC_SPEED = "" - - -class BluetoothCallback: - def __init__(self): - self.received_speed_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the direto data to the master_collector.py script via UDP over localhost - # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC - # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi - self.udp_port = 1111 - - async def notify_resistance_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - # print(data) - test = "123" - - - async def notify_speed_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] - output = result * 0.01 - normalized_output = normalize_speed_value(output, 0.0, 2.5) # changed from 3.5 to 2.5 for a better scaling. change it back if the value range is too small - - if output < 0: - output = abs(output) - - print("Normalized Direto Speed: ", normalized_output) - self.received_speed_data = normalized_output - self.send_speed_data_udp(self.received_speed_data) - - - def send_speed_data_udp(self, speed_data): - # Create a UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(speed_data).encode(), (self.udp_ip, self.udp_port)) - - -def normalize_speed_value(value, min_val, max_val): - range_val = max_val - min_val - normalized_value = (value - min_val) / range_val - return normalized_value - - -async def write_resistance(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - await client.start_notify(characteristic, bluetooth_callback.notify_resistance_callback) # characteristic.uuid - except Exception as e: - print("Error: ", e) - # resistance_input = input("Enter a value between 1-100 to set the resistance level. (or 'x' to exit): ") - resistance_input = 60 - resistance_value = int(resistance_input) - try: - if 1 <= resistance_value <= 100: - await client.write_gatt_char(characteristic, bytearray([0x04, resistance_value])) - elif resistance_input.lower() == 'x': - await client.stop_notify(characteristic) # characteristic.uuid - await asyncio.sleep(1) - sys.exit() - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - # print("Test resistance") - - -async def read_speed(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - # await asyncio.sleep(0.5) - await client.start_notify(characteristic, bluetooth_callback.notify_speed_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - # print("Test speed") - - except Exception as e: - print("Error: ", e) - - - -async def scan_and_connect_direto(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_resistance_uuid - global characteristic_speed_uuid - - global CHARACTERISTIC_RESISTANCE - global CHARACTERISTIC_SPEED - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - # print(device) - # print("Test direto") - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - # new - ''' - try: - await stop_event.wait() - except KeyboardInterrupt: - print("Scanning stopped by user.") - scanner.stop() - # new end - ''' - await stop_event.wait() - - if(DEVICEID != ""): - client_is_connected = False - while(client_is_connected == False): - - try: - async with BleakClient(DEVICEID, timeout=90) as client: - client_is_connected = True - print("Client connected to ", DEVICEID.name) - # print("Device ID ", DEVICEID) - for service in client.services: - - if (service.uuid == service_uuid): - SERVICE = service - - if (SERVICE != ""): - for characteristic in SERVICE.characteristics: - if("write" in characteristic.properties and characteristic.uuid == characteristic_resistance_uuid): - CHARACTERISTIC_RESISTANCE = characteristic - # print("Characteristic resistance: ",CHARACTERISTIC_RESISTANCE) - - if("notify" in characteristic.properties and characteristic.uuid == characteristic_speed_uuid): - CHARACTERISTIC_SPEED = characteristic - # print("Characteristic speed: ",CHARACTERISTIC_SPEED) - - while True: - await write_resistance(client, CHARACTERISTIC_RESISTANCE) - await read_speed(client, CHARACTERISTIC_SPEED) - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") - # Add additional error handling or logging as needed - # raise - - - +import asyncio +from bleak import BleakScanner, BleakClient, exc +import struct +import sys +import socket +from dataReceiverSingleton import DataReceiverSingleton + + +device_name = "DIRETO XR" # Replace with the name of your desired BLE device +DEVICE = "" + +service_uuid = "00001826-0000-1000-8000-00805f9b34fb" # Direto XR +SERVICE = "" + +characteristic_resistance_uuid = "00002ad9-0000-1000-8000-00805f9b34fb" # Write Resistance +characteristic_speed_uuid = "00002ad2-0000-1000-8000-00805f9b34fb" # Read Speed + + +CHARACTERISTIC_RESISTANCE = "" +CHARACTERISTIC_SPEED = "" + + +class BluetoothCallback: + def __init__(self): + self.received_speed_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the direto data to the master_collector.py script via UDP over localhost + # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC + # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi + self.udp_port = 1111 + + async def notify_resistance_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + # print(data) + test = "123" + + + async def notify_speed_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + if struct.pack("@h", 1) == struct.pack("h", data, 2)[0] + output = result * 0.01 + normalized_output = normalize_speed_value(output, 0.0, 2.5) # changed from 3.5 to 2.5 for a better scaling. change it back if the value range is too small + + if output < 0: + output = abs(output) + + print("Normalized Direto Speed: ", normalized_output) + self.received_speed_data = normalized_output + self.send_speed_data_udp(self.received_speed_data) + + + def send_speed_data_udp(self, speed_data): + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(speed_data).encode(), (self.udp_ip, self.udp_port)) + + +def normalize_speed_value(value, min_val, max_val): + range_val = max_val - min_val + normalized_value = (value - min_val) / range_val + return normalized_value + + +async def write_resistance(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + await client.start_notify(characteristic, bluetooth_callback.notify_resistance_callback) # characteristic.uuid + except Exception as e: + print("Error: ", e) + # resistance_input = input("Enter a value between 1-100 to set the resistance level. (or 'x' to exit): ") + resistance_input = 60 + resistance_value = int(resistance_input) + try: + if 1 <= resistance_value <= 100: + await client.write_gatt_char(characteristic, bytearray([0x04, resistance_value])) + elif resistance_input.lower() == 'x': + await client.stop_notify(characteristic) # characteristic.uuid + await asyncio.sleep(1) + sys.exit() + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + # print("Test resistance") + + +async def read_speed(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + # await asyncio.sleep(0.5) + await client.start_notify(characteristic, bluetooth_callback.notify_speed_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + # print("Test speed") + + except Exception as e: + print("Error: ", e) + + + +async def scan_and_connect_direto(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_resistance_uuid + global characteristic_speed_uuid + + global CHARACTERISTIC_RESISTANCE + global CHARACTERISTIC_SPEED + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + # print(device) + # print("Test direto") + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + # new + ''' + try: + await stop_event.wait() + except KeyboardInterrupt: + print("Scanning stopped by user.") + scanner.stop() + # new end + ''' + await stop_event.wait() + + if(DEVICEID != ""): + client_is_connected = False + while(client_is_connected == False): + + try: + async with BleakClient(DEVICEID, timeout=90) as client: + client_is_connected = True + print("Client connected to ", DEVICEID.name) + # print("Device ID ", DEVICEID) + for service in client.services: + + if (service.uuid == service_uuid): + SERVICE = service + + if (SERVICE != ""): + for characteristic in SERVICE.characteristics: + if("write" in characteristic.properties and characteristic.uuid == characteristic_resistance_uuid): + CHARACTERISTIC_RESISTANCE = characteristic + # print("Characteristic resistance: ",CHARACTERISTIC_RESISTANCE) + + if("notify" in characteristic.properties and characteristic.uuid == characteristic_speed_uuid): + CHARACTERISTIC_SPEED = characteristic + # print("Characteristic speed: ",CHARACTERISTIC_SPEED) + + while True: + await write_resistance(client, CHARACTERISTIC_RESISTANCE) + await read_speed(client, CHARACTERISTIC_SPEED) + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") + # Add additional error handling or logging as needed + # raise + + + asyncio.run(scan_and_connect_direto()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 0fc487f..8088d12 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -1,175 +1,175 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time -from master_collector import DataReceiver - -DEVICE_NAME = "RIZER" -DEVICE_UUID = "fc:12:65:28:cb:44" - -SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - -INCREASE_TILT_HEX = "060102" -DECREASE_TILT_HEX = "060402" - -steering_service = "" -tilt_service = "" - -stering_ready = 0 -tilt_ready = 0 - -stored_tilt_value = 0 - -CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - -steering_characteristics = "" -tilt_characteristics = "" - - -class BluetoothCallback: - def __init__(self): - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC - # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi - self.udp_port = 2222 - - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print(self.received_steering_data) - - self.send_steering_data_udp(self.received_steering_data) - - #send steering data over udp - def send_steering_data_udp(self, steering_data): - # Create a UDP socket - # print(steering_data) - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) - - def listening_udp(self): - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) - print("Hello: ", udp_tilt_data) - -async def read_steering(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - # print("Test steering") - except Exception as e: - print("Error: ", e) - -async def write_tilt(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - #TODO Write bt stuff - print("BT stuff") - await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) - stored_tilt_value += 0.5 - except Exception as e: - print("Error: ", e) - -# check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) -def get_new_tilt_value(): - receiver = DataReceiver() - try: - receiver.start_udp_listener() - tilt_value = receiver.get_tilt() - print("RIZER tilt: ", tilt_value) - if tilt_value != stored_tilt_value: - stored_tilt_value = tilt_value - - except Exception as e: - print("Error: ", e) - - -async def scan_and_connect_rizer(): - global DEVICE_NAME - - global SERVICE_STEERING_UUID - - global steering_service - global tilt_service - - global CHARACTERISTICS_STEERING_UUID - global steering_characteristics - global tilt_characteristics - - global stering_ready #connection to steering BLE service ready - global tilt_ready #connection to tilt BLE service ready - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - #print("Client connected to ", DEVICE_ID.name) - #print("Client connected to ", DEVICE_ID) - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEERING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(stering_ready) - stering_ready = 1 - - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - while (stering_ready == 1 and tilt_ready == 1): - print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print(tilt_characteristics) - print("read and write!") - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - -asyncio.run(scan_and_connect_rizer()) - +import asyncio +from bleak import BleakClient, exc +import socket +import time +from master_collector import DataReceiver + +DEVICE_NAME = "RIZER" +DEVICE_UUID = "fc:12:65:28:cb:44" + +SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + +INCREASE_TILT_HEX = "060102" +DECREASE_TILT_HEX = "060402" + +steering_service = "" +tilt_service = "" + +stering_ready = 0 +tilt_ready = 0 + +stored_tilt_value = 0 + +CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + +steering_characteristics = "" +tilt_characteristics = "" + + +class BluetoothCallback: + def __init__(self): + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC + # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi + self.udp_port = 2222 + + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + self.send_steering_data_udp(self.received_steering_data) + + #send steering data over udp + def send_steering_data_udp(self, steering_data): + # Create a UDP socket + # print(steering_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + + def listening_udp(self): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) + print("Hello: ", udp_tilt_data) + +async def read_steering(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + # print("Test steering") + except Exception as e: + print("Error: ", e) + +async def write_tilt(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + #TODO Write bt stuff + print("BT stuff") + await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) + stored_tilt_value += 0.5 + except Exception as e: + print("Error: ", e) + +# check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) +def get_new_tilt_value(): + receiver = DataReceiver() + try: + receiver.start_udp_listener() + tilt_value = receiver.get_tilt() + print("RIZER tilt: ", tilt_value) + if tilt_value != stored_tilt_value: + stored_tilt_value = tilt_value + + except Exception as e: + print("Error: ", e) + + +async def scan_and_connect_rizer(): + global DEVICE_NAME + + global SERVICE_STEERING_UUID + + global steering_service + global tilt_service + + global CHARACTERISTICS_STEERING_UUID + global steering_characteristics + global tilt_characteristics + + global stering_ready #connection to steering BLE service ready + global tilt_ready #connection to tilt BLE service ready + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + #print("Client connected to ", DEVICE_ID.name) + #print("Client connected to ", DEVICE_ID) + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEERING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + await read_steering(client, steering_characteristics) + await write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + print("read and write!") + + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + +asyncio.run(scan_and_connect_rizer()) + 'create UDP_Handler in own task' \ No newline at end of file diff --git a/collector_scripts/elite_rizer2.py b/collector_scripts/elite_rizer2.py index e108958..d3604aa 100644 --- a/collector_scripts/elite_rizer2.py +++ b/collector_scripts/elite_rizer2.py @@ -1,90 +1,90 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time -from master_collector import DataReceiver - -#Global BLE variables -steering_service = "" -tilt_service = "" - -steering_characteristics = "" -tilt_characteristics = "" - -stering_ready = 0 -tilt_ready = 0 - -stored_tilt_value = 0 - - -async def scan_and_connect_rizer(): - - #BLE constant - DEVICE_NAME = "RIZER" - DEVICE_UUID = "fc:12:65:28:cb:44" - - SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering - SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - - INCREASE_TILT_HEX = "060102" - DECREASE_TILT_HEX = "060402" - - CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(stering_ready) - stering_ready = 1 - - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - while (stering_ready == 1 and tilt_ready == 1): - print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print(tilt_characteristics) - print("read and write!") - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - +import asyncio +from bleak import BleakClient, exc +import socket +import time +from master_collector import DataReceiver + +#Global BLE variables +steering_service = "" +tilt_service = "" + +steering_characteristics = "" +tilt_characteristics = "" + +stering_ready = 0 +tilt_ready = 0 + +stored_tilt_value = 0 + + +async def scan_and_connect_rizer(): + + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" + + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering + SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + INCREASE_TILT_HEX = "060102" + DECREASE_TILT_HEX = "060402" + + CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering + CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + await read_steering(client, steering_characteristics) + await write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + print("read and write!") + + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + asyncio.run(scan_and_connect_rizer()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index d56bfb5..9ea2190 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -1,302 +1,298 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time -from master_collector import DataReceiver - -#global variables -global incline_received #received tilt data form UDP. Ready to send over BLE -global steering_received #received steering data from RIZER. Ready to send over UDP -global incline_value -global current_tilt_value_on_razer # current incline from RAZER to compute 0.5 steps fo reach desired incline -client = None -asyncio_sleep = 3 -steering_ready_to_send = 0 - - -class UDP_Handler: - - def __init__(self): - global incline_value - global incline_received - global isRunningBT - global isRunningUDP - - incline_value = 0 - incline_received = 0 - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip_to_master_collector = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - self.udp_ip_from_master_collector = "127.0.0.3" - self.udp_port = 2222 - self.receive_from_collector_port = 2223 - print("udp handler started") - - - async def main(self): - global incline_value - global steering_received - global asyncio_sleep - global isRunningUDP - global isRunningBT - - isRunningUDP = True - self.steering_data = None - steering_received = None - #print("rizer id: ", id(receiver)) - while(True): - await asyncio.sleep(asyncio_sleep) - print("udp main") - print("start listener") - self.receive_incline_data_udp() - if (steering_received == 1): #when steering value has chanched, send it to unity - print("send steering data") - await self.send_steering_data_udp(self.steering_data) - try: - #self.receiver._receiver.stop_udp_listener() - #print("fan speed (b c)", self.receiver.get_fan_speed()) - print("incline from UDP (b c): ", incline_value) - #self.check_new_incline(incline_value) #check if tilt value has changed or is still the same - - except Exception as e: - print("Error: ", e) - - print("udp loop finish") - isRunningUDP = False - isRunningBT = True - - # async def udp_handler_listen(self): - # print("open udp socket") - # self.receiver.open_udp_socket() - - - - def set_incline_received(self, received): - self.incline_received = received - - def get_incline_received(self): - return self.incline_received - - #send steering data over udp - def send_steering_data_udp(self, steering_data): - # Create a UDP socket - #print("send steering data: ", steering_data) - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) - - def receive_incline_data_udp(self): - udp_incline_data = 0 - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip_to_master_collector, self.receive_from_collector_port)) - print("Hello: ", udp_incline_data) - # def listening_udp(self): - # udp_incline_data = 0 - # with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip, self.udp_port)) - # print("Hello: ", udp_incline_data) - - # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) - def check_new_incline(self, udp_incline_value): - global incline_value - global incline_received - print("RIZER incline: ", udp_incline_value) - if incline_value != udp_incline_value: - incline_value = udp_incline_value - incline_received = 1 - -class BLE_Handler: - #BLE constant - DEVICE_NAME = "RIZER" - DEVICE_UUID = "fc:12:65:28:cb:44" - - SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering - SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - - INCREASE_INCLINE_HEX = "060102" - DECREASE_INCLINE_HEX = "060402" - - CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - global steering_characteristics - global tilt_characteristics - - global steering_service - global tilt_service - - global isRunningUDP - global isRunningBT - - global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) - - def __init__(self): - global steering_characteristics - global tilt_characteristics - - global steering_service - global tilt_service - - global current_tilt_value_on_razer - - #global steering_ready #connection to steering BLE service ready - #global tilt_ready #connection to tilt BLE service ready - - #global client_is_connected - #global client_is_connected - - # Connecting to BLE Device - print("Connecting to BLE Device") - self.client_is_connected = False - self.steering_ready = 0 - self.incline_ready = 0 - self.tilt_received = 0 - self.steering_characteristics = None - self.incline_characteristics = 0 - self.init_ack = False - - current_tilt_value_on_razer = 0 - - #main function for BLE Handler - async def read_and_ride_rizer(self): - global incline_received - global client - global isRunningBT - global isRunningUDP - print("read and write") - while(True): - await asyncio.sleep(asyncio_sleep) - print("udp handler sleep") - if (self.init_ack == True): - await self.read_steering() - print("read steering rizer") - if (incline_received == 1): - print("write incline") - await self.write_incline(self) - else: - print("wait init ack") - isRunningBT = False - isRunningUDP = True - - - async def read_steering(self): - data = bytearray(8) - sender = 0 - await asyncio.sleep(asyncio_sleep) - try: - await client.start_notify(self.steering_characteristics, self.notify_steering_callback) - # Access the notification data using data argument - #print(f"Steering data: {sender}") - #print(f"Steering data: {data}") - await asyncio.sleep(0.5) # keeps the connection open for 10 seconds - await client.stop_notify(self.steering_characteristics.uuid) - except Exception as e: - print("Error: ", e) - - async def write_incline(self): - global client - global current_tilt_value_on_razer - global incline_value - incline_different = abs(current_tilt_value_on_razer, incline_value) #absolute different of old and new incline value - print("incline_different", incline_different) - - if(current_tilt_value_on_razer + incline_value > 0): - for x in range (incline_different): - try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += 1 - self.incline_received = 0 - print("tilt writed") - except Exception as e: - print("Error: ", e) - else: - for x in range (incline_different): - try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += -1 - self.incline_received = 0 - print("tilt writed, x ", x) - except Exception as e: - print("Error: ", e) - - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print("readed steering: ", self.received_steering_data) - udp.send_steering_data_udp(self.received_steering_data) - - async def async_init(self): - global client - print("start async init") - while not self.client_is_connected: - try: - client = BleakClient(self.DEVICE_UUID, timeout=90) - print("try to connect") - await client.connect() - self.client_is_connected = True - print("Client connected to ", self.DEVICE_UUID) - for service in client.services: - if (service.uuid == self.SERVICE_STEERING_UUID): - self.steering_service = service - print("[service uuid] ", self.steering_service.uuid) - - if (self.steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in self.steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): - self.steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - - #print("steering ready", self.steering_ready) - self.steering_ready = 1 - - if (service.uuid == self.SERVICE_INCLINE_UUID): - self.incline_service = service - print("[service uuid] ", self.incline_service.uuid) - - if (self.incline_service != ""): - for characteristic in self.incline_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): - self.incline_characteristics = characteristic - self.incline_ready = 1 - - if (self.steering_ready == 1 and self.incline_ready == 1): - print("all ready!") - self.init_ack = True - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - -async def main(): - ble_async_init_task = asyncio.create_task(ble.async_init()) - await ble_async_init_task - - #ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) - print("ble main started") - udp_handler_task = asyncio.create_task(udp.main()) - print("udp main start") -# udp_listener_task = asyncio.create_task(udp.udp_handler_listen()) - #await ble_handler_task - await udp_handler_task #TODO - # print("start udp listener") - # await udp_listener_task - -# Creating instances of handlers -udp = UDP_Handler() -ble = BLE_Handler() - -# Call async_init right after the instance is created -#asyncio.run(ble.async_init()) - +import asyncio +from bleak import BleakClient, exc +import socket +import time +from master_collector import DataReceiver + +#global variables +global incline_received #received tilt data form UDP. Ready to send over BLE +global steering_received #received steering data from RIZER. Ready to send over UDP +global incline_value +global current_tilt_value_on_razer # current incline from RAZER to compute 0.5 steps fo reach desired incline +client = None +asyncio_sleep = 3 +steering_ready_to_send = 0 + + +class UDP_Handler: + + def __init__(self): + global incline_value + global incline_received + global isRunningBT + global isRunningUDP + + incline_value = 0 + incline_received = 0 + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip_to_master_collector = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + self.udp_ip_from_master_collector = "127.0.0.3" + self.udp_port = 2222 + self.receive_from_collector_port = 2223 + print("udp handler started") + + + async def main(self): + global incline_value + global steering_received + global asyncio_sleep + global isRunningUDP + global isRunningBT + + isRunningUDP = True + self.steering_data = None + steering_received = None + #print("rizer id: ", id(receiver)) + while(True): + await asyncio.sleep(asyncio_sleep) + print("udp main") + print("start listener") + self.receive_incline_data_udp() + if (steering_received == 1): #when steering value has chanched, send it to unity + print("send steering data") + await self.send_steering_data_udp(self.steering_data) + try: + #self.receiver._receiver.stop_udp_listener() + #print("fan speed (b c)", self.receiver.get_fan_speed()) + print("incline from UDP (b c): ", incline_value) + #self.check_new_incline(incline_value) #check if tilt value has changed or is still the same + + except Exception as e: + print("Error: ", e) + + print("udp loop finish") + isRunningUDP = False + isRunningBT = True + + # async def udp_handler_listen(self): + # print("open udp socket") + # self.receiver.open_udp_socket() + + + def set_incline_received(self, received): + self.incline_received = received + + def get_incline_received(self): + return self.incline_received + + #send steering data over udp + def send_steering_data_udp(self, steering_data): + # Create a UDP socket + #print("send steering data: ", steering_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) + + def receive_incline_data_udp(self): + udp_incline_data = 0 + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip_to_master_collector, self.receive_from_collector_port)) + print("Hello: ", udp_incline_data) + self.incline_value = udp_incline_data + + + # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) + def check_new_incline(self, udp_incline_value): + global incline_value + global incline_received + print("RIZER incline: ", udp_incline_value) + if incline_value != udp_incline_value: + incline_value = udp_incline_value + incline_received = 1 + +class BLE_Handler: + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" + + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering + SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + INCREASE_INCLINE_HEX = "060102" + DECREASE_INCLINE_HEX = "060402" + + CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering + CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + global steering_characteristics + global tilt_characteristics + + global steering_service + global tilt_service + + global isRunningUDP + global isRunningBT + + global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) + + def __init__(self): + global steering_characteristics + global tilt_characteristics + + global steering_service + global tilt_service + + global current_tilt_value_on_razer + + #global steering_ready #connection to steering BLE service ready + #global tilt_ready #connection to tilt BLE service ready + + #global client_is_connected + #global client_is_connected + + # Connecting to BLE Device + print("Connecting to BLE Device") + self.client_is_connected = False + self.steering_ready = 0 + self.incline_ready = 0 + self.tilt_received = 0 + self.steering_characteristics = None + self.incline_characteristics = 0 + self.init_ack = False + + current_tilt_value_on_razer = 0 + + #main function for BLE Handler + async def read_and_ride_rizer(self): + global incline_received + global client + global isRunningBT + global isRunningUDP + print("read and write") + while(True): + await asyncio.sleep(asyncio_sleep) + print("udp handler sleep") + if (self.init_ack == True): + await self.read_steering() + print("read steering rizer") + if (incline_received == 1): + print("write incline") + await self.write_incline(self) + else: + print("wait init ack") + isRunningBT = False + isRunningUDP = True + + + async def read_steering(self): + data = bytearray(8) + sender = 0 + await asyncio.sleep(asyncio_sleep) + try: + await client.start_notify(self.steering_characteristics, self.notify_steering_callback) + # Access the notification data using data argument + #print(f"Steering data: {sender}") + #print(f"Steering data: {data}") + await asyncio.sleep(0.5) # keeps the connection open for 10 seconds + await client.stop_notify(self.steering_characteristics.uuid) + except Exception as e: + print("Error: ", e) + + async def write_incline(self): + global client + global current_tilt_value_on_razer + global incline_value + incline_different = abs(current_tilt_value_on_razer, incline_value) #absolute different of old and new incline value + print("incline_different", incline_different) + + if(current_tilt_value_on_razer + incline_value > 0): + for x in range (incline_different): + try: + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + current_tilt_value_on_razer += 1 + self.incline_received = 0 + print("tilt writed") + except Exception as e: + print("Error: ", e) + else: + for x in range (incline_different): + try: + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + current_tilt_value_on_razer += -1 + self.incline_received = 0 + print("tilt writed, x ", x) + except Exception as e: + print("Error: ", e) + + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print("readed steering: ", self.received_steering_data) + udp.send_steering_data_udp(self.received_steering_data) + + async def async_init(self): + global client + print("start async init") + while not self.client_is_connected: + try: + client = BleakClient(self.DEVICE_UUID, timeout=90) + print("try to connect") + await client.connect() + self.client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + self.steering_service = service + print("[service uuid] ", self.steering_service.uuid) + + if (self.steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in self.steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + + #print("steering ready", self.steering_ready) + self.steering_ready = 1 + + if (service.uuid == self.SERVICE_INCLINE_UUID): + self.incline_service = service + print("[service uuid] ", self.incline_service.uuid) + + if (self.incline_service != ""): + for characteristic in self.incline_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): + self.incline_characteristics = characteristic + self.incline_ready = 1 + + if (self.steering_ready == 1 and self.incline_ready == 1): + print("all ready!") + self.init_ack = True + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + +async def main(): + ble_async_init_task = asyncio.create_task(ble.async_init()) + await ble_async_init_task + + #ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) + print("ble main started") + udp_handler_task = asyncio.create_task(udp.main()) + print("udp main start") +# udp_listener_task = asyncio.create_task(udp.udp_handler_listen()) + #await ble_handler_task + await udp_handler_task #TODO + # print("start udp listener") + # await udp_listener_task + +# Creating instances of handlers +udp = UDP_Handler() +ble = BLE_Handler() + +# Call async_init right after the instance is created +#asyncio.run(ble.async_init()) + asyncio.run(main()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer_old.py b/collector_scripts/elite_rizer_old.py index 5e5b58b..8b2727c 100644 --- a/collector_scripts/elite_rizer_old.py +++ b/collector_scripts/elite_rizer_old.py @@ -1,152 +1,152 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time - -DEVICE_NAME = "RIZER" -DEVICE_UUID = "fc:12:65:28:cb:44" - -SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - -INCREASE_TILT_HEX = "060102" -DECREASE_TILT_HEX = "060402" - -steering_service = "" -tilt_service = "" - -stering_ready = 0 -tilt_ready = 0 - - -CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - -steering_characteristics = "" -tilt_characteristics = "" - - -class BluetoothCallback: - def __init__(self): - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC - # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi - self.udp_port = 2222 - - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print(self.received_steering_data) - - self.send_steering_data_udp(self.received_steering_data) - - - def send_steering_data_udp(self, steering_data): - # Create a UDP socket - # print(steering_data) - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) - -async def read_steering(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - # print("Test steering") - except Exception as e: - print("Error: ", e) - -async def write_tilt(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - #TODO Write bt stuff - print("BT stuff") - await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) - except Exception as e: - print("Error: ", e) - - -async def scan_and_connect_rizer(): - global DEVICE_NAME - - global SERVICE_STEERING_UUID - - global steering_service - global tilt_service - - global CHARACTERISTICS_STEEING_UUID - global steering_characteristics - global tilt_characteristics - - global stering_ready - global tilt_ready - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - #print("Client connected to ", DEVICE_ID.name) - #print("Client connected to ", DEVICE_ID) - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(stering_ready) - stering_ready = 1 - - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - while (stering_ready == 1 and tilt_ready == 1): - print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print(tilt_characteristics) - print("read and write!") - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - +import asyncio +from bleak import BleakClient, exc +import socket +import time + +DEVICE_NAME = "RIZER" +DEVICE_UUID = "fc:12:65:28:cb:44" + +SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering +SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + +INCREASE_TILT_HEX = "060102" +DECREASE_TILT_HEX = "060402" + +steering_service = "" +tilt_service = "" + +stering_ready = 0 +tilt_ready = 0 + + +CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering +CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + +steering_characteristics = "" +tilt_characteristics = "" + + +class BluetoothCallback: + def __init__(self): + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC + # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi + self.udp_port = 2222 + + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + self.send_steering_data_udp(self.received_steering_data) + + + def send_steering_data_udp(self, steering_data): + # Create a UDP socket + # print(steering_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send speed_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + +async def read_steering(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) + await asyncio.sleep(10) # keeps the connection open for 10 seconds + await client.stop_notify(characteristic.uuid) + # print("Test steering") + except Exception as e: + print("Error: ", e) + +async def write_tilt(client, characteristic): + bluetooth_callback = BluetoothCallback() + try: + #TODO Write bt stuff + print("BT stuff") + await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) + except Exception as e: + print("Error: ", e) + + +async def scan_and_connect_rizer(): + global DEVICE_NAME + + global SERVICE_STEERING_UUID + + global steering_service + global tilt_service + + global CHARACTERISTICS_STEEING_UUID + global steering_characteristics + global tilt_characteristics + + global stering_ready + global tilt_ready + + # Connecting to BLE Device + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICE_UUID, timeout=90) as client: + client_is_connected = True + #print("Client connected to ", DEVICE_ID.name) + #print("Client connected to ", DEVICE_ID) + print("Client connected to ", DEVICE_UUID) + # return True + # logger.info("Device ID ", device_id) + for service in client.services: + # print("service: ", service) + + if (service.uuid == SERVICE_STEERING_UUID): + steering_service = service + print("[service uuid] ", steering_service.uuid) + + if (steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): + steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + print("IF") + + print(stering_ready) + stering_ready = 1 + + if (service.uuid == SERVICE_TILT_UUID): + tilt_service = service + print("[service uuid] ", tilt_service.uuid) + + if (tilt_service != ""): + for characteristic in tilt_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): + tilt_characteristics = characteristic + print(tilt_ready) + tilt_ready = 1 + + while (stering_ready == 1 and tilt_ready == 1): + print("all ready!") + await read_steering(client, steering_characteristics) + await write_tilt(client, tilt_characteristics) + print(tilt_characteristics) + print("read and write!") + + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + asyncio.run(scan_and_connect_rizer()) \ No newline at end of file diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 2648004..6b5d1d0 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -1,154 +1,154 @@ -import asyncio -from bleak import BleakScanner, BleakClient, exc -from master_collector import DataReceiver - -class BluetoothCallback(): - def __init__(self): - self.received_data = 0 # Initialize with None or any default value - - async def notify_callback(self, sender, data): - # Assuming data is received from the Bluetooth device - # print(data) - test = "123" - - -device_name = "HEADWIND BC55" # Replace with the name of your desired BLE device -DEVICE = "" - -service_uuid = "a026ee0c-0a7d-4ab3-97fa-f1500f9feb8b" -SERVICE = "" - -characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" -CHARACTERISTIC = "" - -async def scan_and_connect_headwind(): - global device_name - - global service_uuid - global SERVICE - - global characteristic_uuid - global CHARACTERISTIC - - global value_to_write - global old_value - - global is_first_entry - global run_read_loop - - stop_event = asyncio.Event() - - # Scanning and printing for BLE devices - def callback(device, advertising_data): - global DEVICEID - print(device) - if(device.name == device_name): - DEVICEID = device - stop_event.set() - - # Stops the scanning event - async with BleakScanner(callback) as scanner: - # new - try: - await stop_event.wait() - except KeyboardInterrupt: - print("Scanning stopped by user.") - scanner.stop() - # new end - # old############ - await stop_event.wait() - # old############ - - if(DEVICEID != ""): - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICEID, timeout=90) as client: - client_is_connected = True - print("Client connected to ", DEVICEID.name) - # print("Device ID ", DEVICEID) - for service in client.services: - - if (service.uuid == service_uuid): - SERVICE = service - - if (SERVICE != ""): - for characteristic in SERVICE.characteristics: - - if(characteristic.uuid == characteristic_uuid): - CHARACTERISTIC = characteristic - - #receiver = DataReceiver() - #print("rizer id: ", id(receiver)) - - bluetooth_callback = BluetoothCallback() - #receiver.open_udp_socket() - while True: - - try: - print("headwind: try") - #receiver.start_udp_listener() - # print("FAN SPEED: ", receiver.get_fan_speed()) - speed_value = DataReceiver.ble_fan_speed - print("incline: ", DataReceiver.get_ble_incline()) - print("Fan Speed: ", DataReceiver.get_ble_fan_speed()) - print("Fan Speed: ", speed_value) - print("get ble incline headwind", DataReceiver.get_ble_incline()) - except Exception as e: - print("Error: ", e) - try: - await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid - except Exception as e: - print("Error: ", e) - - - ''' - try: - if speed_value > 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - elif speed_value <= 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) # Turn fan off - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Error turning fan on or off.") - - try: - if speed_value > 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - - ''' - try: - if 2 <= speed_value <= 100: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - elif speed_value == 1: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - elif speed_value == 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") - # Add additional error handling or logging as needed - # raise - -try: - asyncio.run(scan_and_connect_headwind()) -except BaseException: - import sys - print(sys.exc_info()[0]) - import traceback - print(traceback.format_exc()) -finally: - print("Press Enter to continue ...") - input() - +import asyncio +from bleak import BleakScanner, BleakClient, exc +from master_collector import DataReceiver + +class BluetoothCallback(): + def __init__(self): + self.received_data = 0 # Initialize with None or any default value + + async def notify_callback(self, sender, data): + # Assuming data is received from the Bluetooth device + # print(data) + test = "123" + + +device_name = "HEADWIND BC55" # Replace with the name of your desired BLE device +DEVICE = "" + +service_uuid = "a026ee0c-0a7d-4ab3-97fa-f1500f9feb8b" +SERVICE = "" + +characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" +CHARACTERISTIC = "" + +async def scan_and_connect_headwind(): + global device_name + + global service_uuid + global SERVICE + + global characteristic_uuid + global CHARACTERISTIC + + global value_to_write + global old_value + + global is_first_entry + global run_read_loop + + stop_event = asyncio.Event() + + # Scanning and printing for BLE devices + def callback(device, advertising_data): + global DEVICEID + print(device) + if(device.name == device_name): + DEVICEID = device + stop_event.set() + + # Stops the scanning event + async with BleakScanner(callback) as scanner: + # new + try: + await stop_event.wait() + except KeyboardInterrupt: + print("Scanning stopped by user.") + scanner.stop() + # new end + # old############ + await stop_event.wait() + # old############ + + if(DEVICEID != ""): + client_is_connected = False + while(client_is_connected == False): + try: + async with BleakClient(DEVICEID, timeout=90) as client: + client_is_connected = True + print("Client connected to ", DEVICEID.name) + # print("Device ID ", DEVICEID) + for service in client.services: + + if (service.uuid == service_uuid): + SERVICE = service + + if (SERVICE != ""): + for characteristic in SERVICE.characteristics: + + if(characteristic.uuid == characteristic_uuid): + CHARACTERISTIC = characteristic + + #receiver = DataReceiver() + #print("rizer id: ", id(receiver)) + + bluetooth_callback = BluetoothCallback() + #receiver.open_udp_socket() + while True: + + try: + print("headwind: try") + #receiver.start_udp_listener() + # print("FAN SPEED: ", receiver.get_fan_speed()) + speed_value = DataReceiver.ble_fan_speed + print("incline: ", DataReceiver.get_ble_incline()) + print("Fan Speed: ", DataReceiver.get_ble_fan_speed()) + print("Fan Speed: ", speed_value) + print("get ble incline headwind", DataReceiver.get_ble_incline()) + except Exception as e: + print("Error: ", e) + try: + await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid + except Exception as e: + print("Error: ", e) + + + ''' + try: + if speed_value > 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + elif speed_value <= 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) # Turn fan off + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Error turning fan on or off.") + + try: + if speed_value > 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + + ''' + try: + if 2 <= speed_value <= 100: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + elif speed_value == 1: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + elif speed_value == 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") + # Add additional error handling or logging as needed + # raise + +try: + asyncio.run(scan_and_connect_headwind()) +except BaseException: + import sys + print(sys.exc_info()[0]) + import traceback + print(traceback.format_exc()) +finally: + print("Press Enter to continue ...") + input() + diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 47c44a9..e907457 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -1,238 +1,252 @@ -import asyncio -import socket -import select -import json -import time - -class DataSender: - def __init__(self): - self.speed_value = 0 - self.steering_value = 0 - self.brake_value = 0 - self.bno_value = 0 - self.roll_value = 0 - self.udp_unity_send_ip = "127.0.0.2" # IP of the computer running Unity (just the localhost ip if the script is running on the same computer than the simulation) - # self.udp_unity_send_ip = "10.30.77.221" # IP of the computer running Unity - self.udp_unity_send_port = 1337 - - def collect_speed(self, speed): - self.speed_value = speed - - def collect_steering(self, steering): - self.steering_value = steering - - def collect_brake(self, brake): - self.brake_value = brake - - def collect_bno(self, bno): - self.bno_value = bno - - def collect_roll(self, roll): - self.roll_value = roll - - def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, roll_data): - - - # Create a dictionary with the required parameters - data = { - "diretoSpeed": float(speed_data), - "rizerSteering": float(steering_data), - "espBno": float(bno_data), - "espBrake": float(brake_data), - "espRoll": float(roll_data) - } - print(data) - # Convert dictionary to JSON string - json_data = json.dumps(data) - - # Create a UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send JSON data - udp_socket.sendto(json_data.encode(), (self.udp_unity_send_ip, self.udp_unity_send_port)) - - -# This class might be to be located in the headwind script -class DataReceiver: - def __init__(self): - self.ble_fan_speed = 0 - self.ble_incline = 40 - self.ble_resistance = 0 - self.send_to_actuator_ip = "127.0.0.3" - self.send_to_rizer_port = 2223 - self.send_to_headwind_port = 2224 - self.udp_unity_receive_socket = None - self.main_loop() - - # self.udp_unity_receive_ip = "127.0.0.1" - # self.udp_unity_receive_port = 12345 - - def set_ble_fan_speed(self, fan_speed): - self.ble_fan_speed = fan_speed - - def set_ble_incline(self, incline_data): - self.ble_incline = incline_data - - def get_fan_speed(self): - #global ble_fan_speed - print("Self ble fan speed: ", self.ble_fan_speed) - return self.ble_fan_speed - - def get_incline(self): - print("Self ble incline: ", self.ble_incline) - return self.ble_incline - - def get_resistance(self): - #global ble_resistance - print("Self ble incline: ", self.ble_resistance) - return self.ble_resistance - - def main_loop(self): - print("start main loop master collector") - for x in range -20, 40: - self.send_udp_data_to_rizer(x) - time.sleep(2) - - - - def open_udp_socket(self): - # Create a UDP socket - udp_unity_receive_ip = "127.0.0.1" - udp_unity_receive_port = 12345 - - self.udp_unity_receive_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.udp_unity_receive_socket.bind((udp_unity_receive_ip, udp_unity_receive_port)) - - print("Listening for UDP data...") - - def start_udp_listener(self): - - # Infinite loop to continuously receive data - try: - data_receiver = DataReceiver() - data, addr = self.udp_unity_receive_socket.recvfrom(1024) # Buffer size is 1024 bytes - - #self.udp_unity_receive_socket.setblocking(False) - - json_data = data.decode('utf-8') # Decode bytes to string - # value = json.loads(data.decode()) - unity_values = json.loads(json_data) - self.ble_fan_value = unity_values["bleFan"] - self.ble_incline_value = unity_values["bleIncline"] - # print("ble fan from unity: ", ble_fan_value) - print("incline from unity: ", self.ble_incline_value) - data_receiver.set_ble_fan_speed(self.ble_fan_value) - data_receiver.set_ble_incline(self.ble_incline_value) - print("incline mastercollector", data_receiver.get_incline()) - #self.ble_incline = self.ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly - except Exception as e: - print(f"Error while receiving UDP data: {e}") - - def send_udp_data_to_rizer(self, incline_data): - # Create a dictionary with the required parameters - data = { - "rizerIncline": float(incline_data), - } - print(data) - # Convert dictionary to JSON string - json_data = json.dumps(data) - - # Create a UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send JSON data - udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_rizer_port)) - - - def send_udp_data_to_headwind(self, fan_speed): - - - # Create a dictionary with the required parameters - data = { - "fanSpeed": float(fan_speed), - } - print(data) - # Convert dictionary to JSON string - json_data = json.dumps(data) - - # Create a UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send JSON data - udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_headwind_port)) - - - def stop_udp_listener(self): - if self.udp_unity_receive_socket: - self.udp_unity_receive_socket.close() - print("UDP listener stopped.") - - - -if __name__ == "__main__": - data_sender = DataSender() - - print("Master Collector script started...") - # IP adresses to receive data from actuators and sensors - UDP_IP = "127.0.0.1" # IP to receive data from elite_rizer.py as well as from direto_xr.py scripts via UDP - # UDP_IP_UNITY_RECEIVE = "127.0.0.1" # Receives Data from unity such as the ble fan data - UDP_ESP_IP = "192.168.0.101" # IP of the computer running this script to receive data from ESP32 -> Bicycle Simulator Desktop PC - # UDP_ESP_IP = "192.168.9.184" # Raspberry Pi 3 - # UDP_ESP_IP = "192.168.9.198" # Raspberry Pi 5 - - # ports to receive data from actuators and sensors - UDP_PORT_DIRETO = 1111 - UDP_PORT_RIZER = 2222 - UDP_PORT_ROLL = 6666 - UDP_PORT_BRAKE = 7777 - UDP_PORT_BNO = 8888 - # UDP_PORT_UNITY_RECEIVE = 12345 # Port to receive data from unity such as the ble fan data - - - udp_rizer_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp_rizer_socket.bind((UDP_IP, UDP_PORT_RIZER)) - - udp_direto_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp_direto_socket.bind((UDP_IP, UDP_PORT_DIRETO)) - - udp_brake_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp_brake_socket.bind((UDP_ESP_IP, UDP_PORT_BRAKE)) - - udp_bno_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp_bno_socket.bind((UDP_ESP_IP, UDP_PORT_BNO)) - - udp_roll_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - udp_roll_socket.bind((UDP_ESP_IP, UDP_PORT_ROLL)) - - # udp_unity_receive_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - # udp_unity_receive_socket.bind((UDP_IP_UNITY_RECEIVE, UDP_PORT_UNITY_RECEIVE)) - - - - while True: - # print("udp_direto_socket: ", udp_direto_socket) - data_sender.send_unity_data_udp(data_sender.speed_value, data_sender.steering_value, data_sender.brake_value, data_sender.bno_value, data_sender.roll_value) - readable, _, _ = select.select([udp_rizer_socket, udp_direto_socket, udp_brake_socket, udp_bno_socket, udp_roll_socket], [], []) - - for sock in readable: - data, addr = sock.recvfrom(1024) - if sock is udp_rizer_socket: - steering_value = data.decode() - # print("steering: ", steering_value) - data_sender.collect_steering(steering_value) - elif sock is udp_direto_socket: - speed_value = data.decode() - # print("SPEED: ", speed_value) - data_sender.collect_speed(speed_value) - elif sock is udp_brake_socket: - brake_value = json.loads(data.decode()) - brake_value = brake_value["sensor_value"] - # print("Brake_value: ", brake_value) - data_sender.collect_brake(brake_value) - elif sock is udp_bno_socket: - bno_value = json.loads(data.decode()) - # print("BNO_Value: ", bno_value) - bno_value = bno_value["euler_r"] - data_sender.collect_bno(bno_value) - elif sock is udp_roll_socket: - roll_value = json.loads(data.decode()) - # print("Roll_Value: ", roll_value) - roll_value = roll_value["sensor_value"] +import asyncio +import socket +import select +import json +import time + +class DataSender: + def __init__(self): + self.speed_value = 0 + self.steering_value = 0 + self.brake_value = 0 + self.bno_value = 0 + self.roll_value = 0 + self.udp_unity_send_ip = "127.0.0.2" # IP of the computer running Unity (just the localhost ip if the script is running on the same computer than the simulation) + # self.udp_unity_send_ip = "10.30.77.221" # IP of the computer running Unity + self.udp_unity_send_port = 1337 + + def collect_speed(self, speed): + self.speed_value = speed + + def collect_steering(self, steering): + self.steering_value = steering + + def collect_brake(self, brake): + self.brake_value = brake + + def collect_bno(self, bno): + self.bno_value = bno + + def collect_roll(self, roll): + self.roll_value = roll + + def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, roll_data): + + + # Create a dictionary with the required parameters + data = { + "diretoSpeed": float(speed_data), + "rizerSteering": float(steering_data), + "espBno": float(bno_data), + "espBrake": float(brake_data), + "espRoll": float(roll_data) + } + print(data) + # Convert dictionary to JSON string + json_data = json.dumps(data) + + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send JSON data + udp_socket.sendto(json_data.encode(), (self.udp_unity_send_ip, self.udp_unity_send_port)) + + +# This class might be to be located in the headwind script +class DataReceiver: + def __init__(self): + self.ble_fan_speed = 0 + self.ble_incline = 40 + self.ble_resistance = 0 + self.ble_resistance = 0 + self.send_to_actuator_ip = "127.0.0.3" + self.send_to_rizer_port = 2223 + self.send_to_headwind_port = 2224 + self.send_to_direto_port = 2225 + self.udp_unity_receive_socket = None + self.main_loop() + + # self.udp_unity_receive_ip = "127.0.0.1" + # self.udp_unity_receive_port = 12345 + + def set_ble_fan_speed(self, fan_speed): + self.ble_fan_speed = fan_speed + + def set_ble_incline(self, incline_data): + self.ble_incline = incline_data + + def get_fan_speed(self): + #global ble_fan_speed + print("Self ble fan speed: ", self.ble_fan_speed) + return self.ble_fan_speed + + def get_incline(self): + print("Self ble incline: ", self.ble_incline) + return self.ble_incline + + def get_resistance(self): + #global ble_resistance + print("Self ble incline: ", self.ble_resistance) + return self.ble_resistance + + def main_loop(self): + print("start main loop master collector") + for x in range -20, 40: + self.send_udp_data_to_rizer(x) + time.sleep(2) + + + # UDP socket to receive data from Unity + def open_udp_socket(self): + # Create a UDP socket + udp_unity_receive_ip = "127.0.0.1" + udp_unity_receive_port = 12345 + + self.udp_unity_receive_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.udp_unity_receive_socket.bind((udp_unity_receive_ip, udp_unity_receive_port)) + + print("Listening for UDP data...") + + def start_udp_listener(self): + + # Infinite loop to continuously receive data + try: + data_receiver = DataReceiver() + data, addr = self.udp_unity_receive_socket.recvfrom(1024) # Buffer size is 1024 bytes + + #self.udp_unity_receive_socket.setblocking(False) + + json_data = data.decode('utf-8') # Decode bytes to string + # value = json.loads(data.decode()) + unity_values = json.loads(json_data) + self.ble_fan_value = unity_values["bleFan"] + self.ble_incline_value = unity_values["bleIncline"] + # print("ble fan from unity: ", ble_fan_value) + print("incline from unity: ", self.ble_incline_value) + data_receiver.set_ble_fan_speed(self.ble_fan_value) + data_receiver.set_ble_incline(self.ble_incline_value) + print("incline mastercollector", data_receiver.get_incline()) + #self.ble_incline = self.ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly + except Exception as e: + print(f"Error while receiving UDP data: {e}") + + def send_udp_data_to_rizer(self, incline_data): + # Create a dictionary with the required parameters + data = { + "rizerIncline": float(incline_data), + } + print(data) + # Convert dictionary to JSON string + json_data = json.dumps(data) + + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send JSON data + udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_rizer_port)) + + + def send_udp_data_to_headwind(self, fan_speed): + # Create a dictionary with the required parameters + data = { + "fanSpeed": float(fan_speed), + } + print(data) + # Convert dictionary to JSON string + json_data = json.dumps(data) + + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send JSON data + udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_headwind_port)) + + def send_udp_data_to_direto(self, resistance_data): + # Create a dictionary with the required parameters + data = { + "fanSpeed": float(resistance_data), + } + print(data) + # Convert dictionary to JSON string + json_data = json.dumps(data) + + # Create a UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send JSON data + udp_socket.sendto(json_data.encode(), (self.send_to_actuator_ip, self.send_to_direto_port)) + + + def stop_udp_listener(self): + if self.udp_unity_receive_socket: + self.udp_unity_receive_socket.close() + print("UDP listener stopped.") + + + +if __name__ == "__main__": + data_sender = DataSender() + + print("Master Collector script started...") + # IP adresses to receive data from actuators and sensors + UDP_IP = "127.0.0.1" # IP to receive data from elite_rizer.py as well as from direto_xr.py scripts via UDP + # UDP_IP_UNITY_RECEIVE = "127.0.0.1" # Receives Data from unity such as the ble fan data + UDP_ESP_IP = "192.168.0.101" # IP of the computer running this script to receive data from ESP32 -> Bicycle Simulator Desktop PC + # UDP_ESP_IP = "192.168.9.184" # Raspberry Pi 3 + # UDP_ESP_IP = "192.168.9.198" # Raspberry Pi 5 + + # ports to receive data from actuators and sensors + UDP_PORT_DIRETO = 1111 + UDP_PORT_RIZER = 2222 + UDP_PORT_ROLL = 6666 + UDP_PORT_BRAKE = 7777 + UDP_PORT_BNO = 8888 + # UDP_PORT_UNITY_RECEIVE = 12345 # Port to receive data from unity such as the ble fan data + + + udp_rizer_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_rizer_socket.bind((UDP_IP, UDP_PORT_RIZER)) + + udp_direto_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_direto_socket.bind((UDP_IP, UDP_PORT_DIRETO)) + + udp_brake_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_brake_socket.bind((UDP_ESP_IP, UDP_PORT_BRAKE)) + + udp_bno_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_bno_socket.bind((UDP_ESP_IP, UDP_PORT_BNO)) + + udp_roll_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_roll_socket.bind((UDP_ESP_IP, UDP_PORT_ROLL)) + + # udp_unity_receive_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + # udp_unity_receive_socket.bind((UDP_IP_UNITY_RECEIVE, UDP_PORT_UNITY_RECEIVE)) + + + + while True: + # print("udp_direto_socket: ", udp_direto_socket) + data_sender.send_unity_data_udp(data_sender.speed_value, data_sender.steering_value, data_sender.brake_value, data_sender.bno_value, data_sender.roll_value) + readable, _, _ = select.select([udp_rizer_socket, udp_direto_socket, udp_brake_socket, udp_bno_socket, udp_roll_socket], [], []) + + for sock in readable: + data, addr = sock.recvfrom(1024) + if sock is udp_rizer_socket: + steering_value = data.decode() + # print("steering: ", steering_value) + data_sender.collect_steering(steering_value) + elif sock is udp_direto_socket: + speed_value = data.decode() + # print("SPEED: ", speed_value) + data_sender.collect_speed(speed_value) + elif sock is udp_brake_socket: + brake_value = json.loads(data.decode()) + brake_value = brake_value["sensor_value"] + # print("Brake_value: ", brake_value) + data_sender.collect_brake(brake_value) + elif sock is udp_bno_socket: + bno_value = json.loads(data.decode()) + # print("BNO_Value: ", bno_value) + bno_value = bno_value["euler_r"] + data_sender.collect_bno(bno_value) + elif sock is udp_roll_socket: + roll_value = json.loads(data.decode()) + # print("Roll_Value: ", roll_value) + roll_value = roll_value["sensor_value"] data_sender.collect_roll(roll_value) \ No newline at end of file diff --git a/collector_scripts/p110_connect.py b/collector_scripts/p110_connect.py index d4016e9..e3af1f5 100644 --- a/collector_scripts/p110_connect.py +++ b/collector_scripts/p110_connect.py @@ -1,35 +1,35 @@ -from PyP100 import PyP100 -import time - - -def connect_and_start_p100(): - p100 = PyP100.P100("192.168.0.110", "unitylab.hhn3@gmail.com", "Unitylab") #Creates a P100 plug object - # p100.handshake() #Creates the cookies required for further methods - # p100.login() - print("Restarting P100 Power Outlet") - try: - if(p100.get_status() == False): - print("P100 already off") - - - if(p100.get_status() == True): - try: - time.sleep(1) - print("Waiting for devices to turn off...") - p100.turnOff() - time.sleep(5) - # print("Turning on the devices...") - # p100.turnOn() - except Exception: - pass - time.sleep(5) - print("Turn P100 on...") - p100.turnOn() - time.sleep(10) - except Exception: - pass - print("done") - return True - -connect_and_start_p100() # uncomment this if the bicycle simulator getting started by a batch or a shell script and comment it if the bicycle simulator is getting started by run.py python script - +from PyP100 import PyP100 +import time + + +def connect_and_start_p100(): + p100 = PyP100.P100("192.168.0.110", "unitylab.hhn3@gmail.com", "Unitylab") #Creates a P100 plug object + # p100.handshake() #Creates the cookies required for further methods + # p100.login() + print("Restarting P100 Power Outlet") + try: + if(p100.get_status() == False): + print("P100 already off") + + + if(p100.get_status() == True): + try: + time.sleep(1) + print("Waiting for devices to turn off...") + p100.turnOff() + time.sleep(5) + # print("Turning on the devices...") + # p100.turnOn() + except Exception: + pass + time.sleep(5) + print("Turn P100 on...") + p100.turnOn() + time.sleep(10) + except Exception: + pass + print("done") + return True + +connect_and_start_p100() # uncomment this if the bicycle simulator getting started by a batch or a shell script and comment it if the bicycle simulator is getting started by run.py python script + diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 74ec50c..5b37699 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -1,33 +1,33 @@ -@echo off - -REM Set the Python script to be executed 5 seconds before others -set pre_execution_script="p110_connect.py" - -ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 - -REM Set the list of Python scripts to start -set python_scripts=("elite_rizer3.py", "master_collector.py") -REM "direto_xr.py", "headwind.py", - -REM Start the pre-execution script -start "" python %pre_execution_script% - -REM Wait for the pre-execution script to complete -:wait_for_pre_execution -echo Pre execution script started -ping 127.0.0.1 -n 6 > nul -tasklist | find "python.exe" | findstr "%pre_execution_script%" > nul -if errorlevel 1 goto pre_execution_completed -goto wait_for_pre_execution - -:pre_execution_completed -REM Wait for an additional 5 seconds -timeout 5 >NUL - -REM Loop through the other scripts and start them -for %%i in %python_scripts% do ( - start "" python %%i - echo Started %%i -) - +@echo off + +REM Set the Python script to be executed 5 seconds before others +set pre_execution_script="p110_connect.py" + +ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 + +REM Set the list of Python scripts to start +set python_scripts=("elite_rizer3.py", "master_collector.py") +REM "direto_xr.py", "headwind.py", + +REM Start the pre-execution script +start "" python %pre_execution_script% + +REM Wait for the pre-execution script to complete +:wait_for_pre_execution +echo Pre execution script started +ping 127.0.0.1 -n 6 > nul +tasklist | find "python.exe" | findstr "%pre_execution_script%" > nul +if errorlevel 1 goto pre_execution_completed +goto wait_for_pre_execution + +:pre_execution_completed +REM Wait for an additional 5 seconds +timeout 5 >NUL + +REM Loop through the other scripts and start them +for %%i in %python_scripts% do ( + start "" python %%i + echo Started %%i +) + echo All scripts started successfully. \ No newline at end of file diff --git a/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino b/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino index cc731f6..22e1a78 100644 --- a/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino +++ b/collector_scripts/sensor_scripts/Arduino_Files/ESP32-AS5600-Roll/ESP32-AS5600-Roll.ino @@ -1,302 +1,302 @@ -#include -#include -#include -#include -#include - -const int HALL_PIN = 32; - -//const char* ssid = "raspi-webgui"; -//const char* password = "bikingismylife"; -const char *ssid = "Bicycle_Simulator_Network"; -const char *password = "17701266"; -unsigned int localUdpPort = 6666; -const unsigned long udpSendInterval = 500; // Interval for sending UDP packets in milliseconds -unsigned long lastUdpSendTime = 0; // Variable to store the last time UDP packet was sent - -WiFiUDP udp; - -using namespace std; // Use the std namespace for ArduinoSTL - -// General control settings and flags -const int debugging = 0; -const int printResult = 1; -const int directionalMode = 1; - -// Control timings in ms -const int loopTime = 60; // time between value calculation and printing in average angle reading -const int iterationCount = 20; // how many angle readings to average in one loop -const int iterationPadding = 0; // time between individual value readings - -// Control settings for turn direction reading -const float turnSensitivityActivation = 6; // default: 22 - for turn direction -const float turnSensitivityDeactivation = 31; - -// AS5600 device specifics -const int deviceAddress = 0x36; -const int registerAddressHigh = 0x0E; // Register for high 4 bits -const int registerAddressLow = 0x0F; // Register for low 8 bits -const int maxSensorValue = 4095; -const int maxDegrees = 360; -const int SDA_PIN = 18; -const int SCL_PIN = 19; - -//reused variables -unsigned long loopStartTime; -unsigned long loopEndTime; -unsigned long executionTime; -float lastReadAngle = 0; -int directionBufferIndex = 0; -int directionBufferSize = 0; -uint64_t directionBuffer = 0; -int turnDirection = 0; -int lastTurnDirection = 0; -int nextBit = 0; -int angleTolerance = 0; - - -int readValues = 0; - -void AddToBuffer(int val) { - directionBuffer = (directionBuffer << 1) | (val & 1); -} - -int countOnes(uint64_t n) -{ - unsigned int c; // the total bits set in n - for (c = 0; n; n = n & (n-1)) - { - c++; - } - return c; -} - -void printBufferBits() { - int i; - // The number of bits in a uint64_t is 64 - for (i = 63; i >= 0; i--) { - // Check the i-th bit using bitwise AND - uint64_t bit = (directionBuffer >> i) & 1; - Serial.print((int)bit); - } - Serial.println(); // Print a new line after all bits -} - -void setup() { - Wire.begin(SDA_PIN, SCL_PIN); - Serial.begin(115200); - - WiFi.hostname("Roll_ESP"); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) - { - delay(1000); - Serial.print("Connecting with WiFi with ssid: "); - Serial.print(ssid); - Serial.print(" and password: "); - Serial.println(password); - Serial.println(WiFi.status()); - } - - Serial.println("Connection with WiFi successful"); - udp.begin(localUdpPort); - - loopStartTime = millis(); -} - -void loop() { - if (directionalMode) { - getRotation(); - } else { - averageAngle(); - } - - // Check if it's time to send the UDP packet - unsigned long currentTime = millis(); - if (currentTime - lastUdpSendTime >= udpSendInterval) { - StaticJsonDocument<500> doc; - - String jsonStr; - doc["sensor"] = "AS5600"; - doc["sensor_value"] = turnDirection; - - serializeJson(doc, jsonStr); - - // Read UDP messages - int packetSize = udp.parsePacket(); - if (packetSize) { - char packetBuffer[255]; - udp.read(packetBuffer, packetSize); - - Serial.print("Received message: "); - Serial.println(packetBuffer); - } - - udp.beginPacket("192.168.0.101", 6666); - udp.print(jsonStr); - udp.endPacket(); - - // Update the last send time - lastUdpSendTime = currentTime; - } -} - -void averageAngle() { - loopStartTime = millis(); - float angleReadings[iterationCount]; - - for (int it = 0; it < iterationCount; ++it) { - float angle = readAngle(); - - // Check if readAngle encountered an error - if (isnan(angle)) { - Serial.print(" !Reading Error! "); - continue; // Skip the rest of the loop if there's an error - } - angleReadings[it] = angle; - if(iterationPadding > 0)delay(iterationPadding); - } - - int readValuesSize = sizeof(angleReadings) / sizeof(angleReadings[0]); - // Calculate the mean angle using the array of readings - float averagedAngle = meanAngle(angleReadings, readValuesSize); - - if(debugging){ - Serial.println(""); - Serial.print("Raw values: "); - Serial.println(readValuesSize); - printArray(angleReadings, readValuesSize); - } - if(printResult) { - Serial.print("Angle: "); - Serial.println(averagedAngle, 3); // Print with 3 decimal places - } - loopEndTime = millis(); - executionTime = loopEndTime - loopStartTime; - - if(debugging) { - Serial.print("execution time: "); - Serial.println(executionTime); - } - if(executionTime <= loopTime) { - delay((float)(loopTime - executionTime)); - } else { - if(debugging) { - Serial.println("Cant keep up! Execution time of " + (String)executionTime + "ms is " + (String) (executionTime - loopTime) + "ms over the budget!"); - } else { - Serial.println(" Device Slowdown!"); - } - } -} - -float readAngle() { - // Request the high byte from the specified register of the device - Wire.beginTransmission(deviceAddress); - Wire.write(registerAddressHigh); - int transmissionStatusHigh = Wire.endTransmission(); - - if (transmissionStatusHigh != 0) { - Serial.print("Error in I2C transmission (high byte). Status: "); - Serial.println(transmissionStatusHigh); - return NAN; // Skip the rest of the loop if there's an error - } - - Wire.requestFrom(deviceAddress, 1); - - while (Wire.available() < 1); - - byte highByte = Wire.read(); - - // Request the low byte from the specified register of the device - Wire.beginTransmission(deviceAddress); - Wire.write(registerAddressLow); - int transmissionStatusLow = Wire.endTransmission(); - - if (transmissionStatusLow != 0) { - Serial.print("Error in I2C transmission (low byte). Status: "); - Serial.println(transmissionStatusLow); - return NAN; // Skip the rest of the loop if there's an error - } - - Wire.requestFrom(deviceAddress, 1); - - while (Wire.available() < 1); - - byte lowByte = Wire.read(); - - // Combine the high and low bytes to form a 12-bit value - int sensorValue = (highByte << 8) | lowByte; - - // Map the sensor value to degrees - float degrees = (float(sensorValue) / maxSensorValue) * maxDegrees; - - // Check if the angle is exactly 360.0, and if so, set it to 0.0 - if (degrees == 360.0) { - degrees = 0.0; - } - readValues++; - return degrees; -} - -// Calculate the mean angle from -180 to 180 degrees -double meanAngle(const float angles[], int size) { - double x = 0.0; - double y = 0.0; - - for (int i = 0; i < size; ++i) { - x += cos(angles[i] * PI / 180); - y += sin(angles[i] * PI / 180); - } - - return (atan2(y, x) * 180 / PI) + 180; -} - -void printArray(const float arr[], int size) { - Serial.print("["); - for (int i = 0; i < size; ++i) { - Serial.print(arr[i], 3); // Print each element with 3 decimal places - if (i < size - 1) { - Serial.print(", "); - } - } - Serial.println("]"); -} - -void getRotation() { - float newAngle = readAngle(); - if(newAngle > lastReadAngle + angleTolerance) { - AddToBuffer(1); - } else if(newAngle < lastReadAngle - angleTolerance) { - AddToBuffer(0); - } else { - nextBit = (nextBit + 1) % 2; //Add 1 and 0 in equal amounts - AddToBuffer(nextBit); - } - - lastReadAngle = newAngle; - - int ones = countOnes(directionBuffer); - int zeroes = 64 - ones; - if(debugging) { - Serial.println("Zeroes count: " + (String)zeroes + ", Ones count: " + (String)ones + "."); - } - int sensitivity = (lastTurnDirection == -1 || lastTurnDirection == 1) ? turnSensitivityDeactivation : turnSensitivityActivation; - if(ones > 64 - sensitivity) turnDirection = 1; - else if(ones <= sensitivity) turnDirection = -1; - else turnDirection = 0; - - if(printResult && lastTurnDirection != turnDirection) { - lastTurnDirection = turnDirection; - if(turnDirection == 1) Serial.println("Turning clockwise..."); - else if(turnDirection == -1) Serial.println("Turning counter-clockwise..."); - else if(turnDirection == 0) Serial.println("Not turning..."); - } - if(millis() - loopStartTime > 10000){ - Serial.println("Rate of "+(String)(readValues / 10)+" values per second."); - loopStartTime = millis(); - readValues = 0; - } -} - - +#include +#include +#include +#include +#include + +const int HALL_PIN = 32; + +//const char* ssid = "raspi-webgui"; +//const char* password = "bikingismylife"; +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 6666; +const unsigned long udpSendInterval = 500; // Interval for sending UDP packets in milliseconds +unsigned long lastUdpSendTime = 0; // Variable to store the last time UDP packet was sent + +WiFiUDP udp; + +using namespace std; // Use the std namespace for ArduinoSTL + +// General control settings and flags +const int debugging = 0; +const int printResult = 1; +const int directionalMode = 1; + +// Control timings in ms +const int loopTime = 60; // time between value calculation and printing in average angle reading +const int iterationCount = 20; // how many angle readings to average in one loop +const int iterationPadding = 0; // time between individual value readings + +// Control settings for turn direction reading +const float turnSensitivityActivation = 6; // default: 22 - for turn direction +const float turnSensitivityDeactivation = 31; + +// AS5600 device specifics +const int deviceAddress = 0x36; +const int registerAddressHigh = 0x0E; // Register for high 4 bits +const int registerAddressLow = 0x0F; // Register for low 8 bits +const int maxSensorValue = 4095; +const int maxDegrees = 360; +const int SDA_PIN = 18; +const int SCL_PIN = 19; + +//reused variables +unsigned long loopStartTime; +unsigned long loopEndTime; +unsigned long executionTime; +float lastReadAngle = 0; +int directionBufferIndex = 0; +int directionBufferSize = 0; +uint64_t directionBuffer = 0; +int turnDirection = 0; +int lastTurnDirection = 0; +int nextBit = 0; +int angleTolerance = 0; + + +int readValues = 0; + +void AddToBuffer(int val) { + directionBuffer = (directionBuffer << 1) | (val & 1); +} + +int countOnes(uint64_t n) +{ + unsigned int c; // the total bits set in n + for (c = 0; n; n = n & (n-1)) + { + c++; + } + return c; +} + +void printBufferBits() { + int i; + // The number of bits in a uint64_t is 64 + for (i = 63; i >= 0; i--) { + // Check the i-th bit using bitwise AND + uint64_t bit = (directionBuffer >> i) & 1; + Serial.print((int)bit); + } + Serial.println(); // Print a new line after all bits +} + +void setup() { + Wire.begin(SDA_PIN, SCL_PIN); + Serial.begin(115200); + + WiFi.hostname("Roll_ESP"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + Serial.print("Connecting with WiFi with ssid: "); + Serial.print(ssid); + Serial.print(" and password: "); + Serial.println(password); + Serial.println(WiFi.status()); + } + + Serial.println("Connection with WiFi successful"); + udp.begin(localUdpPort); + + loopStartTime = millis(); +} + +void loop() { + if (directionalMode) { + getRotation(); + } else { + averageAngle(); + } + + // Check if it's time to send the UDP packet + unsigned long currentTime = millis(); + if (currentTime - lastUdpSendTime >= udpSendInterval) { + StaticJsonDocument<500> doc; + + String jsonStr; + doc["sensor"] = "AS5600"; + doc["sensor_value"] = turnDirection; + + serializeJson(doc, jsonStr); + + // Read UDP messages + int packetSize = udp.parsePacket(); + if (packetSize) { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + udp.beginPacket("192.168.0.101", 6666); + udp.print(jsonStr); + udp.endPacket(); + + // Update the last send time + lastUdpSendTime = currentTime; + } +} + +void averageAngle() { + loopStartTime = millis(); + float angleReadings[iterationCount]; + + for (int it = 0; it < iterationCount; ++it) { + float angle = readAngle(); + + // Check if readAngle encountered an error + if (isnan(angle)) { + Serial.print(" !Reading Error! "); + continue; // Skip the rest of the loop if there's an error + } + angleReadings[it] = angle; + if(iterationPadding > 0)delay(iterationPadding); + } + + int readValuesSize = sizeof(angleReadings) / sizeof(angleReadings[0]); + // Calculate the mean angle using the array of readings + float averagedAngle = meanAngle(angleReadings, readValuesSize); + + if(debugging){ + Serial.println(""); + Serial.print("Raw values: "); + Serial.println(readValuesSize); + printArray(angleReadings, readValuesSize); + } + if(printResult) { + Serial.print("Angle: "); + Serial.println(averagedAngle, 3); // Print with 3 decimal places + } + loopEndTime = millis(); + executionTime = loopEndTime - loopStartTime; + + if(debugging) { + Serial.print("execution time: "); + Serial.println(executionTime); + } + if(executionTime <= loopTime) { + delay((float)(loopTime - executionTime)); + } else { + if(debugging) { + Serial.println("Cant keep up! Execution time of " + (String)executionTime + "ms is " + (String) (executionTime - loopTime) + "ms over the budget!"); + } else { + Serial.println(" Device Slowdown!"); + } + } +} + +float readAngle() { + // Request the high byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressHigh); + int transmissionStatusHigh = Wire.endTransmission(); + + if (transmissionStatusHigh != 0) { + Serial.print("Error in I2C transmission (high byte). Status: "); + Serial.println(transmissionStatusHigh); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte highByte = Wire.read(); + + // Request the low byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressLow); + int transmissionStatusLow = Wire.endTransmission(); + + if (transmissionStatusLow != 0) { + Serial.print("Error in I2C transmission (low byte). Status: "); + Serial.println(transmissionStatusLow); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte lowByte = Wire.read(); + + // Combine the high and low bytes to form a 12-bit value + int sensorValue = (highByte << 8) | lowByte; + + // Map the sensor value to degrees + float degrees = (float(sensorValue) / maxSensorValue) * maxDegrees; + + // Check if the angle is exactly 360.0, and if so, set it to 0.0 + if (degrees == 360.0) { + degrees = 0.0; + } + readValues++; + return degrees; +} + +// Calculate the mean angle from -180 to 180 degrees +double meanAngle(const float angles[], int size) { + double x = 0.0; + double y = 0.0; + + for (int i = 0; i < size; ++i) { + x += cos(angles[i] * PI / 180); + y += sin(angles[i] * PI / 180); + } + + return (atan2(y, x) * 180 / PI) + 180; +} + +void printArray(const float arr[], int size) { + Serial.print("["); + for (int i = 0; i < size; ++i) { + Serial.print(arr[i], 3); // Print each element with 3 decimal places + if (i < size - 1) { + Serial.print(", "); + } + } + Serial.println("]"); +} + +void getRotation() { + float newAngle = readAngle(); + if(newAngle > lastReadAngle + angleTolerance) { + AddToBuffer(1); + } else if(newAngle < lastReadAngle - angleTolerance) { + AddToBuffer(0); + } else { + nextBit = (nextBit + 1) % 2; //Add 1 and 0 in equal amounts + AddToBuffer(nextBit); + } + + lastReadAngle = newAngle; + + int ones = countOnes(directionBuffer); + int zeroes = 64 - ones; + if(debugging) { + Serial.println("Zeroes count: " + (String)zeroes + ", Ones count: " + (String)ones + "."); + } + int sensitivity = (lastTurnDirection == -1 || lastTurnDirection == 1) ? turnSensitivityDeactivation : turnSensitivityActivation; + if(ones > 64 - sensitivity) turnDirection = 1; + else if(ones <= sensitivity) turnDirection = -1; + else turnDirection = 0; + + if(printResult && lastTurnDirection != turnDirection) { + lastTurnDirection = turnDirection; + if(turnDirection == 1) Serial.println("Turning clockwise..."); + else if(turnDirection == -1) Serial.println("Turning counter-clockwise..."); + else if(turnDirection == 0) Serial.println("Not turning..."); + } + if(millis() - loopStartTime > 10000){ + Serial.println("Rate of "+(String)(readValues / 10)+" values per second."); + loopStartTime = millis(); + readValues = 0; + } +} + + diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore b/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore index 03f4a3c..16539a7 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.gitignore @@ -1 +1 @@ -.pio +.pio diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml b/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml index 7c486f1..a8bbc57 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.travis.yml @@ -1,67 +1,67 @@ -# Continuous Integration (CI) is the practice, in software -# engineering, of merging all developer working copies with a shared mainline -# several times a day < https://docs.platformio.org/page/ci/index.html > -# -# Documentation: -# -# * Travis CI Embedded Builds with PlatformIO -# < https://docs.travis-ci.com/user/integration/platformio/ > -# -# * PlatformIO integration with Travis CI -# < https://docs.platformio.org/page/ci/travis.html > -# -# * User Guide for `platformio ci` command -# < https://docs.platformio.org/page/userguide/cmd_ci.html > -# -# -# Please choose one of the following templates (proposed below) and uncomment -# it (remove "# " before each line) or use own configuration according to the -# Travis CI documentation (see above). -# - - -# -# Template #1: General project. Test it using existing `platformio.ini`. -# - -# language: python -# python: -# - "2.7" -# -# sudo: false -# cache: -# directories: -# - "~/.platformio" -# -# install: -# - pip install -U platformio -# - platformio update -# -# script: -# - platformio run - - -# -# Template #2: The project is intended to be used as a library with examples. -# - -# language: python -# python: -# - "2.7" -# -# sudo: false -# cache: -# directories: -# - "~/.platformio" -# -# env: -# - PLATFORMIO_CI_SRC=path/to/test/file.c -# - PLATFORMIO_CI_SRC=examples/file.ino -# - PLATFORMIO_CI_SRC=path/to/test/directory -# -# install: -# - pip install -U platformio -# - platformio update -# -# script: -# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < https://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < https://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < https://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choose one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to be used as a library with examples. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json index d409ba9..3afbab7 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/c_cpp_properties.json @@ -1,501 +1,501 @@ -// -// !!! WARNING !!! AUTO-GENERATED FILE! -// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": -// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags -// -{ - "configurations": [ - { - "name": "PlatformIO", - "includePath": [ - "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/include", - "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", - "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/libdeps/esp32dev/ArduinoJson/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/cores/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/variants/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", - "" - ], - "browse": { - "limitSymbolsToIncludedHeaders": true, - "path": [ - "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/include", - "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", - "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/libdeps/esp32dev/ArduinoJson/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/cores/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/variants/esp32", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", - "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", - "" - ] - }, - "defines": [ - "PLATFORMIO=60114", - "ARDUINO_ESP32_DEV", - "HAVE_CONFIG_H", - "MBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"", - "UNITY_INCLUDE_CONFIG_H", - "WITH_POSIX", - "_GNU_SOURCE", - "IDF_VER=\"v4.4.6-dirty\"", - "ESP_PLATFORM", - "_POSIX_READER_WRITER_LOCKS", - "ARDUINO_ARCH_ESP32", - "ESP32", - "F_CPU=240000000L", - "ARDUINO=10812", - "ARDUINO_VARIANT=\"esp32\"", - "ARDUINO_BOARD=\"Espressif ESP32 Dev Module\"", - "ARDUINO_PARTITION_default", - "" - ], - "cStandard": "gnu99", - "cppStandard": "gnu++11", - "compilerPath": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc.exe", - "compilerArgs": [ - "-mlongcalls", - "" - ] - } - ], - "version": 4 -} +// +// !!! WARNING !!! AUTO-GENERATED FILE! +// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": +// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags +// +{ + "configurations": [ + { + "name": "PlatformIO", + "includePath": [ + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/include", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/libdeps/esp32dev/ArduinoJson/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/cores/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/variants/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", + "" + ], + "browse": { + "limitSymbolsToIncludedHeaders": true, + "path": [ + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/include", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src", + "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/libdeps/esp32dev/ArduinoJson/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/mem/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/dio_qspi/include", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/cores/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/variants/esp32", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/FS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/USB/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Update/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src", + "C:/Users/unity/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src", + "" + ] + }, + "defines": [ + "PLATFORMIO=60114", + "ARDUINO_ESP32_DEV", + "HAVE_CONFIG_H", + "MBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"", + "UNITY_INCLUDE_CONFIG_H", + "WITH_POSIX", + "_GNU_SOURCE", + "IDF_VER=\"v4.4.6-dirty\"", + "ESP_PLATFORM", + "_POSIX_READER_WRITER_LOCKS", + "ARDUINO_ARCH_ESP32", + "ESP32", + "F_CPU=240000000L", + "ARDUINO=10812", + "ARDUINO_VARIANT=\"esp32\"", + "ARDUINO_BOARD=\"Espressif ESP32 Dev Module\"", + "ARDUINO_PARTITION_default", + "" + ], + "cStandard": "gnu99", + "cppStandard": "gnu++11", + "compilerPath": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcc.exe", + "compilerArgs": [ + "-mlongcalls", + "" + ] + } + ], + "version": 4 +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json index 080e70d..0678aa0 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/extensions.json @@ -1,10 +1,10 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json index 09ba71d..6213b90 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json +++ b/collector_scripts/sensor_scripts/Brake_Sensor/.vscode/launch.json @@ -1,44 +1,44 @@ -// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY -// -// PlatformIO Debugging Solution -// -// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html -// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html - -{ - "version": "0.2.0", - "configurations": [ - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug", - "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", - "projectEnvName": "esp32dev", - "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", - "internalConsoleOptions": "openOnSessionStart", - "preLaunchTask": { - "type": "PlatformIO", - "task": "Pre-Debug" - } - }, - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug (skip Pre-Debug)", - "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", - "projectEnvName": "esp32dev", - "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug (without uploading)", - "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", - "projectEnvName": "esp32dev", - "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", - "internalConsoleOptions": "openOnSessionStart", - "loadMode": "manual" - } - ] -} +// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY +// +// PlatformIO Debugging Solution +// +// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html +// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html + +{ + "version": "0.2.0", + "configurations": [ + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug", + "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32dev", + "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", + "internalConsoleOptions": "openOnSessionStart", + "preLaunchTask": { + "type": "PlatformIO", + "task": "Pre-Debug" + } + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (skip Pre-Debug)", + "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32dev", + "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", + "internalConsoleOptions": "openOnSessionStart" + }, + { + "type": "platformio-debug", + "request": "launch", + "name": "PIO Debug (without uploading)", + "executable": "c:/Users/unity/Desktop/BicycleSimulator/Brake_Sensor/.pio/build/esp32dev/firmware.elf", + "projectEnvName": "esp32dev", + "toolchainBinDir": "C:/Users/unity/.platformio/packages/toolchain-xtensa-esp32/bin", + "internalConsoleOptions": "openOnSessionStart", + "loadMode": "manual" + } + ] +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/README.rst b/collector_scripts/sensor_scripts/Brake_Sensor/README.rst index 48bf203..90d2137 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/README.rst +++ b/collector_scripts/sensor_scripts/Brake_Sensor/README.rst @@ -1,38 +1,38 @@ -.. Copyright 2014-present PlatformIO - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -How to build PlatformIO based project -===================================== - -1. `Install PlatformIO Core `_ -2. Download `development platform with examples `_ -3. Extract ZIP archive -4. Run these commands: - -.. code-block:: bash - - # Change directory to example - > cd platform-espressif32/examples/arduino-wifiscan - - # Build project - > platformio run - - # Upload firmware - > platformio run --target upload - - # Build specific environment - > platformio run -e quantum - - # Upload firmware for the specific environment - > platformio run -e quantum --target upload - - # Clean build files - > platformio run --target clean +.. Copyright 2014-present PlatformIO + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +How to build PlatformIO based project +===================================== + +1. `Install PlatformIO Core `_ +2. Download `development platform with examples `_ +3. Extract ZIP archive +4. Run these commands: + +.. code-block:: bash + + # Change directory to example + > cd platform-espressif32/examples/arduino-wifiscan + + # Build project + > platformio run + + # Upload firmware + > platformio run --target upload + + # Build specific environment + > platformio run -e quantum + + # Upload firmware for the specific environment + > platformio run -e quantum --target upload + + # Clean build files + > platformio run --target clean diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini b/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini index 9d8e03c..16189e5 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini +++ b/collector_scripts/sensor_scripts/Brake_Sensor/platformio.ini @@ -1,17 +1,17 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:esp32dev] -platform = espressif32 -board = esp32dev -framework = arduino -monitor_speed = 115200 -lib_deps = - bblanchon/ArduinoJson@^6.21.2 +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +monitor_speed = 115200 +lib_deps = + bblanchon/ArduinoJson@^6.21.2 diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp b/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp index 525995d..e0305c7 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp +++ b/collector_scripts/sensor_scripts/Brake_Sensor/src/main.cpp @@ -1,63 +1,63 @@ -#include -#include -#include -#include - -const int HALL_PIN = 32; - -const char *ssid = "Bicycle_Simulator_Network"; -const char *password = "17701266"; -unsigned int localUdpPort = 8888; - -WiFiUDP udp; - -void setup() -{ - - pinMode(HALL_PIN, INPUT_PULLUP); - delay(1000); - - WiFi.hostname("Brake_ESP"); - - Serial.begin(115200); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) - { - delay(1000); - Serial.println("Connecting with WiFi"); - } - - Serial.println("Connection with WiFi successful"); - udp.begin(localUdpPort); -} - -void loop() -{ - - int sensorValue = analogRead(HALL_PIN); // read Sensor value - StaticJsonDocument<500> doc; - - String jsonStr; - doc["sensor"] = "Brake"; - doc["sensor_value"] = sensorValue; - - serializeJson(doc, jsonStr); - - // Read UDP messages - int packetSize = udp.parsePacket(); - if (packetSize) - { - char packetBuffer[255]; - udp.read(packetBuffer, packetSize); - - Serial.print("Received message: "); - Serial.println(packetBuffer); - } - - udp.beginPacket("192.168.0.101", 7777); - udp.print(jsonStr); - udp.endPacket(); - - delay(1000); -} +#include +#include +#include +#include + +const int HALL_PIN = 32; + +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 8888; + +WiFiUDP udp; + +void setup() +{ + + pinMode(HALL_PIN, INPUT_PULLUP); + delay(1000); + + WiFi.hostname("Brake_ESP"); + + Serial.begin(115200); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + Serial.println("Connecting with WiFi"); + } + + Serial.println("Connection with WiFi successful"); + udp.begin(localUdpPort); +} + +void loop() +{ + + int sensorValue = analogRead(HALL_PIN); // read Sensor value + StaticJsonDocument<500> doc; + + String jsonStr; + doc["sensor"] = "Brake"; + doc["sensor_value"] = sensorValue; + + serializeJson(doc, jsonStr); + + // Read UDP messages + int packetSize = udp.parsePacket(); + if (packetSize) + { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + udp.beginPacket("192.168.0.101", 7777); + udp.print(jsonStr); + udp.endPacket(); + + delay(1000); +} diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/test/README b/collector_scripts/sensor_scripts/Brake_Sensor/test/README index df5066e..c3b0ed6 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/test/README +++ b/collector_scripts/sensor_scripts/Brake_Sensor/test/README @@ -1,11 +1,11 @@ - -This directory is intended for PIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore b/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore index 89cc49c..5762142 100644 --- a/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/.gitignore @@ -1,5 +1,5 @@ -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json b/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json index 080e70d..0678aa0 100644 --- a/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/.vscode/extensions.json @@ -1,10 +1,10 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini b/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini index b62f6af..b25c41d 100644 --- a/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/platformio.ini @@ -1,18 +1,18 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:upesy_wroom] -platform = espressif32 -board = upesy_wroom -framework = arduino -monitor_speed = 115200 -lib_deps = - bblanchon/ArduinoJson@^6.21.2 - arduino-libraries/BNO055@^1.2.1 +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:upesy_wroom] +platform = espressif32 +board = upesy_wroom +framework = arduino +monitor_speed = 115200 +lib_deps = + bblanchon/ArduinoJson@^6.21.2 + arduino-libraries/BNO055@^1.2.1 diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp b/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp index d934ed6..a80bd35 100644 --- a/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/src/main.cpp @@ -1,84 +1,84 @@ -#include - -#include -#include - -#include "BNO055_support.h" -#include - -#include - -const char *ssid = "Bicycle_Simulator_Network"; -const char *password = "17701266"; -unsigned int localUdpPort = 8888; - -WiFiUDP udp; - -struct bno055_t myBNO; -struct bno055_euler myEulerData; // Structure to hold the Euler data -struct bno055_gyro myGyroData; // Structure to hold the Gyro data - -unsigned long lastTime = 0; - -void setup() -{ - // Initialize I2C communication - Wire.begin(); - - // Create a static buffer for framing BME280 sensor data with ESP32 chip ID. - - // Initialization of the BNO055 - BNO_Init(&myBNO); // Assigning the structure to hold information about the device - - // Configuration to NDoF mode - bno055_set_operation_mode(OPERATION_MODE_NDOF); - - delay(1); - - Serial.begin(115200); - WiFi.hostname("Gyro_ESP"); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) - { - delay(1000); // setting sending rate - Serial.println("Connecting with WiFi"); - } - - Serial.println("Connection with WiFi successful"); - - udp.begin(localUdpPort); -} - -void loop() -{ - bno055_read_euler_hrp(&myEulerData); // Update Euler data into the structure - StaticJsonDocument<500> doc; - - int packetSize = udp.parsePacket(); - - // Method for receiving data - if (packetSize) - { - char packetBuffer[255]; - udp.read(packetBuffer, packetSize); - - Serial.print("Received message: "); - Serial.println(packetBuffer); - } - - // Creating JSON for sensor data - String jsonStr; - doc["sensor"] = "BNO055"; - doc["euler_h"] = ((myEulerData.h) / 16.00); - doc["euler_r"] = ((myEulerData.r) / 16.00); - doc["euler_p"] = ((myEulerData.p) / 16.00); - - serializeJson(doc, jsonStr); - - udp.beginPacket("192.168.0.101", 8888); - udp.print(jsonStr); - udp.endPacket(); - - delay(10); -} +#include + +#include +#include + +#include "BNO055_support.h" +#include + +#include + +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 8888; + +WiFiUDP udp; + +struct bno055_t myBNO; +struct bno055_euler myEulerData; // Structure to hold the Euler data +struct bno055_gyro myGyroData; // Structure to hold the Gyro data + +unsigned long lastTime = 0; + +void setup() +{ + // Initialize I2C communication + Wire.begin(); + + // Create a static buffer for framing BME280 sensor data with ESP32 chip ID. + + // Initialization of the BNO055 + BNO_Init(&myBNO); // Assigning the structure to hold information about the device + + // Configuration to NDoF mode + bno055_set_operation_mode(OPERATION_MODE_NDOF); + + delay(1); + + Serial.begin(115200); + WiFi.hostname("Gyro_ESP"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); // setting sending rate + Serial.println("Connecting with WiFi"); + } + + Serial.println("Connection with WiFi successful"); + + udp.begin(localUdpPort); +} + +void loop() +{ + bno055_read_euler_hrp(&myEulerData); // Update Euler data into the structure + StaticJsonDocument<500> doc; + + int packetSize = udp.parsePacket(); + + // Method for receiving data + if (packetSize) + { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + // Creating JSON for sensor data + String jsonStr; + doc["sensor"] = "BNO055"; + doc["euler_h"] = ((myEulerData.h) / 16.00); + doc["euler_r"] = ((myEulerData.r) / 16.00); + doc["euler_p"] = ((myEulerData.p) / 16.00); + + serializeJson(doc, jsonStr); + + udp.beginPacket("192.168.0.101", 8888); + udp.print(jsonStr); + udp.endPacket(); + + delay(10); +} diff --git a/collector_scripts/sensor_scripts/Gyro_Sensor/test/README b/collector_scripts/sensor_scripts/Gyro_Sensor/test/README index 9b1e87b..b0416ad 100644 --- a/collector_scripts/sensor_scripts/Gyro_Sensor/test/README +++ b/collector_scripts/sensor_scripts/Gyro_Sensor/test/README @@ -1,11 +1,11 @@ - -This directory is intended for PlatformIO Test Runner and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore b/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore index 89cc49c..5762142 100644 --- a/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore +++ b/collector_scripts/sensor_scripts/Roll_Sensor/.gitignore @@ -1,5 +1,5 @@ -.pio -.vscode/.browse.c_cpp.db* -.vscode/c_cpp_properties.json -.vscode/launch.json -.vscode/ipch +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json b/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json index 080e70d..0678aa0 100644 --- a/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json +++ b/collector_scripts/sensor_scripts/Roll_Sensor/.vscode/extensions.json @@ -1,10 +1,10 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini b/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini index dc48bbc..5408a38 100644 --- a/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini +++ b/collector_scripts/sensor_scripts/Roll_Sensor/platformio.ini @@ -1,15 +1,15 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[env:esp32dev] -platform = espressif32 -board = esp32dev -framework = arduino -lib_deps = bblanchon/ArduinoJson@^7.0.3 +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = bblanchon/ArduinoJson@^7.0.3 diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp b/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp index f64894e..e8e088a 100644 --- a/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp +++ b/collector_scripts/sensor_scripts/Roll_Sensor/src/main.cpp @@ -1,300 +1,300 @@ -#include -#include -#include -#include -#include - -const int HALL_PIN = 32; - -//const char* ssid = "raspi-webgui"; -//const char* password = "bikingismylife"; -const char *ssid = "Bicycle_Simulator_Network"; -const char *password = "17701266"; -unsigned int localUdpPort = 6666; -const unsigned long udpSendInterval = 500; // Interval for sending UDP packets in milliseconds -unsigned long lastUdpSendTime = 0; // Variable to store the last time UDP packet was sent - -WiFiUDP udp; - -using namespace std; // Use the std namespace for ArduinoSTL - -// General control settings and flags -const int debugging = 0; -const int printResult = 1; -const int directionalMode = 1; - -// Control timings in ms -const int loopTime = 60; // time between value calculation and printing in average angle reading -const int iterationCount = 20; // how many angle readings to average in one loop -const int iterationPadding = 0; // time between individual value readings - -// Control settings for turn direction reading -const float turnSensitivityActivation = 6; // default: 22 - for turn direction -const float turnSensitivityDeactivation = 31; - -// AS5600 device specifics -const int deviceAddress = 0x36; -const int registerAddressHigh = 0x0E; // Register for high 4 bits -const int registerAddressLow = 0x0F; // Register for low 8 bits -const int maxSensorValue = 4095; -const int maxDegrees = 360; -const int SDA_PIN = 18; -const int SCL_PIN = 19; - -//reused variables -unsigned long loopStartTime; -unsigned long loopEndTime; -unsigned long executionTime; -float lastReadAngle = 0; -int directionBufferIndex = 0; -int directionBufferSize = 0; -uint64_t directionBuffer = 0; -int turnDirection = 0; -int lastTurnDirection = 0; -int nextBit = 0; -int angleTolerance = 0; - - -int readValues = 0; - -void AddToBuffer(int val) { - directionBuffer = (directionBuffer << 1) | (val & 1); -} - -int countOnes(uint64_t n) -{ - unsigned int c; // the total bits set in n - for (c = 0; n; n = n & (n-1)) - { - c++; - } - return c; -} - -void printBufferBits() { - int i; - // The number of bits in a uint64_t is 64 - for (i = 63; i >= 0; i--) { - // Check the i-th bit using bitwise AND - uint64_t bit = (directionBuffer >> i) & 1; - Serial.print((int)bit); - } - Serial.println(); // Print a new line after all bits -} - -void setup() { - Wire.begin(SDA_PIN, SCL_PIN); - Serial.begin(115200); - - WiFi.hostname("Roll_ESP"); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) - { - delay(1000); - Serial.print("Connecting with WiFi with ssid: "); - Serial.print(ssid); - Serial.print(" and password: "); - Serial.println(password); - Serial.println(WiFi.status()); - } - - Serial.println("Connection with WiFi successful"); - udp.begin(localUdpPort); - - loopStartTime = millis(); -} - -void loop() { - if (directionalMode) { - getRotation(); - } else { - averageAngle(); - } - - // Check if it's time to send the UDP packet - unsigned long currentTime = millis(); - if (currentTime - lastUdpSendTime >= udpSendInterval) { - StaticJsonDocument<500> doc; - - String jsonStr; - doc["sensor"] = "AS5600"; - doc["sensor_value"] = turnDirection; - - serializeJson(doc, jsonStr); - - // Read UDP messages - int packetSize = udp.parsePacket(); - if (packetSize) { - char packetBuffer[255]; - udp.read(packetBuffer, packetSize); - - Serial.print("Received message: "); - Serial.println(packetBuffer); - } - - udp.beginPacket("192.168.0.101", 6666); - udp.print(jsonStr); - udp.endPacket(); - - // Update the last send time - lastUdpSendTime = currentTime; - } -} - -void averageAngle() { - loopStartTime = millis(); - float angleReadings[iterationCount]; - - for (int it = 0; it < iterationCount; ++it) { - float angle = readAngle(); - - // Check if readAngle encountered an error - if (isnan(angle)) { - Serial.print(" !Reading Error! "); - continue; // Skip the rest of the loop if there's an error - } - angleReadings[it] = angle; - if(iterationPadding > 0)delay(iterationPadding); - } - - int readValuesSize = sizeof(angleReadings) / sizeof(angleReadings[0]); - // Calculate the mean angle using the array of readings - float averagedAngle = meanAngle(angleReadings, readValuesSize); - - if(debugging){ - Serial.println(""); - Serial.print("Raw values: "); - Serial.println(readValuesSize); - printArray(angleReadings, readValuesSize); - } - if(printResult) { - Serial.print("Angle: "); - Serial.println(averagedAngle, 3); // Print with 3 decimal places - } - loopEndTime = millis(); - executionTime = loopEndTime - loopStartTime; - - if(debugging) { - Serial.print("execution time: "); - Serial.println(executionTime); - } - if(executionTime <= loopTime) { - delay((float)(loopTime - executionTime)); - } else { - if(debugging) { - Serial.println("Cant keep up! Execution time of " + (String)executionTime + "ms is " + (String) (executionTime - loopTime) + "ms over the budget!"); - } else { - Serial.println(" Device Slowdown!"); - } - } -} - -float readAngle() { - // Request the high byte from the specified register of the device - Wire.beginTransmission(deviceAddress); - Wire.write(registerAddressHigh); - int transmissionStatusHigh = Wire.endTransmission(); - - if (transmissionStatusHigh != 0) { - Serial.print("Error in I2C transmission (high byte). Status: "); - Serial.println(transmissionStatusHigh); - return NAN; // Skip the rest of the loop if there's an error - } - - Wire.requestFrom(deviceAddress, 1); - - while (Wire.available() < 1); - - byte highByte = Wire.read(); - - // Request the low byte from the specified register of the device - Wire.beginTransmission(deviceAddress); - Wire.write(registerAddressLow); - int transmissionStatusLow = Wire.endTransmission(); - - if (transmissionStatusLow != 0) { - Serial.print("Error in I2C transmission (low byte). Status: "); - Serial.println(transmissionStatusLow); - return NAN; // Skip the rest of the loop if there's an error - } - - Wire.requestFrom(deviceAddress, 1); - - while (Wire.available() < 1); - - byte lowByte = Wire.read(); - - // Combine the high and low bytes to form a 12-bit value - int sensorValue = (highByte << 8) | lowByte; - - // Map the sensor value to degrees - float degrees = (float(sensorValue) / maxSensorValue) * maxDegrees; - - // Check if the angle is exactly 360.0, and if so, set it to 0.0 - if (degrees == 360.0) { - degrees = 0.0; - } - readValues++; - return degrees; -} - -// Calculate the mean angle from -180 to 180 degrees -double meanAngle(const float angles[], int size) { - double x = 0.0; - double y = 0.0; - - for (int i = 0; i < size; ++i) { - x += cos(angles[i] * PI / 180); - y += sin(angles[i] * PI / 180); - } - - return (atan2(y, x) * 180 / PI) + 180; -} - -void printArray(const float arr[], int size) { - Serial.print("["); - for (int i = 0; i < size; ++i) { - Serial.print(arr[i], 3); // Print each element with 3 decimal places - if (i < size - 1) { - Serial.print(", "); - } - } - Serial.println("]"); -} - -void getRotation() { - float newAngle = readAngle(); - if(newAngle > lastReadAngle + angleTolerance) { - AddToBuffer(1); - } else if(newAngle < lastReadAngle - angleTolerance) { - AddToBuffer(0); - } else { - nextBit = (nextBit + 1) % 2; //Add 1 and 0 in equal amounts - AddToBuffer(nextBit); - } - - lastReadAngle = newAngle; - - int ones = countOnes(directionBuffer); - int zeroes = 64 - ones; - if(debugging) { - Serial.println("Zeroes count: " + (String)zeroes + ", Ones count: " + (String)ones + "."); - } - int sensitivity = (lastTurnDirection == -1 || lastTurnDirection == 1) ? turnSensitivityDeactivation : turnSensitivityActivation; - if(ones > 64 - sensitivity) turnDirection = 1; - else if(ones <= sensitivity) turnDirection = -1; - else turnDirection = 0; - - if(printResult && lastTurnDirection != turnDirection) { - lastTurnDirection = turnDirection; - if(turnDirection == 1) Serial.println("Turning clockwise..."); - else if(turnDirection == -1) Serial.println("Turning counter-clockwise..."); - else if(turnDirection == 0) Serial.println("Not turning..."); - } - if(millis() - loopStartTime > 10000){ - Serial.println("Rate of "+(String)(readValues / 10)+" values per second."); - loopStartTime = millis(); - readValues = 0; - } +#include +#include +#include +#include +#include + +const int HALL_PIN = 32; + +//const char* ssid = "raspi-webgui"; +//const char* password = "bikingismylife"; +const char *ssid = "Bicycle_Simulator_Network"; +const char *password = "17701266"; +unsigned int localUdpPort = 6666; +const unsigned long udpSendInterval = 500; // Interval for sending UDP packets in milliseconds +unsigned long lastUdpSendTime = 0; // Variable to store the last time UDP packet was sent + +WiFiUDP udp; + +using namespace std; // Use the std namespace for ArduinoSTL + +// General control settings and flags +const int debugging = 0; +const int printResult = 1; +const int directionalMode = 1; + +// Control timings in ms +const int loopTime = 60; // time between value calculation and printing in average angle reading +const int iterationCount = 20; // how many angle readings to average in one loop +const int iterationPadding = 0; // time between individual value readings + +// Control settings for turn direction reading +const float turnSensitivityActivation = 6; // default: 22 - for turn direction +const float turnSensitivityDeactivation = 31; + +// AS5600 device specifics +const int deviceAddress = 0x36; +const int registerAddressHigh = 0x0E; // Register for high 4 bits +const int registerAddressLow = 0x0F; // Register for low 8 bits +const int maxSensorValue = 4095; +const int maxDegrees = 360; +const int SDA_PIN = 18; +const int SCL_PIN = 19; + +//reused variables +unsigned long loopStartTime; +unsigned long loopEndTime; +unsigned long executionTime; +float lastReadAngle = 0; +int directionBufferIndex = 0; +int directionBufferSize = 0; +uint64_t directionBuffer = 0; +int turnDirection = 0; +int lastTurnDirection = 0; +int nextBit = 0; +int angleTolerance = 0; + + +int readValues = 0; + +void AddToBuffer(int val) { + directionBuffer = (directionBuffer << 1) | (val & 1); +} + +int countOnes(uint64_t n) +{ + unsigned int c; // the total bits set in n + for (c = 0; n; n = n & (n-1)) + { + c++; + } + return c; +} + +void printBufferBits() { + int i; + // The number of bits in a uint64_t is 64 + for (i = 63; i >= 0; i--) { + // Check the i-th bit using bitwise AND + uint64_t bit = (directionBuffer >> i) & 1; + Serial.print((int)bit); + } + Serial.println(); // Print a new line after all bits +} + +void setup() { + Wire.begin(SDA_PIN, SCL_PIN); + Serial.begin(115200); + + WiFi.hostname("Roll_ESP"); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) + { + delay(1000); + Serial.print("Connecting with WiFi with ssid: "); + Serial.print(ssid); + Serial.print(" and password: "); + Serial.println(password); + Serial.println(WiFi.status()); + } + + Serial.println("Connection with WiFi successful"); + udp.begin(localUdpPort); + + loopStartTime = millis(); +} + +void loop() { + if (directionalMode) { + getRotation(); + } else { + averageAngle(); + } + + // Check if it's time to send the UDP packet + unsigned long currentTime = millis(); + if (currentTime - lastUdpSendTime >= udpSendInterval) { + StaticJsonDocument<500> doc; + + String jsonStr; + doc["sensor"] = "AS5600"; + doc["sensor_value"] = turnDirection; + + serializeJson(doc, jsonStr); + + // Read UDP messages + int packetSize = udp.parsePacket(); + if (packetSize) { + char packetBuffer[255]; + udp.read(packetBuffer, packetSize); + + Serial.print("Received message: "); + Serial.println(packetBuffer); + } + + udp.beginPacket("192.168.0.101", 6666); + udp.print(jsonStr); + udp.endPacket(); + + // Update the last send time + lastUdpSendTime = currentTime; + } +} + +void averageAngle() { + loopStartTime = millis(); + float angleReadings[iterationCount]; + + for (int it = 0; it < iterationCount; ++it) { + float angle = readAngle(); + + // Check if readAngle encountered an error + if (isnan(angle)) { + Serial.print(" !Reading Error! "); + continue; // Skip the rest of the loop if there's an error + } + angleReadings[it] = angle; + if(iterationPadding > 0)delay(iterationPadding); + } + + int readValuesSize = sizeof(angleReadings) / sizeof(angleReadings[0]); + // Calculate the mean angle using the array of readings + float averagedAngle = meanAngle(angleReadings, readValuesSize); + + if(debugging){ + Serial.println(""); + Serial.print("Raw values: "); + Serial.println(readValuesSize); + printArray(angleReadings, readValuesSize); + } + if(printResult) { + Serial.print("Angle: "); + Serial.println(averagedAngle, 3); // Print with 3 decimal places + } + loopEndTime = millis(); + executionTime = loopEndTime - loopStartTime; + + if(debugging) { + Serial.print("execution time: "); + Serial.println(executionTime); + } + if(executionTime <= loopTime) { + delay((float)(loopTime - executionTime)); + } else { + if(debugging) { + Serial.println("Cant keep up! Execution time of " + (String)executionTime + "ms is " + (String) (executionTime - loopTime) + "ms over the budget!"); + } else { + Serial.println(" Device Slowdown!"); + } + } +} + +float readAngle() { + // Request the high byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressHigh); + int transmissionStatusHigh = Wire.endTransmission(); + + if (transmissionStatusHigh != 0) { + Serial.print("Error in I2C transmission (high byte). Status: "); + Serial.println(transmissionStatusHigh); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte highByte = Wire.read(); + + // Request the low byte from the specified register of the device + Wire.beginTransmission(deviceAddress); + Wire.write(registerAddressLow); + int transmissionStatusLow = Wire.endTransmission(); + + if (transmissionStatusLow != 0) { + Serial.print("Error in I2C transmission (low byte). Status: "); + Serial.println(transmissionStatusLow); + return NAN; // Skip the rest of the loop if there's an error + } + + Wire.requestFrom(deviceAddress, 1); + + while (Wire.available() < 1); + + byte lowByte = Wire.read(); + + // Combine the high and low bytes to form a 12-bit value + int sensorValue = (highByte << 8) | lowByte; + + // Map the sensor value to degrees + float degrees = (float(sensorValue) / maxSensorValue) * maxDegrees; + + // Check if the angle is exactly 360.0, and if so, set it to 0.0 + if (degrees == 360.0) { + degrees = 0.0; + } + readValues++; + return degrees; +} + +// Calculate the mean angle from -180 to 180 degrees +double meanAngle(const float angles[], int size) { + double x = 0.0; + double y = 0.0; + + for (int i = 0; i < size; ++i) { + x += cos(angles[i] * PI / 180); + y += sin(angles[i] * PI / 180); + } + + return (atan2(y, x) * 180 / PI) + 180; +} + +void printArray(const float arr[], int size) { + Serial.print("["); + for (int i = 0; i < size; ++i) { + Serial.print(arr[i], 3); // Print each element with 3 decimal places + if (i < size - 1) { + Serial.print(", "); + } + } + Serial.println("]"); +} + +void getRotation() { + float newAngle = readAngle(); + if(newAngle > lastReadAngle + angleTolerance) { + AddToBuffer(1); + } else if(newAngle < lastReadAngle - angleTolerance) { + AddToBuffer(0); + } else { + nextBit = (nextBit + 1) % 2; //Add 1 and 0 in equal amounts + AddToBuffer(nextBit); + } + + lastReadAngle = newAngle; + + int ones = countOnes(directionBuffer); + int zeroes = 64 - ones; + if(debugging) { + Serial.println("Zeroes count: " + (String)zeroes + ", Ones count: " + (String)ones + "."); + } + int sensitivity = (lastTurnDirection == -1 || lastTurnDirection == 1) ? turnSensitivityDeactivation : turnSensitivityActivation; + if(ones > 64 - sensitivity) turnDirection = 1; + else if(ones <= sensitivity) turnDirection = -1; + else turnDirection = 0; + + if(printResult && lastTurnDirection != turnDirection) { + lastTurnDirection = turnDirection; + if(turnDirection == 1) Serial.println("Turning clockwise..."); + else if(turnDirection == -1) Serial.println("Turning counter-clockwise..."); + else if(turnDirection == 0) Serial.println("Not turning..."); + } + if(millis() - loopStartTime > 10000){ + Serial.println("Rate of "+(String)(readValues / 10)+" values per second."); + loopStartTime = millis(); + readValues = 0; + } } \ No newline at end of file diff --git a/collector_scripts/sensor_scripts/Roll_Sensor/test/README b/collector_scripts/sensor_scripts/Roll_Sensor/test/README index 9b1e87b..b0416ad 100644 --- a/collector_scripts/sensor_scripts/Roll_Sensor/test/README +++ b/collector_scripts/sensor_scripts/Roll_Sensor/test/README @@ -1,11 +1,11 @@ - -This directory is intended for PlatformIO Test Runner and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/docs/ hardware_idears.md b/docs/ hardware_idears.md index e484490..bf37691 100644 --- a/docs/ hardware_idears.md +++ b/docs/ hardware_idears.md @@ -1,3 +1,3 @@ -- Foot sensor while stand still Structure-borne sound transducer maybe with light barrier or pressure sensor -- Structure-borne sound transducer for undergound feedback and crashes (rockhorn) -- Brake feedback (maybe with pressure sensor) +- Foot sensor while stand still Structure-borne sound transducer maybe with light barrier or pressure sensor +- Structure-borne sound transducer for undergound feedback and crashes (rockhorn) +- Brake feedback (maybe with pressure sensor) diff --git a/docs/rizer_classdiagram.wsd b/docs/rizer_classdiagram.wsd index 931741c..8cbddec 100644 --- a/docs/rizer_classdiagram.wsd +++ b/docs/rizer_classdiagram.wsd @@ -1,16 +1,16 @@ -@startuml - -class BluetoothCallback { - notify_steering_callback - read_steering - write_tilt - scan_and_connect_rizer -} - -class UDP_Handler{ - send_steering_data_udp - listening_udp - get_new_tilt_value -} - +@startuml + +class BluetoothCallback { + notify_steering_callback + read_steering + write_tilt + scan_and_connect_rizer +} + +class UDP_Handler{ + send_steering_data_udp + listening_udp + get_new_tilt_value +} + @enduml \ No newline at end of file diff --git a/docs/rizer_sequencediagram.wsd b/docs/rizer_sequencediagram.wsd index 712409a..12ec8c6 100644 --- a/docs/rizer_sequencediagram.wsd +++ b/docs/rizer_sequencediagram.wsd @@ -1,35 +1,35 @@ -@startuml test -title "RIZER" - -participant Unity -participant Master_Collector -participant UDP_Handler -participant BluetoothCallback -participant RIZER - - -par - UDP_Handler -> UDP_Handler: listen for tilt -end - -Unity -> Master_Collector: send new tilt -Master_Collector -> UDP_Handler: send new tilt - -UDP_Handler -> BluetoothCallback: computed tilt commands - -par "tilt 0.5" - - alt - BluetoothCallback -> RIZER: send tilt - end - - BluetoothCallback -> BluetoothCallback: listen for steering - RIZER -> BluetoothCallback: send steering -end - - -BluetoothCallback -> UDP_Handler: send steering -UDP_Handler -> Master_Collector: send steering -Master_Collector -> Unity: send steering - -@enduml +@startuml test +title "RIZER" + +participant Unity +participant Master_Collector +participant UDP_Handler +participant BluetoothCallback +participant RIZER + + +par + UDP_Handler -> UDP_Handler: listen for tilt +end + +Unity -> Master_Collector: send new tilt +Master_Collector -> UDP_Handler: send new tilt + +UDP_Handler -> BluetoothCallback: computed tilt commands + +par "tilt 0.5" + + alt + BluetoothCallback -> RIZER: send tilt + end + + BluetoothCallback -> BluetoothCallback: listen for steering + RIZER -> BluetoothCallback: send steering +end + + +BluetoothCallback -> UDP_Handler: send steering +UDP_Handler -> Master_Collector: send steering +Master_Collector -> Unity: send steering + +@enduml diff --git a/requirements.txt b/requirements.txt index f59bbe2..c9f803e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -# bluepy==1.3.0 -asyncio==3.4.3 -bleak==0.21.1 -sockets==1.0.0 -# git+https://github.com/almottier/TapoP100.git@main -# logging -# struct +# bluepy==1.3.0 +asyncio==3.4.3 +bleak==0.21.1 +sockets==1.0.0 +# git+https://github.com/almottier/TapoP100.git@main +# logging +# struct From f3e32b7f6c855e75b2a3071f948c150e78f159be Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 25 Jun 2024 14:23:17 +0200 Subject: [PATCH 52/65] added UDP tester --- collector_scripts/UDP_tester.py | 18 +++++++++++ collector_scripts/elite_rizer3.py | 52 ++++++++++++++++++------------- 2 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 collector_scripts/UDP_tester.py diff --git a/collector_scripts/UDP_tester.py b/collector_scripts/UDP_tester.py new file mode 100644 index 0000000..16844fe --- /dev/null +++ b/collector_scripts/UDP_tester.py @@ -0,0 +1,18 @@ +import socket + +def send_udp_data(ip, port, data): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Konvertiere die Ganzzahl in Bytes + data_bytes = str(data).encode() + udp_socket.sendto(data_bytes, (ip, port)) + print(f"Gesendete Nachricht: {data} an {ip}:{port}") + +# Beispiel für die Nutzung der Funktion +if __name__ == "__main__": + # Eingaben vom Benutzer einholen + ip = input("Geben Sie die IP-Adresse des Empfängers ein: ") + port = int(input("Geben Sie den Port des Empfängers ein: ")) + data = int(input("Geben Sie die zu sendende Ganzzahl ein: ")) + + # UDP-Daten senden + send_udp_data(ip, port, data) \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 9ea2190..0c328b6 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -42,27 +42,35 @@ async def main(self): isRunningUDP = True self.steering_data = None steering_received = None + udp_incline_data = 0 #print("rizer id: ", id(receiver)) - while(True): - await asyncio.sleep(asyncio_sleep) - print("udp main") - print("start listener") - self.receive_incline_data_udp() - if (steering_received == 1): #when steering value has chanched, send it to unity - print("send steering data") - await self.send_steering_data_udp(self.steering_data) - try: - #self.receiver._receiver.stop_udp_listener() - #print("fan speed (b c)", self.receiver.get_fan_speed()) - print("incline from UDP (b c): ", incline_value) - #self.check_new_incline(incline_value) #check if tilt value has changed or is still the same + # Set up the UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.bind((self.udp_ip_from_master_collector, self.receive_from_collector_port)) + while(True): + await asyncio.sleep(asyncio_sleep) + print("udp main") + print("start listener") + # Receive data from the socket + udp_incline_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + sender_ip, sender_port = addr # Extract the sender's IP and port from addr + print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + self.incline_value = udp_incline_data.decode() + if (steering_received == 1): #when steering value has chanched, send it to unity + print("send steering data") + await self.send_steering_data_udp(self.steering_data) + try: + #self.receiver._receiver.stop_udp_listener() + #print("fan speed (b c)", self.receiver.get_fan_speed()) + print("incline from UDP (b c): ", incline_value) + #self.check_new_incline(incline_value) #check if tilt value has changed or is still the same - except Exception as e: - print("Error: ", e) + except Exception as e: + print("Error: ", e) - print("udp loop finish") - isRunningUDP = False - isRunningBT = True + print("udp loop finish") + isRunningUDP = False + isRunningBT = True # async def udp_handler_listen(self): # print("open udp socket") @@ -85,8 +93,10 @@ def send_steering_data_udp(self, steering_data): def receive_incline_data_udp(self): udp_incline_data = 0 + # Set up the UDP socket with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.listen(str(udp_incline_data).encode(), (self.udp_ip_to_master_collector, self.receive_from_collector_port)) + # Bind the socket to the IP and port + udp_socket.bind((self.udp_ip_to_master_collector, self.receive_from_collector_port)) print("Hello: ", udp_incline_data) self.incline_value = udp_incline_data @@ -275,8 +285,8 @@ async def async_init(self): # raise async def main(): - ble_async_init_task = asyncio.create_task(ble.async_init()) - await ble_async_init_task + #ble_async_init_task = asyncio.create_task(ble.async_init()) + #await ble_async_init_task #ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) print("ble main started") From 4a00952bc392bf65b4e82a4f00caa893cf00db8f Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 28 Jun 2024 01:55:47 +0200 Subject: [PATCH 53/65] still working --- collector_scripts/UDP_tester.py | 34 +++--- collector_scripts/elite_rizer3.py | 16 ++- collector_scripts/elite_rizer4.py | 189 ++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 23 deletions(-) create mode 100644 collector_scripts/elite_rizer4.py diff --git a/collector_scripts/UDP_tester.py b/collector_scripts/UDP_tester.py index 16844fe..e130950 100644 --- a/collector_scripts/UDP_tester.py +++ b/collector_scripts/UDP_tester.py @@ -1,18 +1,18 @@ -import socket - -def send_udp_data(ip, port, data): - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Konvertiere die Ganzzahl in Bytes - data_bytes = str(data).encode() - udp_socket.sendto(data_bytes, (ip, port)) - print(f"Gesendete Nachricht: {data} an {ip}:{port}") - -# Beispiel für die Nutzung der Funktion -if __name__ == "__main__": - # Eingaben vom Benutzer einholen - ip = input("Geben Sie die IP-Adresse des Empfängers ein: ") - port = int(input("Geben Sie den Port des Empfängers ein: ")) - data = int(input("Geben Sie die zu sendende Ganzzahl ein: ")) - - # UDP-Daten senden +import socket + +def send_udp_data(ip, port, data): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Konvertiere die Ganzzahl in Bytes + data_bytes = str(data).encode() + udp_socket.sendto(data_bytes, (ip, port)) + print(f"Gesendete Nachricht: {data} an {ip}:{port}") + +# Beispiel für die Nutzung der Funktion +if __name__ == "__main__": + # Eingaben vom Benutzer einholen + ip = input("Geben Sie die IP-Adresse des Empfängers ein: ") + port = int(input("Geben Sie den Port des Empfängers ein: ")) + data = int(input("Geben Sie die zu sendende Ganzzahl ein: ")) + + # UDP-Daten senden send_udp_data(ip, port, data) \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py index 0c328b6..89232c8 100644 --- a/collector_scripts/elite_rizer3.py +++ b/collector_scripts/elite_rizer3.py @@ -8,7 +8,7 @@ global incline_received #received tilt data form UDP. Ready to send over BLE global steering_received #received steering data from RIZER. Ready to send over UDP global incline_value -global current_tilt_value_on_razer # current incline from RAZER to compute 0.5 steps fo reach desired incline +global current_tilt_value_on_razer #current incline from RAZER to compute 0.5 steps fo reach desired incline client = None asyncio_sleep = 3 steering_ready_to_send = 0 @@ -85,13 +85,11 @@ def get_incline_received(self): #send steering data over udp def send_steering_data_udp(self, steering_data): - # Create a UDP socket - #print("send steering data: ", steering_data) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data + # Send steering_data udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) - def receive_incline_data_udp(self): + async def receive_incline_data_udp(self): udp_incline_data = 0 # Set up the UDP socket with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: @@ -223,7 +221,11 @@ async def write_incline(self): print("tilt writed, x ", x) except Exception as e: print("Error: ", e) - + + async def write_numbers(self): + for el in range(999999999999999): + print(el) + await asyncio.sleep(1) async def notify_steering_callback(self, sender, data): data = bytearray(data) @@ -292,9 +294,11 @@ async def main(): print("ble main started") udp_handler_task = asyncio.create_task(udp.main()) print("udp main start") + numnbers_task = asyncio.create_task(ble.write_numbers()) # udp_listener_task = asyncio.create_task(udp.udp_handler_listen()) #await ble_handler_task await udp_handler_task #TODO + await numnbers_task # print("start udp listener") # await udp_listener_task diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py new file mode 100644 index 0000000..9c14338 --- /dev/null +++ b/collector_scripts/elite_rizer4.py @@ -0,0 +1,189 @@ +import asyncio +from bleak import BleakClient, exc +import socket +import time + +class Rizer: + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" + + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering + SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + + INCREASE_INCLINE_HEX = "060102" + DECREASE_INCLINE_HEX = "060402" + + CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering + CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + + + def __init__(self) -> None: + # UDP initialization + self.received_steering_data = 0 # Initialize with None or any default value + self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC + # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi + self.udp_port = 2222 + + async def main(self): + # create UDP socket to receive data from master collector + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.bind((self.udp_ip_from_master_collector, self.receive_from_collector_port)) + self.connect_rizer() #connect to the Rizer + + while(True): + # Receive data from the socket + udp_incline_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + sender_ip, sender_port = addr # Extract the sender's IP and port from addr + print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + if self.check_new_incline(udp_incline_data.decode()): #check if the incline value has changed + await self.write_incline() #write the new incline value to the Rizer + await self.read_steering() + await self.send_steering_data_udp(self.steering_data) + + + + +#---------------------------UDP functions-------------------------------- + #send steering data over udp + def send_steering_data_udp(self, steering_data): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Send steering_data + udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) + + #receive incline data over udp + async def receive_incline_data_udp(self): + udp_incline_data = 0 + # Set up the UDP socket + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Bind the socket to the IP and port + udp_socket.bind((self.udp_ip_to_master_collector, self.receive_from_collector_port)) + print("Hello: ", udp_incline_data) + self.incline_value = udp_incline_data + + + +#---------------------------BLE functions-------------------------------- + + #function to initialize the BLE connection with the Rizer + async def connect_rizer(self): + global client + print("start async init") + while not self.client_is_connected: + try: + client = BleakClient(self.DEVICE_UUID, timeout=90) + print("try to connect") + await client.connect() + self.client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + self.steering_service = service + print("[service uuid] ", self.steering_service.uuid) + + if (self.steering_service != ""): + # print("SERVICE", SERVICE) + for characteristic in self.steering_service.characteristics: + + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.steering_characteristics = characteristic + # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) + + #print("steering ready", self.steering_ready) + self.steering_ready = 1 + + if (service.uuid == self.SERVICE_INCLINE_UUID): + self.incline_service = service + print("[service uuid] ", self.incline_service.uuid) + + if (self.incline_service != ""): + for characteristic in self.incline_service.characteristics: + + print("characteristics UUID: ", characteristic.uuid) + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): + self.incline_characteristics = characteristic + self.incline_ready = 1 + + if (self.steering_ready == 1 and self.incline_ready == 1): + print("all ready!") + self.init_ack = True + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + + async def write_incline(self): + global client + global current_tilt_value_on_razer + global incline_value + incline_different = abs(current_tilt_value_on_razer, incline_value) #absolute different of old and new incline value + print("incline_different", incline_different) + + if(current_tilt_value_on_razer + incline_value > 0): + for x in range (incline_different): + try: + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + current_tilt_value_on_razer += 1 + self.incline_received = 0 + print("tilt writed") + except Exception as e: + print("Error: ", e) + else: + for x in range (incline_different): + try: + await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + current_tilt_value_on_razer += -1 + self.incline_received = 0 + print("tilt writed, x ", x) + except Exception as e: + print("Error: ", e) + + #read steering from the Rizer + async def read_steering(self): + data = bytearray(8) + sender = 0 + try: + await client.start_notify(self.steering_characteristics, self.notify_steering_callback) + # Access the notification data using data argument + #print(f"Steering data: {sender}") + #print(f"Steering data: {data}") + await asyncio.sleep(0.5) # keeps the connection open for 10 seconds + await client.stop_notify(self.steering_characteristics.uuid) + except Exception as e: + print("Error: ", e) + + + +#---------------------------Utility functions-------------------------------- + def set_incline_value(self, incline): + self.incline_value = incline + + def get_incline_value(self): + return self.incline_value + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + #we get this random values who are -1; 0 or 1 + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + self.send_steering_data_udp(self.received_steering_data) + + #check if the incline value has changed + def check_new_incline(self, new_inline_udp): + print("RIZER incline: ", new_inline_udp) + if self.get_incline_value != new_inline_udp: + self.set_incline_value(new_inline_udp) + return True + else: + return False + From 713670b43891d0571682f67a1680b71c7a9e6a2c Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 1 Jul 2024 13:39:43 +0200 Subject: [PATCH 54/65] incline works. steering method call is wrong --- collector_scripts/elite_rizer4.py | 52 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py index 9c14338..5d39a4d 100644 --- a/collector_scripts/elite_rizer4.py +++ b/collector_scripts/elite_rizer4.py @@ -17,40 +17,51 @@ class Rizer: CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt + #UDP constant + UDP_IP_TO_MASTER_COLLECTOR = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + UDP_IP_FROM_MASTER_COLLECOTOR = "127.0.0.3" + udp_port = 2222 + RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2223 + + def __init__(self) -> None: # UDP initialization self.received_steering_data = 0 # Initialize with None or any default value + self.client_is_connected = False + + + self.current_incline_on_rizer = int(0) self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi self.udp_port = 2222 + print("init") async def main(self): + print("main") # create UDP socket to receive data from master collector with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.bind((self.udp_ip_from_master_collector, self.receive_from_collector_port)) - self.connect_rizer() #connect to the Rizer + udp_socket.bind((self.UDP_IP_FROM_MASTER_COLLECOTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + await self.connect_rizer() #connect to the Rizer while(True): # Receive data from the socket udp_incline_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes sender_ip, sender_port = addr # Extract the sender's IP and port from addr print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + if self.check_new_incline(udp_incline_data.decode()): #check if the incline value has changed await self.write_incline() #write the new incline value to the Rizer await self.read_steering() - await self.send_steering_data_udp(self.steering_data) - - #---------------------------UDP functions-------------------------------- #send steering data over udp def send_steering_data_udp(self, steering_data): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: # Send steering_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) + udp_socket.sendto(str(steering_data).encode(), (self.UDP_IP_TO_MASTER_COLLECTOR, self.udp_port)) #receive incline data over udp async def receive_incline_data_udp(self): @@ -58,7 +69,7 @@ async def receive_incline_data_udp(self): # Set up the UDP socket with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: # Bind the socket to the IP and port - udp_socket.bind((self.udp_ip_to_master_collector, self.receive_from_collector_port)) + udp_socket.bind((self.UDP_IP_TO_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) print("Hello: ", udp_incline_data) self.incline_value = udp_incline_data @@ -114,29 +125,30 @@ async def connect_rizer(self): # Add additional error handling or logging as needed # raise + #write incline to rizer over ble and store the value of his state async def write_incline(self): global client - global current_tilt_value_on_razer - global incline_value - incline_different = abs(current_tilt_value_on_razer, incline_value) #absolute different of old and new incline value + incline = self.get_incline_value() + incline_different_temp = int(self.current_incline_on_rizer) + int(incline) #absolute different of old and new incline value + incline_different = abs(incline_different_temp) print("incline_different", incline_different) - if(current_tilt_value_on_razer + incline_value > 0): + if(int(self.current_incline_on_rizer) + int(incline) > 0): for x in range (incline_different): try: await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += 1 - self.incline_received = 0 - print("tilt writed") + self.current_incline_on_rizer += 1 + #self.incline_received = 0 + print("tilt writed, x ", x) except Exception as e: print("Error: ", e) else: for x in range (incline_different): try: await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += -1 - self.incline_received = 0 - print("tilt writed, x ", x) + self.current_incline_on_rizer += -1 + #self.incline_received = 0 + print("tilt writed, x -", x) except Exception as e: print("Error: ", e) @@ -149,7 +161,7 @@ async def read_steering(self): # Access the notification data using data argument #print(f"Steering data: {sender}") #print(f"Steering data: {data}") - await asyncio.sleep(0.5) # keeps the connection open for 10 seconds + await asyncio.sleep(0.1) # keeps the connection open for 10 seconds await client.stop_notify(self.steering_characteristics.uuid) except Exception as e: print("Error: ", e) @@ -167,7 +179,7 @@ async def notify_steering_callback(self, sender, data): data = bytearray(data) steering = 0.0 - #we get this random values who are -1; 0 or 1 + #we get this random values who stands for -1; 0 or 1 if data[3] == 65: steering = 1.0 elif data[3] == 193: @@ -187,3 +199,5 @@ def check_new_incline(self, new_inline_udp): else: return False +rizer = Rizer() +asyncio.run(rizer.main()) \ No newline at end of file From d932922613d1156b3608a82830be0615e7fe21ae Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 1 Jul 2024 17:06:08 +0200 Subject: [PATCH 55/65] rizer is working --- collector_scripts/UDP_tester.py | 15 ++++---- collector_scripts/direto_xr.py | 4 +-- collector_scripts/elite_rizer4.py | 46 ++++++++++++++----------- collector_scripts/master_collector.py | 49 ++------------------------- collector_scripts/master_receiver.py | 39 +++++++++++++++++++++ collector_scripts/run_scripts.bat | 2 +- 6 files changed, 79 insertions(+), 76 deletions(-) create mode 100644 collector_scripts/master_receiver.py diff --git a/collector_scripts/UDP_tester.py b/collector_scripts/UDP_tester.py index e130950..4d45055 100644 --- a/collector_scripts/UDP_tester.py +++ b/collector_scripts/UDP_tester.py @@ -10,9 +10,12 @@ def send_udp_data(ip, port, data): # Beispiel für die Nutzung der Funktion if __name__ == "__main__": # Eingaben vom Benutzer einholen - ip = input("Geben Sie die IP-Adresse des Empfängers ein: ") - port = int(input("Geben Sie den Port des Empfängers ein: ")) - data = int(input("Geben Sie die zu sendende Ganzzahl ein: ")) - - # UDP-Daten senden - send_udp_data(ip, port, data) \ No newline at end of file + if input("rizer? ") == ("y"): + incline = int(input("incline ")) + send_udp_data("127.0.0.3", 2223, incline) + else: + ip = input("Geben Sie die IP-Adresse des Empfängers ein: ") + port = int(input("Geben Sie den Port des Empfängers ein: ")) + data = int(input("Geben Sie die zu sendende Ganzzahl ein: ")) + # UDP-Daten senden + send_udp_data(ip, port, data) \ No newline at end of file diff --git a/collector_scripts/direto_xr.py b/collector_scripts/direto_xr.py index f081223..04ce1c9 100644 --- a/collector_scripts/direto_xr.py +++ b/collector_scripts/direto_xr.py @@ -2,9 +2,7 @@ from bleak import BleakScanner, BleakClient, exc import struct import sys -import socket -from dataReceiverSingleton import DataReceiverSingleton - +import socket device_name = "DIRETO XR" # Replace with the name of your desired BLE device DEVICE = "" diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py index 5d39a4d..7077622 100644 --- a/collector_scripts/elite_rizer4.py +++ b/collector_scripts/elite_rizer4.py @@ -2,6 +2,7 @@ from bleak import BleakClient, exc import socket import time +import json class Rizer: #BLE constant @@ -19,7 +20,7 @@ class Rizer: #UDP constant UDP_IP_TO_MASTER_COLLECTOR = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - UDP_IP_FROM_MASTER_COLLECOTOR = "127.0.0.3" + UDP_IP_FROM_MASTER_COLLECTOR = "127.0.0.3" udp_port = 2222 RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2223 @@ -27,7 +28,7 @@ class Rizer: def __init__(self) -> None: # UDP initialization - self.received_steering_data = 0 # Initialize with None or any default value + self.received_steering_data = 0 # Initialize with None or any default value self.client_is_connected = False @@ -42,17 +43,22 @@ async def main(self): print("main") # create UDP socket to receive data from master collector with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.bind((self.UDP_IP_FROM_MASTER_COLLECOTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + udp_socket.bind((self.UDP_IP_FROM_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) await self.connect_rizer() #connect to the Rizer while(True): # Receive data from the socket + print("wait for udp") udp_incline_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes sender_ip, sender_port = addr # Extract the sender's IP and port from addr print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") - - if self.check_new_incline(udp_incline_data.decode()): #check if the incline value has changed + + incline_value = json.loads(udp_incline_data.decode()) + incline_value = int(incline_value["rizerIncline"]) + if self.check_new_incline(incline_value): #check if the incline value has changed await self.write_incline() #write the new incline value to the Rizer + + print("want to read steering! =(") await self.read_steering() @@ -76,19 +82,17 @@ async def receive_incline_data_udp(self): #---------------------------BLE functions-------------------------------- - #function to initialize the BLE connection with the Rizer async def connect_rizer(self): - global client print("start async init") while not self.client_is_connected: try: - client = BleakClient(self.DEVICE_UUID, timeout=90) + self.client = BleakClient(self.DEVICE_UUID, timeout=90) print("try to connect") - await client.connect() + await self.client.connect() self.client_is_connected = True print("Client connected to ", self.DEVICE_UUID) - for service in client.services: + for service in self.client.services: if (service.uuid == self.SERVICE_STEERING_UUID): self.steering_service = service print("[service uuid] ", self.steering_service.uuid) @@ -127,16 +131,20 @@ async def connect_rizer(self): #write incline to rizer over ble and store the value of his state async def write_incline(self): - global client incline = self.get_incline_value() - incline_different_temp = int(self.current_incline_on_rizer) + int(incline) #absolute different of old and new incline value + incline = min(int(incline), 40) + incline = max(int(incline), -20) + incline = int(incline) + print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) + incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute different of old and new incline value incline_different = abs(incline_different_temp) print("incline_different", incline_different) + # print("Incline: ", incline, " current Incline: ", current_incline_on_rizer) - if(int(self.current_incline_on_rizer) + int(incline) > 0): + if((int(incline) - int(self.current_incline_on_rizer)) > 0): for x in range (incline_different): try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) self.current_incline_on_rizer += 1 #self.incline_received = 0 print("tilt writed, x ", x) @@ -145,7 +153,7 @@ async def write_incline(self): else: for x in range (incline_different): try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) self.current_incline_on_rizer += -1 #self.incline_received = 0 print("tilt writed, x -", x) @@ -157,12 +165,12 @@ async def read_steering(self): data = bytearray(8) sender = 0 try: - await client.start_notify(self.steering_characteristics, self.notify_steering_callback) + await self.client.start_notify(self.steering_characteristics, self.notify_steering_callback) # Access the notification data using data argument - #print(f"Steering data: {sender}") - #print(f"Steering data: {data}") + # print(f"Steering data: {sender}") + # print(f"Steering data: {data}") await asyncio.sleep(0.1) # keeps the connection open for 10 seconds - await client.stop_notify(self.steering_characteristics.uuid) + await self.client.stop_notify(self.steering_characteristics.uuid) except Exception as e: print("Error: ", e) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index e907457..d1b90bf 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -62,17 +62,13 @@ def __init__(self): self.send_to_rizer_port = 2223 self.send_to_headwind_port = 2224 self.send_to_direto_port = 2225 - self.udp_unity_receive_socket = None - self.main_loop() - - # self.udp_unity_receive_ip = "127.0.0.1" - # self.udp_unity_receive_port = 12345 def set_ble_fan_speed(self, fan_speed): self.ble_fan_speed = fan_speed def set_ble_incline(self, incline_data): self.ble_incline = incline_data + print("Self incline data: ", self.ble_incline) def get_fan_speed(self): #global ble_fan_speed @@ -87,47 +83,6 @@ def get_resistance(self): #global ble_resistance print("Self ble incline: ", self.ble_resistance) return self.ble_resistance - - def main_loop(self): - print("start main loop master collector") - for x in range -20, 40: - self.send_udp_data_to_rizer(x) - time.sleep(2) - - - # UDP socket to receive data from Unity - def open_udp_socket(self): - # Create a UDP socket - udp_unity_receive_ip = "127.0.0.1" - udp_unity_receive_port = 12345 - - self.udp_unity_receive_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.udp_unity_receive_socket.bind((udp_unity_receive_ip, udp_unity_receive_port)) - - print("Listening for UDP data...") - - def start_udp_listener(self): - - # Infinite loop to continuously receive data - try: - data_receiver = DataReceiver() - data, addr = self.udp_unity_receive_socket.recvfrom(1024) # Buffer size is 1024 bytes - - #self.udp_unity_receive_socket.setblocking(False) - - json_data = data.decode('utf-8') # Decode bytes to string - # value = json.loads(data.decode()) - unity_values = json.loads(json_data) - self.ble_fan_value = unity_values["bleFan"] - self.ble_incline_value = unity_values["bleIncline"] - # print("ble fan from unity: ", ble_fan_value) - print("incline from unity: ", self.ble_incline_value) - data_receiver.set_ble_fan_speed(self.ble_fan_value) - data_receiver.set_ble_incline(self.ble_incline_value) - print("incline mastercollector", data_receiver.get_incline()) - #self.ble_incline = self.ble_incline_value #maybe ble_xx_value is not nessesary and we can use the self.ble_xx directly - except Exception as e: - print(f"Error while receiving UDP data: {e}") def send_udp_data_to_rizer(self, incline_data): # Create a dictionary with the required parameters @@ -224,7 +179,7 @@ def stop_udp_listener(self): # print("udp_direto_socket: ", udp_direto_socket) data_sender.send_unity_data_udp(data_sender.speed_value, data_sender.steering_value, data_sender.brake_value, data_sender.bno_value, data_sender.roll_value) readable, _, _ = select.select([udp_rizer_socket, udp_direto_socket, udp_brake_socket, udp_bno_socket, udp_roll_socket], [], []) - + for sock in readable: data, addr = sock.recvfrom(1024) if sock is udp_rizer_socket: diff --git a/collector_scripts/master_receiver.py b/collector_scripts/master_receiver.py new file mode 100644 index 0000000..d67c7ef --- /dev/null +++ b/collector_scripts/master_receiver.py @@ -0,0 +1,39 @@ +from master_collector import DataReceiver +import json +import socket +import select + + +if __name__ == "__main__": + print("Master Receiver started...") + data_receiver = DataReceiver() + + # Create a UDP socket + udp_unity_receive_ip = "127.0.0.1" + udp_unity_receive_port = 12345 + + udp_unity_receive_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udp_unity_receive_socket.bind((udp_unity_receive_ip, udp_unity_receive_port)) + + while True: + print("Listening for UDP data...") + readable, _, _ = select.select([udp_unity_receive_socket], [], []) + for sock in readable: + try: + data, addr = sock.recvfrom(1024) + if sock is udp_unity_receive_socket: + data = json.loads(data.decode()) + unity_ble_fan_speed = data["bleFan"] + unity_ble_incline = data["bleIncline"] + unity_ble_resistance = data["bleResistance"] + print(unity_ble_incline, unity_ble_fan_speed) + data_receiver.send_udp_data_to_rizer(unity_ble_incline) + data_receiver.send_udp_data_to_headwind(unity_ble_fan_speed) + data_receiver.send_udp_data_to_direto(unity_ble_resistance) + + except Exception as e: + print(f"Error while receiving UDP data: {e}") + + + + diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 5b37699..5f192a7 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -6,7 +6,7 @@ set pre_execution_script="p110_connect.py" ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 REM Set the list of Python scripts to start -set python_scripts=("elite_rizer3.py", "master_collector.py") +set python_scripts=("direto_xr.py", "elite_rizer4.py", "master_collector.py", "master_receiver.py") REM "direto_xr.py", "headwind.py", REM Start the pre-execution script From 855857d23156bb98d066ac6ba79a40b9dde841ca Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 1 Jul 2024 19:12:29 +0200 Subject: [PATCH 56/65] seems everything is working and has to be tested in unity --- collector_scripts/UDP_tester.py | 18 ++++- collector_scripts/direto_xr.py | 38 +++++++---- collector_scripts/elite_rizer4.py | 5 +- collector_scripts/headwind.py | 98 ++++++++++++--------------- collector_scripts/master_collector.py | 4 +- collector_scripts/run_scripts.bat | 2 +- 6 files changed, 90 insertions(+), 75 deletions(-) diff --git a/collector_scripts/UDP_tester.py b/collector_scripts/UDP_tester.py index 4d45055..4f78066 100644 --- a/collector_scripts/UDP_tester.py +++ b/collector_scripts/UDP_tester.py @@ -1,4 +1,5 @@ import socket +import json def send_udp_data(ip, port, data): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: @@ -7,6 +8,21 @@ def send_udp_data(ip, port, data): udp_socket.sendto(data_bytes, (ip, port)) print(f"Gesendete Nachricht: {data} an {ip}:{port}") +def send_udp_json_data(ip, port, data): + + json_data = { + "diretoResistance": float(data), + } + print(json_data) + # Convert dictionary to JSON string + json_data = json.dumps(json_data) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + # Konvertiere die Ganzzahl in Bytes + data_bytes = str(json_data).encode() + udp_socket.sendto(data_bytes, (ip, port)) + print(f"Gesendete Nachricht: {data} an {ip}:{port}") + + # Beispiel für die Nutzung der Funktion if __name__ == "__main__": # Eingaben vom Benutzer einholen @@ -18,4 +34,4 @@ def send_udp_data(ip, port, data): port = int(input("Geben Sie den Port des Empfängers ein: ")) data = int(input("Geben Sie die zu sendende Ganzzahl ein: ")) # UDP-Daten senden - send_udp_data(ip, port, data) \ No newline at end of file + send_udp_json_data(ip, port, data) \ No newline at end of file diff --git a/collector_scripts/direto_xr.py b/collector_scripts/direto_xr.py index 04ce1c9..1176e2e 100644 --- a/collector_scripts/direto_xr.py +++ b/collector_scripts/direto_xr.py @@ -3,6 +3,7 @@ import struct import sys import socket +import json device_name = "DIRETO XR" # Replace with the name of your desired BLE device DEVICE = "" @@ -13,10 +14,12 @@ characteristic_resistance_uuid = "00002ad9-0000-1000-8000-00805f9b34fb" # Write Resistance characteristic_speed_uuid = "00002ad2-0000-1000-8000-00805f9b34fb" # Read Speed - CHARACTERISTIC_RESISTANCE = "" CHARACTERISTIC_SPEED = "" +UDP_IP_FROM_MASTER_COLLECTOR = "127.0.0.3" +RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2225 + class BluetoothCallback: def __init__(self): @@ -62,22 +65,23 @@ def normalize_speed_value(value, min_val, max_val): return normalized_value -async def write_resistance(client, characteristic): +async def write_resistance(client, characteristic, resistance_value): bluetooth_callback = BluetoothCallback() try: await client.start_notify(characteristic, bluetooth_callback.notify_resistance_callback) # characteristic.uuid except Exception as e: print("Error: ", e) # resistance_input = input("Enter a value between 1-100 to set the resistance level. (or 'x' to exit): ") - resistance_input = 60 - resistance_value = int(resistance_input) + resistance_value = int(resistance_value) try: - if 1 <= resistance_value <= 100: - await client.write_gatt_char(characteristic, bytearray([0x04, resistance_value])) - elif resistance_input.lower() == 'x': - await client.stop_notify(characteristic) # characteristic.uuid - await asyncio.sleep(1) - sys.exit() + #if 1 <= resistance_value <= 100: + resistance_value = min(resistance_value, 100) + resistance_value = max(resistance_value, 0) + await client.write_gatt_char(characteristic, bytearray([0x04, resistance_value])) + # elif resistance_value.lower() == 'x': + # await client.stop_notify(characteristic) # characteristic.uuid + # await asyncio.sleep(1) + # sys.exit() except ValueError: print("Invalid input. Please enter a number between 1 and 100.") # print("Test resistance") @@ -156,10 +160,16 @@ def callback(device, advertising_data): if("notify" in characteristic.properties and characteristic.uuid == characteristic_speed_uuid): CHARACTERISTIC_SPEED = characteristic # print("Characteristic speed: ",CHARACTERISTIC_SPEED) - - while True: - await write_resistance(client, CHARACTERISTIC_RESISTANCE) - await read_speed(client, CHARACTERISTIC_SPEED) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.bind((UDP_IP_FROM_MASTER_COLLECTOR, RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + while True: + resistance_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + sender_ip, sender_port = addr # Extract the sender's IP and port from addr + print(f"Received message: {resistance_data.decode()} from {sender_ip}:{sender_port}") + resistance_data = json.loads(resistance_data.decode()) + resistance_value = int(resistance_data["diretoResistance"]) + await write_resistance(client, CHARACTERISTIC_RESISTANCE, resistance_value) + await read_speed(client, CHARACTERISTIC_SPEED) except exc.BleakError as e: print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") # Add additional error handling or logging as needed diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py index 7077622..acb7c2a 100644 --- a/collector_scripts/elite_rizer4.py +++ b/collector_scripts/elite_rizer4.py @@ -55,11 +55,10 @@ async def main(self): incline_value = json.loads(udp_incline_data.decode()) incline_value = int(incline_value["rizerIncline"]) - if self.check_new_incline(incline_value): #check if the incline value has changed + if self.check_new_incline(incline_value): #check if the incline value has changed await self.write_incline() #write the new incline value to the Rizer - print("want to read steering! =(") - await self.read_steering() + await self.read_steering() #read steering over BLE #---------------------------UDP functions-------------------------------- diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 6b5d1d0..191134a 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -1,6 +1,9 @@ import asyncio from bleak import BleakScanner, BleakClient, exc from master_collector import DataReceiver +import socket +import json +import select class BluetoothCallback(): def __init__(self): @@ -21,6 +24,9 @@ async def notify_callback(self, sender, data): characteristic_uuid = "a026e038-0a7d-4ab3-97fa-f1500f9feb8b" CHARACTERISTIC = "" +UDP_IP_FROM_MASTER_COLLECTOR = "127.0.0.3" +RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2224 + async def scan_and_connect_headwind(): global device_name @@ -34,13 +40,16 @@ async def scan_and_connect_headwind(): global old_value global is_first_entry - global run_read_loop + global run_read_loop + + speed_value = 0 stop_event = asyncio.Event() # Scanning and printing for BLE devices def callback(device, advertising_data): - global DEVICEID + global DEVICEID + print(device) if(device.name == device_name): DEVICEID = device @@ -83,58 +92,39 @@ def callback(device, advertising_data): bluetooth_callback = BluetoothCallback() #receiver.open_udp_socket() - while True: - - try: - print("headwind: try") - #receiver.start_udp_listener() - # print("FAN SPEED: ", receiver.get_fan_speed()) - speed_value = DataReceiver.ble_fan_speed - print("incline: ", DataReceiver.get_ble_incline()) - print("Fan Speed: ", DataReceiver.get_ble_fan_speed()) - print("Fan Speed: ", speed_value) - print("get ble incline headwind", DataReceiver.get_ble_incline()) - except Exception as e: - print("Error: ", e) - try: - await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid - except Exception as e: - print("Error: ", e) - - - ''' - try: - if speed_value > 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - elif speed_value <= 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) # Turn fan off - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Error turning fan on or off.") - - try: - if speed_value > 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") - - ''' - try: - if 2 <= speed_value <= 100: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) - print(f"Fan speed set to {speed_value}") - elif speed_value == 1: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) - elif speed_value == 0: - await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) - else: - print("Speed value should be between 1 and 100.") - except ValueError: - print("Invalid input. Please enter a number between 1 and 100.") + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.bind((UDP_IP_FROM_MASTER_COLLECTOR, RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + while True: + try: + ble_fan_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + sender_ip, sender_port = addr # Extract the sender's IP and port from addr + print(f"Received message: {ble_fan_data.decode()} from {sender_ip}:{sender_port}") + print("ble fan data: ", ble_fan_data) + ble_fan_value = json.loads(ble_fan_data.decode()) + print("decoded fan value ", ble_fan_value) + speed_value = int(ble_fan_value["fanSpeed"]) + + print("ble_fan_value: ", ble_fan_value) + print("value: ", speed_value) + except Exception as e: + print("Error: ", e) + try: + await client.start_notify(CHARACTERISTIC, bluetooth_callback.notify_callback) # characteristic.uuid + except Exception as e: + print("Error: ", e) + + try: + if 2 <= speed_value <= 100: #TODO we have to write 1 before we can write some other values + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x02, speed_value])) + print(f"Fan speed set to {speed_value}") + elif speed_value == 1: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x04])) # Turns fan on -> bytearray([0x04, 0x04]), Turns fan off -> bytearray([0x04, 0x01]), Adjust fan Speed -> bytearray([0x02, ]) + elif speed_value == 0: + await client.write_gatt_char(CHARACTERISTIC, bytearray([0x04, 0x01])) + else: + print("Speed value should be between 1 and 100.") + except ValueError: + print("Invalid input. Please enter a number between 1 and 100.") except exc.BleakError as e: print(f"Failed to connect/discover services of {DEVICEID.name}: {e}") diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index d1b90bf..2e9e2ee 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -81,7 +81,7 @@ def get_incline(self): def get_resistance(self): #global ble_resistance - print("Self ble incline: ", self.ble_resistance) + print("Self ble resistance: ", self.ble_resistance) return self.ble_resistance def send_udp_data_to_rizer(self, incline_data): @@ -116,7 +116,7 @@ def send_udp_data_to_headwind(self, fan_speed): def send_udp_data_to_direto(self, resistance_data): # Create a dictionary with the required parameters data = { - "fanSpeed": float(resistance_data), + "diretoResistance": float(resistance_data), } print(data) # Convert dictionary to JSON string diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 5f192a7..7468915 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -6,7 +6,7 @@ set pre_execution_script="p110_connect.py" ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "elite_rizer4.py", "master_collector.py", "master_receiver.py") +set python_scripts=("direto_xr.py", "headwind.py", "elite_rizer4.py", "master_collector.py", "master_receiver.py") REM "direto_xr.py", "headwind.py", REM Start the pre-execution script From 82e8f60ea17b45e78c285e3f122a33285538c2fd Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Wed, 3 Jul 2024 10:10:23 +0200 Subject: [PATCH 57/65] working on rizer delay --- collector_scripts/direto_xr.py | 28 +++++------ collector_scripts/elite_rizer4.py | 81 +++++++++++++++++++------------ collector_scripts/headwind.py | 4 ++ 3 files changed, 66 insertions(+), 47 deletions(-) diff --git a/collector_scripts/direto_xr.py b/collector_scripts/direto_xr.py index 1176e2e..5fb8419 100644 --- a/collector_scripts/direto_xr.py +++ b/collector_scripts/direto_xr.py @@ -4,6 +4,7 @@ import sys import socket import json +import time device_name = "DIRETO XR" # Replace with the name of your desired BLE device DEVICE = "" @@ -113,6 +114,8 @@ async def scan_and_connect_direto(): global CHARACTERISTIC_RESISTANCE global CHARACTERISTIC_SPEED + resistance_value = 0 + stop_event = asyncio.Event() # Scanning and printing for BLE devices @@ -125,16 +128,7 @@ def callback(device, advertising_data): stop_event.set() # Stops the scanning event - async with BleakScanner(callback) as scanner: - # new - ''' - try: - await stop_event.wait() - except KeyboardInterrupt: - print("Scanning stopped by user.") - scanner.stop() - # new end - ''' + async with BleakScanner(callback) as scanner: await stop_event.wait() if(DEVICEID != ""): @@ -162,12 +156,16 @@ def callback(device, advertising_data): # print("Characteristic speed: ",CHARACTERISTIC_SPEED) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: udp_socket.bind((UDP_IP_FROM_MASTER_COLLECTOR, RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + udp_socket.setblocking(False) while True: - resistance_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - sender_ip, sender_port = addr # Extract the sender's IP and port from addr - print(f"Received message: {resistance_data.decode()} from {sender_ip}:{sender_port}") - resistance_data = json.loads(resistance_data.decode()) - resistance_value = int(resistance_data["diretoResistance"]) + try: + resistance_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes + sender_ip, sender_port = addr # Extract the sender's IP and port from addr + print(f"Received message: {resistance_data.decode()} from {sender_ip}:{sender_port}") + resistance_data = json.loads(resistance_data.decode()) + resistance_value = int(resistance_data["diretoResistance"]) + except BlockingIOError: + time.sleep(0.01) # Small sleep to prevent busy-waiting await write_resistance(client, CHARACTERISTIC_RESISTANCE, resistance_value) await read_speed(client, CHARACTERISTIC_SPEED) except exc.BleakError as e: diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py index acb7c2a..6ad9d6f 100644 --- a/collector_scripts/elite_rizer4.py +++ b/collector_scripts/elite_rizer4.py @@ -24,6 +24,10 @@ class Rizer: udp_port = 2222 RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2223 + _incline_value = 0 + incline_rate = 1 + incline_timer = 0 + def __init__(self) -> None: @@ -41,24 +45,37 @@ def __init__(self) -> None: async def main(self): print("main") + incline_value = 0 + # create UDP socket to receive data from master collector with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: udp_socket.bind((self.UDP_IP_FROM_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) - await self.connect_rizer() #connect to the Rizer - + udp_socket.setblocking(False) # Set the socket to non-blocking mode | without this flag it waits until it gets data + await self.connect_rizer() #connect to the Rizer while(True): # Receive data from the socket - print("wait for udp") - udp_incline_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - sender_ip, sender_port = addr # Extract the sender's IP and port from addr - print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + try: + print("wait for udp") + udp_incline_data, addr = udp_socket.recvfrom(47) #TODO try SO_RCVBUF # Buffer size is 1024 bytes + sender_ip, sender_port = addr # Extract the sender's IP and port from addr + print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + incline_value = json.loads(udp_incline_data.decode()) + incline_value = int(incline_value["rizerIncline"]) + except BlockingIOError: + time.sleep(0.01) # Small sleep to prevent busy-waiting + #time.sleep(1) - incline_value = json.loads(udp_incline_data.decode()) - incline_value = int(incline_value["rizerIncline"]) + # Timer logic + #time.sleep(0.01) + #self.incline_timer += 0.01 # Assuming 100 Hz timer (0.01 seconds per loop iteration) + + #if self.incline_timer >= (1): + #self.incline_timer = 0 # Reset timer + if self.check_new_incline(incline_value): #check if the incline value has changed await self.write_incline() #write the new incline value to the Rizer - await self.read_steering() #read steering over BLE + await self.read_steering() #read steering over BLE ONLY WORKING with master_collector script!!! #---------------------------UDP functions-------------------------------- @@ -131,8 +148,6 @@ async def connect_rizer(self): #write incline to rizer over ble and store the value of his state async def write_incline(self): incline = self.get_incline_value() - incline = min(int(incline), 40) - incline = max(int(incline), -20) incline = int(incline) print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute different of old and new incline value @@ -141,23 +156,23 @@ async def write_incline(self): # print("Incline: ", incline, " current Incline: ", current_incline_on_rizer) if((int(incline) - int(self.current_incline_on_rizer)) > 0): - for x in range (incline_different): - try: - await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - self.current_incline_on_rizer += 1 - #self.incline_received = 0 - print("tilt writed, x ", x) - except Exception as e: - print("Error: ", e) + #for x in range (incline_different): + try: + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + self.current_incline_on_rizer += 1 + #self.incline_received = 0 + #print("tilt writed, x ", x) + except Exception as e: + print("Error: ", e) else: - for x in range (incline_different): - try: - await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) - self.current_incline_on_rizer += -1 - #self.incline_received = 0 - print("tilt writed, x -", x) - except Exception as e: - print("Error: ", e) + #for x in range (incline_different): + try: + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + self.current_incline_on_rizer += -1 + #self.incline_received = 0 + #print("tilt writed, x -", x) + except Exception as e: + print("Error: ", e) #read steering from the Rizer async def read_steering(self): @@ -177,10 +192,12 @@ async def read_steering(self): #---------------------------Utility functions-------------------------------- def set_incline_value(self, incline): - self.incline_value = incline + incline = min(int(incline), 40) + incline = max(int(incline), -20) + self._incline_value = incline def get_incline_value(self): - return self.incline_value + return self._incline_value async def notify_steering_callback(self, sender, data): data = bytearray(data) @@ -199,12 +216,12 @@ async def notify_steering_callback(self, sender, data): #check if the incline value has changed def check_new_incline(self, new_inline_udp): - print("RIZER incline: ", new_inline_udp) - if self.get_incline_value != new_inline_udp: + if self.get_incline_value() != new_inline_udp: self.set_incline_value(new_inline_udp) + print("incline value write BLE: ", self.get_incline_value()) return True else: - return False + return False rizer = Rizer() asyncio.run(rizer.main()) \ No newline at end of file diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 191134a..3463911 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -4,6 +4,7 @@ import socket import json import select +import time class BluetoothCallback(): def __init__(self): @@ -94,6 +95,7 @@ def callback(device, advertising_data): #receiver.open_udp_socket() with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: udp_socket.bind((UDP_IP_FROM_MASTER_COLLECTOR, RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + udp_socket.setblocking(False) #without this flag it waits until it gets data while True: try: ble_fan_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes @@ -106,6 +108,8 @@ def callback(device, advertising_data): print("ble_fan_value: ", ble_fan_value) print("value: ", speed_value) + except BlockingIOError: + time.sleep(0.01) # Small sleep to prevent busy-waiting except Exception as e: print("Error: ", e) try: From c2284e4605ffdacfc91c3bf0fc2213438d16b7a6 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Wed, 3 Jul 2024 13:19:24 +0200 Subject: [PATCH 58/65] works until to fast changes appear --- collector_scripts/elite_rizer4.py | 44 +++++++++++++------------------ 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py index 6ad9d6f..9588497 100644 --- a/collector_scripts/elite_rizer4.py +++ b/collector_scripts/elite_rizer4.py @@ -63,14 +63,6 @@ async def main(self): incline_value = int(incline_value["rizerIncline"]) except BlockingIOError: time.sleep(0.01) # Small sleep to prevent busy-waiting - #time.sleep(1) - - # Timer logic - #time.sleep(0.01) - #self.incline_timer += 0.01 # Assuming 100 Hz timer (0.01 seconds per loop iteration) - - #if self.incline_timer >= (1): - #self.incline_timer = 0 # Reset timer if self.check_new_incline(incline_value): #check if the incline value has changed await self.write_incline() #write the new incline value to the Rizer @@ -150,29 +142,29 @@ async def write_incline(self): incline = self.get_incline_value() incline = int(incline) print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) - incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute different of old and new incline value + incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute difference of old and new incline value incline_different = abs(incline_different_temp) - print("incline_different", incline_different) + print("incline_difference: ", incline_different) # print("Incline: ", incline, " current Incline: ", current_incline_on_rizer) if((int(incline) - int(self.current_incline_on_rizer)) > 0): - #for x in range (incline_different): - try: - await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - self.current_incline_on_rizer += 1 - #self.incline_received = 0 - #print("tilt writed, x ", x) - except Exception as e: - print("Error: ", e) + for x in range (incline_different): + try: + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + self.current_incline_on_rizer += 1 + #self.incline_received = 0 + print("tilt writed, x ", x) + except Exception as e: + print("Error: ", e) else: - #for x in range (incline_different): - try: - await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) - self.current_incline_on_rizer += -1 - #self.incline_received = 0 - #print("tilt writed, x -", x) - except Exception as e: - print("Error: ", e) + for x in range (incline_different): + try: + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + self.current_incline_on_rizer += -1 + #self.incline_received = 0 + print("tilt writed, x -", x) + except Exception as e: + print("Error: ", e) #read steering from the Rizer async def read_steering(self): From 384ea1dfbf7240e95ba46b3fb94b0bb3263f8fd8 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Wed, 3 Jul 2024 15:18:29 +0200 Subject: [PATCH 59/65] seems everything works! --- collector_scripts/elite_rizer4.py | 43 +++++++++++++++++++------------ 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py index 9588497..20a92fd 100644 --- a/collector_scripts/elite_rizer4.py +++ b/collector_scripts/elite_rizer4.py @@ -55,15 +55,25 @@ async def main(self): while(True): # Receive data from the socket try: - print("wait for udp") - udp_incline_data, addr = udp_socket.recvfrom(47) #TODO try SO_RCVBUF # Buffer size is 1024 bytes - sender_ip, sender_port = addr # Extract the sender's IP and port from addr - print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") - incline_value = json.loads(udp_incline_data.decode()) - incline_value = int(incline_value["rizerIncline"]) + udp_incline_data = 0 + while True: + try: + udp_incline_data, addr = udp_socket.recvfrom(47) + sender_ip, sender_port = addr + print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + incline_value = json.loads(udp_incline_data.decode()) + incline_value = int(incline_value["rizerIncline"]) # Extract the sender's IP and port from addr + except BlockingIOError: + break + + except BlockingIOError: time.sleep(0.01) # Small sleep to prevent busy-waiting + + + await self.write_incline() #write the new incline value to the Rizer + self.set_incline_value(incline_value) if self.check_new_incline(incline_value): #check if the incline value has changed await self.write_incline() #write the new incline value to the Rizer @@ -141,28 +151,28 @@ async def connect_rizer(self): async def write_incline(self): incline = self.get_incline_value() incline = int(incline) - print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) - incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute difference of old and new incline value - incline_different = abs(incline_different_temp) - print("incline_difference: ", incline_different) + #print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) + #incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute difference of old and new incline value + #incline_different = abs(incline_different_temp) + #print("incline_difference: ", incline_different) # print("Incline: ", incline, " current Incline: ", current_incline_on_rizer) if((int(incline) - int(self.current_incline_on_rizer)) > 0): - for x in range (incline_different): + #for x in range (incline_different): try: await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) self.current_incline_on_rizer += 1 #self.incline_received = 0 - print("tilt writed, x ", x) + #print("incline written, x ", x) except Exception as e: print("Error: ", e) else: - for x in range (incline_different): + #for x in range (incline_different): try: await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) self.current_incline_on_rizer += -1 #self.incline_received = 0 - print("tilt writed, x -", x) + #print("incline written, x -", x) except Exception as e: print("Error: ", e) @@ -206,10 +216,9 @@ async def notify_steering_callback(self, sender, data): self.send_steering_data_udp(self.received_steering_data) - #check if the incline value has changed + #check if rizer alredy arrived position def check_new_incline(self, new_inline_udp): - if self.get_incline_value() != new_inline_udp: - self.set_incline_value(new_inline_udp) + if self.current_incline_on_rizer != new_inline_udp: print("incline value write BLE: ", self.get_incline_value()) return True else: From 8452405df4424abbdb9537b4f2e67001edfa02a9 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Wed, 3 Jul 2024 15:54:53 +0200 Subject: [PATCH 60/65] everything is working! UDP Buffer bug is now fixed in headwind and direto to --- collector_scripts/UDP_tester.py | 2 +- collector_scripts/direto_xr.py | 16 +++++++++++----- collector_scripts/headwind.py | 25 +++++++++++++++---------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/collector_scripts/UDP_tester.py b/collector_scripts/UDP_tester.py index 4f78066..ba889a9 100644 --- a/collector_scripts/UDP_tester.py +++ b/collector_scripts/UDP_tester.py @@ -11,7 +11,7 @@ def send_udp_data(ip, port, data): def send_udp_json_data(ip, port, data): json_data = { - "diretoResistance": float(data), + "fanSpeed": float(data), } print(json_data) # Convert dictionary to JSON string diff --git a/collector_scripts/direto_xr.py b/collector_scripts/direto_xr.py index 5fb8419..516abb6 100644 --- a/collector_scripts/direto_xr.py +++ b/collector_scripts/direto_xr.py @@ -78,6 +78,7 @@ async def write_resistance(client, characteristic, resistance_value): #if 1 <= resistance_value <= 100: resistance_value = min(resistance_value, 100) resistance_value = max(resistance_value, 0) + print("write Resistance: ", resistance_value) await client.write_gatt_char(characteristic, bytearray([0x04, resistance_value])) # elif resistance_value.lower() == 'x': # await client.stop_notify(characteristic) # characteristic.uuid @@ -159,11 +160,16 @@ def callback(device, advertising_data): udp_socket.setblocking(False) while True: try: - resistance_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - sender_ip, sender_port = addr # Extract the sender's IP and port from addr - print(f"Received message: {resistance_data.decode()} from {sender_ip}:{sender_port}") - resistance_data = json.loads(resistance_data.decode()) - resistance_value = int(resistance_data["diretoResistance"]) + while True: + try: + resistance_data, addr = udp_socket.recvfrom(47) + sender_ip, sender_port = addr + print(f"Received message: {resistance_data.decode()} from {sender_ip}:{sender_port}") + resistance_data = json.loads(resistance_data.decode()) + resistance_value = int(resistance_data["diretoResistance"]) # Extract the sender's IP and port from addr + except BlockingIOError: + break + except BlockingIOError: time.sleep(0.01) # Small sleep to prevent busy-waiting await write_resistance(client, CHARACTERISTIC_RESISTANCE, resistance_value) diff --git a/collector_scripts/headwind.py b/collector_scripts/headwind.py index 3463911..da906bb 100644 --- a/collector_scripts/headwind.py +++ b/collector_scripts/headwind.py @@ -98,16 +98,21 @@ def callback(device, advertising_data): udp_socket.setblocking(False) #without this flag it waits until it gets data while True: try: - ble_fan_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - sender_ip, sender_port = addr # Extract the sender's IP and port from addr - print(f"Received message: {ble_fan_data.decode()} from {sender_ip}:{sender_port}") - print("ble fan data: ", ble_fan_data) - ble_fan_value = json.loads(ble_fan_data.decode()) - print("decoded fan value ", ble_fan_value) - speed_value = int(ble_fan_value["fanSpeed"]) - - print("ble_fan_value: ", ble_fan_value) - print("value: ", speed_value) + while True: + try: + ble_fan_data, addr = udp_socket.recvfrom(47) + sender_ip, sender_port = addr + print(f"Received message: {ble_fan_data.decode()} from {sender_ip}:{sender_port}") # Extract the sender's IP and port from addr + print("ble fan data: ", ble_fan_data) + ble_fan_value = json.loads(ble_fan_data.decode()) + print("decoded fan value ", ble_fan_value) + speed_value = int(ble_fan_value["fanSpeed"]) + + print("ble_fan_value: ", ble_fan_value) + print("value: ", speed_value) + except BlockingIOError: + break + except BlockingIOError: time.sleep(0.01) # Small sleep to prevent busy-waiting except Exception as e: From 464755af2410c668f57fe539528c0de5fb7e294c Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 28 Aug 2024 20:01:40 +0200 Subject: [PATCH 61/65] deleted unnecessary code --- collector_scripts/elite_rizer.py | 325 ++++++++++++++++----------- collector_scripts/elite_rizer2.py | 90 -------- collector_scripts/elite_rizer3.py | 312 ------------------------- collector_scripts/elite_rizer4.py | 228 ------------------- collector_scripts/elite_rizer_old.py | 152 ------------- 5 files changed, 189 insertions(+), 918 deletions(-) delete mode 100644 collector_scripts/elite_rizer2.py delete mode 100644 collector_scripts/elite_rizer3.py delete mode 100644 collector_scripts/elite_rizer4.py delete mode 100644 collector_scripts/elite_rizer_old.py diff --git a/collector_scripts/elite_rizer.py b/collector_scripts/elite_rizer.py index 8088d12..20a92fd 100644 --- a/collector_scripts/elite_rizer.py +++ b/collector_scripts/elite_rizer.py @@ -2,174 +2,227 @@ from bleak import BleakClient, exc import socket import time -from master_collector import DataReceiver +import json -DEVICE_NAME = "RIZER" -DEVICE_UUID = "fc:12:65:28:cb:44" +class Rizer: + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" -SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering + SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" -INCREASE_TILT_HEX = "060102" -DECREASE_TILT_HEX = "060402" + INCREASE_INCLINE_HEX = "060102" + DECREASE_INCLINE_HEX = "060402" -steering_service = "" -tilt_service = "" + CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering + CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt -stering_ready = 0 -tilt_ready = 0 + #UDP constant + UDP_IP_TO_MASTER_COLLECTOR = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost + UDP_IP_FROM_MASTER_COLLECTOR = "127.0.0.3" + udp_port = 2222 + RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2223 -stored_tilt_value = 0 + _incline_value = 0 + incline_rate = 1 + incline_timer = 0 -CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt -steering_characteristics = "" -tilt_characteristics = "" + def __init__(self) -> None: + # UDP initialization + self.received_steering_data = 0 # Initialize with None or any default value + self.client_is_connected = False -class BluetoothCallback: - def __init__(self): - self.received_steering_data = 0 # Initialize with None or any default value + self.current_incline_on_rizer = int(0) self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi self.udp_port = 2222 - + print("init") - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print(self.received_steering_data) - - self.send_steering_data_udp(self.received_steering_data) + async def main(self): + print("main") + incline_value = 0 + # create UDP socket to receive data from master collector + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: + udp_socket.bind((self.UDP_IP_FROM_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + udp_socket.setblocking(False) # Set the socket to non-blocking mode | without this flag it waits until it gets data + await self.connect_rizer() #connect to the Rizer + while(True): + # Receive data from the socket + try: + udp_incline_data = 0 + while True: + try: + udp_incline_data, addr = udp_socket.recvfrom(47) + sender_ip, sender_port = addr + print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") + incline_value = json.loads(udp_incline_data.decode()) + incline_value = int(incline_value["rizerIncline"]) # Extract the sender's IP and port from addr + except BlockingIOError: + break + + + except BlockingIOError: + time.sleep(0.01) # Small sleep to prevent busy-waiting + + + await self.write_incline() #write the new incline value to the Rizer + + self.set_incline_value(incline_value) + if self.check_new_incline(incline_value): #check if the incline value has changed + await self.write_incline() #write the new incline value to the Rizer + + await self.read_steering() #read steering over BLE ONLY WORKING with master_collector script!!! + + +#---------------------------UDP functions-------------------------------- #send steering data over udp def send_steering_data_udp(self, steering_data): - # Create a UDP socket - # print(steering_data) with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) + # Send steering_data + udp_socket.sendto(str(steering_data).encode(), (self.UDP_IP_TO_MASTER_COLLECTOR, self.udp_port)) - def listening_udp(self): + #receive incline data over udp + async def receive_incline_data_udp(self): + udp_incline_data = 0 + # Set up the UDP socket with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.listen(str(udp_tilt_data).encode(), (self.udp_ip, self.udp_port)) - print("Hello: ", udp_tilt_data) - -async def read_steering(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - # print("Test steering") - except Exception as e: - print("Error: ", e) - -async def write_tilt(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - #TODO Write bt stuff - print("BT stuff") - await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) - stored_tilt_value += 0.5 - except Exception as e: - print("Error: ", e) - -# check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) -def get_new_tilt_value(): - receiver = DataReceiver() - try: - receiver.start_udp_listener() - tilt_value = receiver.get_tilt() - print("RIZER tilt: ", tilt_value) - if tilt_value != stored_tilt_value: - stored_tilt_value = tilt_value - - except Exception as e: - print("Error: ", e) - - -async def scan_and_connect_rizer(): - global DEVICE_NAME - - global SERVICE_STEERING_UUID - - global steering_service - global tilt_service - - global CHARACTERISTICS_STEERING_UUID - global steering_characteristics - global tilt_characteristics - - global stering_ready #connection to steering BLE service ready - global tilt_ready #connection to tilt BLE service ready - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - #print("Client connected to ", DEVICE_ID.name) - #print("Client connected to ", DEVICE_ID) - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): + # Bind the socket to the IP and port + udp_socket.bind((self.UDP_IP_TO_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) + print("Hello: ", udp_incline_data) + self.incline_value = udp_incline_data + + + +#---------------------------BLE functions-------------------------------- + #function to initialize the BLE connection with the Rizer + async def connect_rizer(self): + print("start async init") + while not self.client_is_connected: + try: + self.client = BleakClient(self.DEVICE_UUID, timeout=90) + print("try to connect") + await self.client.connect() + self.client_is_connected = True + print("Client connected to ", self.DEVICE_UUID) + for service in self.client.services: + if (service.uuid == self.SERVICE_STEERING_UUID): + self.steering_service = service + print("[service uuid] ", self.steering_service.uuid) + + if (self.steering_service != ""): # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: + for characteristic in self.steering_service.characteristics: - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEERING_UUID): - steering_characteristics = characteristic + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.steering_characteristics = characteristic # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - print(stering_ready) - stering_ready = 1 + #print("steering ready", self.steering_ready) + self.steering_ready = 1 - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) + if (service.uuid == self.SERVICE_INCLINE_UUID): + self.incline_service = service + print("[service uuid] ", self.incline_service.uuid) - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: + if (self.incline_service != ""): + for characteristic in self.incline_service.characteristics: print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 + if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): + self.incline_characteristics = characteristic + self.incline_ready = 1 - while (stering_ready == 1 and tilt_ready == 1): + if (self.steering_ready == 1 and self.incline_ready == 1): print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print(tilt_characteristics) - print("read and write!") - + self.init_ack = True + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + # Add additional error handling or logging as needed + # raise + + #write incline to rizer over ble and store the value of his state + async def write_incline(self): + incline = self.get_incline_value() + incline = int(incline) + #print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) + #incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute difference of old and new incline value + #incline_different = abs(incline_different_temp) + #print("incline_difference: ", incline_different) + # print("Incline: ", incline, " current Incline: ", current_incline_on_rizer) + + if((int(incline) - int(self.current_incline_on_rizer)) > 0): + #for x in range (incline_different): + try: + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) + self.current_incline_on_rizer += 1 + #self.incline_received = 0 + #print("incline written, x ", x) + except Exception as e: + print("Error: ", e) + else: + #for x in range (incline_different): + try: + await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) + self.current_incline_on_rizer += -1 + #self.incline_received = 0 + #print("incline written, x -", x) + except Exception as e: + print("Error: ", e) + + #read steering from the Rizer + async def read_steering(self): + data = bytearray(8) + sender = 0 + try: + await self.client.start_notify(self.steering_characteristics, self.notify_steering_callback) + # Access the notification data using data argument + # print(f"Steering data: {sender}") + # print(f"Steering data: {data}") + await asyncio.sleep(0.1) # keeps the connection open for 10 seconds + await self.client.stop_notify(self.steering_characteristics.uuid) + except Exception as e: + print("Error: ", e) + - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise -asyncio.run(scan_and_connect_rizer()) +#---------------------------Utility functions-------------------------------- + def set_incline_value(self, incline): + incline = min(int(incline), 40) + incline = max(int(incline), -20) + self._incline_value = incline -'create UDP_Handler in own task' \ No newline at end of file + def get_incline_value(self): + return self._incline_value + + async def notify_steering_callback(self, sender, data): + data = bytearray(data) + steering = 0.0 + + #we get this random values who stands for -1; 0 or 1 + if data[3] == 65: + steering = 1.0 + elif data[3] == 193: + steering = -1.0 + + self.received_steering_data = steering + print(self.received_steering_data) + + self.send_steering_data_udp(self.received_steering_data) + + #check if rizer alredy arrived position + def check_new_incline(self, new_inline_udp): + if self.current_incline_on_rizer != new_inline_udp: + print("incline value write BLE: ", self.get_incline_value()) + return True + else: + return False + +rizer = Rizer() +asyncio.run(rizer.main()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer2.py b/collector_scripts/elite_rizer2.py deleted file mode 100644 index d3604aa..0000000 --- a/collector_scripts/elite_rizer2.py +++ /dev/null @@ -1,90 +0,0 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time -from master_collector import DataReceiver - -#Global BLE variables -steering_service = "" -tilt_service = "" - -steering_characteristics = "" -tilt_characteristics = "" - -stering_ready = 0 -tilt_ready = 0 - -stored_tilt_value = 0 - - -async def scan_and_connect_rizer(): - - #BLE constant - DEVICE_NAME = "RIZER" - DEVICE_UUID = "fc:12:65:28:cb:44" - - SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering - SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - - INCREASE_TILT_HEX = "060102" - DECREASE_TILT_HEX = "060402" - - CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(stering_ready) - stering_ready = 1 - - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - while (stering_ready == 1 and tilt_ready == 1): - print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print(tilt_characteristics) - print("read and write!") - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - -asyncio.run(scan_and_connect_rizer()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer3.py b/collector_scripts/elite_rizer3.py deleted file mode 100644 index 89232c8..0000000 --- a/collector_scripts/elite_rizer3.py +++ /dev/null @@ -1,312 +0,0 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time -from master_collector import DataReceiver - -#global variables -global incline_received #received tilt data form UDP. Ready to send over BLE -global steering_received #received steering data from RIZER. Ready to send over UDP -global incline_value -global current_tilt_value_on_razer #current incline from RAZER to compute 0.5 steps fo reach desired incline -client = None -asyncio_sleep = 3 -steering_ready_to_send = 0 - - -class UDP_Handler: - - def __init__(self): - global incline_value - global incline_received - global isRunningBT - global isRunningUDP - - incline_value = 0 - incline_received = 0 - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip_to_master_collector = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - self.udp_ip_from_master_collector = "127.0.0.3" - self.udp_port = 2222 - self.receive_from_collector_port = 2223 - print("udp handler started") - - - async def main(self): - global incline_value - global steering_received - global asyncio_sleep - global isRunningUDP - global isRunningBT - - isRunningUDP = True - self.steering_data = None - steering_received = None - udp_incline_data = 0 - #print("rizer id: ", id(receiver)) - # Set up the UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.bind((self.udp_ip_from_master_collector, self.receive_from_collector_port)) - while(True): - await asyncio.sleep(asyncio_sleep) - print("udp main") - print("start listener") - # Receive data from the socket - udp_incline_data, addr = udp_socket.recvfrom(1024) # Buffer size is 1024 bytes - sender_ip, sender_port = addr # Extract the sender's IP and port from addr - print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") - self.incline_value = udp_incline_data.decode() - if (steering_received == 1): #when steering value has chanched, send it to unity - print("send steering data") - await self.send_steering_data_udp(self.steering_data) - try: - #self.receiver._receiver.stop_udp_listener() - #print("fan speed (b c)", self.receiver.get_fan_speed()) - print("incline from UDP (b c): ", incline_value) - #self.check_new_incline(incline_value) #check if tilt value has changed or is still the same - - except Exception as e: - print("Error: ", e) - - print("udp loop finish") - isRunningUDP = False - isRunningBT = True - - # async def udp_handler_listen(self): - # print("open udp socket") - # self.receiver.open_udp_socket() - - - def set_incline_received(self, received): - self.incline_received = received - - def get_incline_received(self): - return self.incline_received - - #send steering data over udp - def send_steering_data_udp(self, steering_data): - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send steering_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip_to_master_collector, self.udp_port)) - - async def receive_incline_data_udp(self): - udp_incline_data = 0 - # Set up the UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Bind the socket to the IP and port - udp_socket.bind((self.udp_ip_to_master_collector, self.receive_from_collector_port)) - print("Hello: ", udp_incline_data) - self.incline_value = udp_incline_data - - - # check if the value of the tilt in unity is the same as on the rizer (currently not possible to check the value. just to store the changes) - def check_new_incline(self, udp_incline_value): - global incline_value - global incline_received - print("RIZER incline: ", udp_incline_value) - if incline_value != udp_incline_value: - incline_value = udp_incline_value - incline_received = 1 - -class BLE_Handler: - #BLE constant - DEVICE_NAME = "RIZER" - DEVICE_UUID = "fc:12:65:28:cb:44" - - SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering - SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - - INCREASE_INCLINE_HEX = "060102" - DECREASE_INCLINE_HEX = "060402" - - CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - global steering_characteristics - global tilt_characteristics - - global steering_service - global tilt_service - - global isRunningUDP - global isRunningBT - - global current_tilt_value_on_razer # current position of RIZER (save ervery change for verification) - - def __init__(self): - global steering_characteristics - global tilt_characteristics - - global steering_service - global tilt_service - - global current_tilt_value_on_razer - - #global steering_ready #connection to steering BLE service ready - #global tilt_ready #connection to tilt BLE service ready - - #global client_is_connected - #global client_is_connected - - # Connecting to BLE Device - print("Connecting to BLE Device") - self.client_is_connected = False - self.steering_ready = 0 - self.incline_ready = 0 - self.tilt_received = 0 - self.steering_characteristics = None - self.incline_characteristics = 0 - self.init_ack = False - - current_tilt_value_on_razer = 0 - - #main function for BLE Handler - async def read_and_ride_rizer(self): - global incline_received - global client - global isRunningBT - global isRunningUDP - print("read and write") - while(True): - await asyncio.sleep(asyncio_sleep) - print("udp handler sleep") - if (self.init_ack == True): - await self.read_steering() - print("read steering rizer") - if (incline_received == 1): - print("write incline") - await self.write_incline(self) - else: - print("wait init ack") - isRunningBT = False - isRunningUDP = True - - - async def read_steering(self): - data = bytearray(8) - sender = 0 - await asyncio.sleep(asyncio_sleep) - try: - await client.start_notify(self.steering_characteristics, self.notify_steering_callback) - # Access the notification data using data argument - #print(f"Steering data: {sender}") - #print(f"Steering data: {data}") - await asyncio.sleep(0.5) # keeps the connection open for 10 seconds - await client.stop_notify(self.steering_characteristics.uuid) - except Exception as e: - print("Error: ", e) - - async def write_incline(self): - global client - global current_tilt_value_on_razer - global incline_value - incline_different = abs(current_tilt_value_on_razer, incline_value) #absolute different of old and new incline value - print("incline_different", incline_different) - - if(current_tilt_value_on_razer + incline_value > 0): - for x in range (incline_different): - try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += 1 - self.incline_received = 0 - print("tilt writed") - except Exception as e: - print("Error: ", e) - else: - for x in range (incline_different): - try: - await client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) - current_tilt_value_on_razer += -1 - self.incline_received = 0 - print("tilt writed, x ", x) - except Exception as e: - print("Error: ", e) - - async def write_numbers(self): - for el in range(999999999999999): - print(el) - await asyncio.sleep(1) - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print("readed steering: ", self.received_steering_data) - udp.send_steering_data_udp(self.received_steering_data) - - async def async_init(self): - global client - print("start async init") - while not self.client_is_connected: - try: - client = BleakClient(self.DEVICE_UUID, timeout=90) - print("try to connect") - await client.connect() - self.client_is_connected = True - print("Client connected to ", self.DEVICE_UUID) - for service in client.services: - if (service.uuid == self.SERVICE_STEERING_UUID): - self.steering_service = service - print("[service uuid] ", self.steering_service.uuid) - - if (self.steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in self.steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): - self.steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - - #print("steering ready", self.steering_ready) - self.steering_ready = 1 - - if (service.uuid == self.SERVICE_INCLINE_UUID): - self.incline_service = service - print("[service uuid] ", self.incline_service.uuid) - - if (self.incline_service != ""): - for characteristic in self.incline_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): - self.incline_characteristics = characteristic - self.incline_ready = 1 - - if (self.steering_ready == 1 and self.incline_ready == 1): - print("all ready!") - self.init_ack = True - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - -async def main(): - #ble_async_init_task = asyncio.create_task(ble.async_init()) - #await ble_async_init_task - - #ble_handler_task = asyncio.create_task(ble.read_and_ride_rizer()) - print("ble main started") - udp_handler_task = asyncio.create_task(udp.main()) - print("udp main start") - numnbers_task = asyncio.create_task(ble.write_numbers()) -# udp_listener_task = asyncio.create_task(udp.udp_handler_listen()) - #await ble_handler_task - await udp_handler_task #TODO - await numnbers_task - # print("start udp listener") - # await udp_listener_task - -# Creating instances of handlers -udp = UDP_Handler() -ble = BLE_Handler() - -# Call async_init right after the instance is created -#asyncio.run(ble.async_init()) - -asyncio.run(main()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer4.py b/collector_scripts/elite_rizer4.py deleted file mode 100644 index 20a92fd..0000000 --- a/collector_scripts/elite_rizer4.py +++ /dev/null @@ -1,228 +0,0 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time -import json - -class Rizer: - #BLE constant - DEVICE_NAME = "RIZER" - DEVICE_UUID = "fc:12:65:28:cb:44" - - SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering - SERVICE_INCLINE_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - - INCREASE_INCLINE_HEX = "060102" - DECREASE_INCLINE_HEX = "060402" - - CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering - CHARACTERISTIC_INCLINE_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - #UDP constant - UDP_IP_TO_MASTER_COLLECTOR = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - UDP_IP_FROM_MASTER_COLLECTOR = "127.0.0.3" - udp_port = 2222 - RECEIVE_FROM_MASTER_COLLECTOR_PORT = 2223 - - _incline_value = 0 - incline_rate = 1 - incline_timer = 0 - - - - def __init__(self) -> None: - # UDP initialization - self.received_steering_data = 0 # Initialize with None or any default value - self.client_is_connected = False - - - self.current_incline_on_rizer = int(0) - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC - # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi - self.udp_port = 2222 - print("init") - - async def main(self): - print("main") - incline_value = 0 - - # create UDP socket to receive data from master collector - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - udp_socket.bind((self.UDP_IP_FROM_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) - udp_socket.setblocking(False) # Set the socket to non-blocking mode | without this flag it waits until it gets data - await self.connect_rizer() #connect to the Rizer - while(True): - # Receive data from the socket - try: - udp_incline_data = 0 - while True: - try: - udp_incline_data, addr = udp_socket.recvfrom(47) - sender_ip, sender_port = addr - print(f"Received message: {udp_incline_data.decode()} from {sender_ip}:{sender_port}") - incline_value = json.loads(udp_incline_data.decode()) - incline_value = int(incline_value["rizerIncline"]) # Extract the sender's IP and port from addr - except BlockingIOError: - break - - - except BlockingIOError: - time.sleep(0.01) # Small sleep to prevent busy-waiting - - - await self.write_incline() #write the new incline value to the Rizer - - self.set_incline_value(incline_value) - if self.check_new_incline(incline_value): #check if the incline value has changed - await self.write_incline() #write the new incline value to the Rizer - - await self.read_steering() #read steering over BLE ONLY WORKING with master_collector script!!! - - -#---------------------------UDP functions-------------------------------- - #send steering data over udp - def send_steering_data_udp(self, steering_data): - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send steering_data - udp_socket.sendto(str(steering_data).encode(), (self.UDP_IP_TO_MASTER_COLLECTOR, self.udp_port)) - - #receive incline data over udp - async def receive_incline_data_udp(self): - udp_incline_data = 0 - # Set up the UDP socket - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Bind the socket to the IP and port - udp_socket.bind((self.UDP_IP_TO_MASTER_COLLECTOR, self.RECEIVE_FROM_MASTER_COLLECTOR_PORT)) - print("Hello: ", udp_incline_data) - self.incline_value = udp_incline_data - - - -#---------------------------BLE functions-------------------------------- - #function to initialize the BLE connection with the Rizer - async def connect_rizer(self): - print("start async init") - while not self.client_is_connected: - try: - self.client = BleakClient(self.DEVICE_UUID, timeout=90) - print("try to connect") - await self.client.connect() - self.client_is_connected = True - print("Client connected to ", self.DEVICE_UUID) - for service in self.client.services: - if (service.uuid == self.SERVICE_STEERING_UUID): - self.steering_service = service - print("[service uuid] ", self.steering_service.uuid) - - if (self.steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in self.steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): - self.steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - - #print("steering ready", self.steering_ready) - self.steering_ready = 1 - - if (service.uuid == self.SERVICE_INCLINE_UUID): - self.incline_service = service - print("[service uuid] ", self.incline_service.uuid) - - if (self.incline_service != ""): - for characteristic in self.incline_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == self.CHARACTERISTIC_INCLINE_UUID): - self.incline_characteristics = characteristic - self.incline_ready = 1 - - if (self.steering_ready == 1 and self.incline_ready == 1): - print("all ready!") - self.init_ack = True - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - - #write incline to rizer over ble and store the value of his state - async def write_incline(self): - incline = self.get_incline_value() - incline = int(incline) - #print("current incline: ", int(self.current_incline_on_rizer), " Incline: ", int(incline) ) - #incline_different_temp = int(incline) - int(self.current_incline_on_rizer) #absolute difference of old and new incline value - #incline_different = abs(incline_different_temp) - #print("incline_difference: ", incline_different) - # print("Incline: ", incline, " current Incline: ", current_incline_on_rizer) - - if((int(incline) - int(self.current_incline_on_rizer)) > 0): - #for x in range (incline_different): - try: - await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.INCREASE_INCLINE_HEX), response=True) - self.current_incline_on_rizer += 1 - #self.incline_received = 0 - #print("incline written, x ", x) - except Exception as e: - print("Error: ", e) - else: - #for x in range (incline_different): - try: - await self.client.write_gatt_char(self.CHARACTERISTIC_INCLINE_UUID, bytes.fromhex(self.DECREASE_INCLINE_HEX), response=True) - self.current_incline_on_rizer += -1 - #self.incline_received = 0 - #print("incline written, x -", x) - except Exception as e: - print("Error: ", e) - - #read steering from the Rizer - async def read_steering(self): - data = bytearray(8) - sender = 0 - try: - await self.client.start_notify(self.steering_characteristics, self.notify_steering_callback) - # Access the notification data using data argument - # print(f"Steering data: {sender}") - # print(f"Steering data: {data}") - await asyncio.sleep(0.1) # keeps the connection open for 10 seconds - await self.client.stop_notify(self.steering_characteristics.uuid) - except Exception as e: - print("Error: ", e) - - - -#---------------------------Utility functions-------------------------------- - def set_incline_value(self, incline): - incline = min(int(incline), 40) - incline = max(int(incline), -20) - self._incline_value = incline - - def get_incline_value(self): - return self._incline_value - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - #we get this random values who stands for -1; 0 or 1 - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print(self.received_steering_data) - - self.send_steering_data_udp(self.received_steering_data) - - #check if rizer alredy arrived position - def check_new_incline(self, new_inline_udp): - if self.current_incline_on_rizer != new_inline_udp: - print("incline value write BLE: ", self.get_incline_value()) - return True - else: - return False - -rizer = Rizer() -asyncio.run(rizer.main()) \ No newline at end of file diff --git a/collector_scripts/elite_rizer_old.py b/collector_scripts/elite_rizer_old.py deleted file mode 100644 index 8b2727c..0000000 --- a/collector_scripts/elite_rizer_old.py +++ /dev/null @@ -1,152 +0,0 @@ -import asyncio -from bleak import BleakClient, exc -import socket -import time - -DEVICE_NAME = "RIZER" -DEVICE_UUID = "fc:12:65:28:cb:44" - -SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" # Rizer - read steering -SERVICE_TILT_UUID = "347b0001-7635-408b-8918-8ff3949ce592" - -INCREASE_TILT_HEX = "060102" -DECREASE_TILT_HEX = "060402" - -steering_service = "" -tilt_service = "" - -stering_ready = 0 -tilt_ready = 0 - - -CHARACTERISTICS_STEEING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" # Rizer - read steering -CHARACTERISTIC_TILT_UUID = "347b0020-7635-408b-8918-8ff3949ce592" # write tilt - - -steering_characteristics = "" -tilt_characteristics = "" - - -class BluetoothCallback: - def __init__(self): - self.received_steering_data = 0 # Initialize with None or any default value - self.udp_ip = "127.0.0.1" # Send the rizer data to the master_collector.py script via UDP over localhost - # self.udp_ip = "10.30.77.221" # Ip of the Bicycle Simulator Desktop PC - # self.udp_ip = "192.168.9.184" # IP of the Raspberry Pi - self.udp_port = 2222 - - - async def notify_steering_callback(self, sender, data): - data = bytearray(data) - steering = 0.0 - - if data[3] == 65: - steering = 1.0 - elif data[3] == 193: - steering = -1.0 - - self.received_steering_data = steering - print(self.received_steering_data) - - self.send_steering_data_udp(self.received_steering_data) - - - def send_steering_data_udp(self, steering_data): - # Create a UDP socket - # print(steering_data) - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as udp_socket: - # Send speed_data - udp_socket.sendto(str(steering_data).encode(), (self.udp_ip, self.udp_port)) - -async def read_steering(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - await client.start_notify(characteristic, bluetooth_callback.notify_steering_callback) - await asyncio.sleep(10) # keeps the connection open for 10 seconds - await client.stop_notify(characteristic.uuid) - # print("Test steering") - except Exception as e: - print("Error: ", e) - -async def write_tilt(client, characteristic): - bluetooth_callback = BluetoothCallback() - try: - #TODO Write bt stuff - print("BT stuff") - await client.write_gatt_char(CHARACTERISTIC_TILT_UUID, bytes.fromhex(INCREASE_TILT_HEX), response=True) - except Exception as e: - print("Error: ", e) - - -async def scan_and_connect_rizer(): - global DEVICE_NAME - - global SERVICE_STEERING_UUID - - global steering_service - global tilt_service - - global CHARACTERISTICS_STEEING_UUID - global steering_characteristics - global tilt_characteristics - - global stering_ready - global tilt_ready - - # Connecting to BLE Device - client_is_connected = False - while(client_is_connected == False): - try: - async with BleakClient(DEVICE_UUID, timeout=90) as client: - client_is_connected = True - #print("Client connected to ", DEVICE_ID.name) - #print("Client connected to ", DEVICE_ID) - print("Client connected to ", DEVICE_UUID) - # return True - # logger.info("Device ID ", device_id) - for service in client.services: - # print("service: ", service) - - if (service.uuid == SERVICE_STEERING_UUID): - steering_service = service - print("[service uuid] ", steering_service.uuid) - - if (steering_service != ""): - # print("SERVICE", SERVICE) - for characteristic in steering_service.characteristics: - - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTICS_STEEING_UUID): - steering_characteristics = characteristic - # print("CHARACTERISTIC: ", CHARACTERISTIC_STEERING, characteristic.properties) - print("IF") - - print(stering_ready) - stering_ready = 1 - - if (service.uuid == SERVICE_TILT_UUID): - tilt_service = service - print("[service uuid] ", tilt_service.uuid) - - if (tilt_service != ""): - for characteristic in tilt_service.characteristics: - - print("characteristics UUID: ", characteristic.uuid) - if("notify" in characteristic.properties and characteristic.uuid == CHARACTERISTIC_TILT_UUID): - tilt_characteristics = characteristic - print(tilt_ready) - tilt_ready = 1 - - while (stering_ready == 1 and tilt_ready == 1): - print("all ready!") - await read_steering(client, steering_characteristics) - await write_tilt(client, tilt_characteristics) - print(tilt_characteristics) - print("read and write!") - - - except exc.BleakError as e: - print(f"Failed to connect/discover services of {DEVICE_UUID}: {e}") - # Add additional error handling or logging as needed - # raise - -asyncio.run(scan_and_connect_rizer()) \ No newline at end of file From 6a08da662f24e68f75981ebedbf5538c3e36879d Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 28 Aug 2024 20:10:51 +0200 Subject: [PATCH 62/65] modified run_runscripts to run the right rizer script --- collector_scripts/run_scripts.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector_scripts/run_scripts.bat b/collector_scripts/run_scripts.bat index 7468915..13c8ea9 100644 --- a/collector_scripts/run_scripts.bat +++ b/collector_scripts/run_scripts.bat @@ -6,7 +6,7 @@ set pre_execution_script="p110_connect.py" ping 127.0.0.1 -n 50 > nul REM pings to delay the script5 REM Set the list of Python scripts to start -set python_scripts=("direto_xr.py", "headwind.py", "elite_rizer4.py", "master_collector.py", "master_receiver.py") +set python_scripts=("direto_xr.py", "headwind.py", "elite_rizer.py", "master_collector.py", "master_receiver.py") REM "direto_xr.py", "headwind.py", REM Start the pre-execution script From 58c0924bb6f6ed9da02b7bde18d02c276c071731 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 9 Sep 2024 10:48:29 +0200 Subject: [PATCH 63/65] ble reverse Eng. Comparing Rizer value for different steering angles. --- develop/ble_device_data.json | 410 ++++++++++++++++++++++++++++ develop/compare_json.py | 34 +++ develop/left_ble_device_data.json | 410 ++++++++++++++++++++++++++++ develop/middle_ble_device_data.json | 410 ++++++++++++++++++++++++++++ develop/read_all_rizer.py | 136 +++++++++ develop/right_ble_device_data.json | 410 ++++++++++++++++++++++++++++ 6 files changed, 1810 insertions(+) create mode 100644 develop/ble_device_data.json create mode 100644 develop/compare_json.py create mode 100644 develop/left_ble_device_data.json create mode 100644 develop/middle_ble_device_data.json create mode 100644 develop/read_all_rizer.py create mode 100644 develop/right_ble_device_data.json diff --git a/develop/ble_device_data.json b/develop/ble_device_data.json new file mode 100644 index 0000000..f902479 --- /dev/null +++ b/develop/ble_device_data.json @@ -0,0 +1,410 @@ +{ + "00001800-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a00-0000-1000-8000-00805f9b34fb", + "properties": [ + "read", + "write" + ], + "data": [ + [ + 82, + 73, + 90, + 69, + 82 + ] + ] + }, + { + "uuid": "00002a01-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0 + ] + ] + }, + { + "uuid": "00002a04-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 24, + 0, + 48, + 0, + 0, + 0, + 144, + 1 + ] + ] + }, + { + "uuid": "00002aa6-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 1 + ] + ] + } + ], + "00001801-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a05-0000-1000-8000-00805f9b34fb", + "properties": [ + "indicate" + ], + "data": [], + "error": "Could not read characteristic handle 11: Protocol Error 0x02: Read Not Permitted" + } + ], + "0000180a-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a29-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 69, + 108, + 105, + 116, + 101 + ] + ] + }, + { + "uuid": "00002a25-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 56, + 48, + 57 + ] + ] + }, + { + "uuid": "00002a27-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 51 + ] + ] + }, + { + "uuid": "00002a26-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + }, + { + "uuid": "00002a28-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + } + ], + "0000fe59-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "8ec90003-f315-4f60-9fb8-838830daea50", + "properties": [ + "write", + "indicate" + ], + "data": [ + [] + ] + } + ], + "347b0001-7635-408b-8918-8ff3949ce592": [ + { + "uuid": "347b0012-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 30: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0013-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0014-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0016-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 37: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0017-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0019-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 228, + 1, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0030-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 168, + 166, + 39, + 193 + ] + ] + }, + { + "uuid": "347b0031-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 47: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0032-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + }, + { + "uuid": "347b0022-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 255, + 100, + 129, + 60 + ] + ] + }, + { + "uuid": "347b0020-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 55: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0021-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + } + ] +} \ No newline at end of file diff --git a/develop/compare_json.py b/develop/compare_json.py new file mode 100644 index 0000000..19f7ed8 --- /dev/null +++ b/develop/compare_json.py @@ -0,0 +1,34 @@ +import json + +def compare_data_lists(file1, file2): + with open(file1, 'r') as f1, open(file2, 'r') as f2: + data1 = json.load(f1) + data2 = json.load(f2) + + differences = [] + + for service_uuid, subdata in data1.items(): + + for charac in subdata: + + if (charac["data"] != get_dict_by_uuid(data2[service_uuid], charac["uuid"])["data"]): + + print("Change in JSON found:") + print("Characterisic: ",charac["uuid"]) + print("Steering left:",charac["data"]) + print("Steering middle:", get_dict_by_uuid(data2[service_uuid], charac["uuid"])["data"]) + +def get_dict_by_uuid(characteristics, target_uuid): + return next((char for char in characteristics if char['uuid'] == target_uuid), None) + +# Usage +file1 = "left_ble_device_data.json" +file2 = "middle_ble_device_data.json" + +differences = compare_data_lists(file1, file2) +#print_differences(differences) + + + + + diff --git a/develop/left_ble_device_data.json b/develop/left_ble_device_data.json new file mode 100644 index 0000000..dad3b2e --- /dev/null +++ b/develop/left_ble_device_data.json @@ -0,0 +1,410 @@ +{ + "00001800-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a00-0000-1000-8000-00805f9b34fb", + "properties": [ + "read", + "write" + ], + "data": [ + [ + 82, + 73, + 90, + 69, + 82 + ] + ] + }, + { + "uuid": "00002a01-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0 + ] + ] + }, + { + "uuid": "00002a04-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 24, + 0, + 48, + 0, + 0, + 0, + 144, + 1 + ] + ] + }, + { + "uuid": "00002aa6-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 1 + ] + ] + } + ], + "00001801-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a05-0000-1000-8000-00805f9b34fb", + "properties": [ + "indicate" + ], + "data": [], + "error": "Could not read characteristic handle 11: Protocol Error 0x02: Read Not Permitted" + } + ], + "0000180a-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a29-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 69, + 108, + 105, + 116, + 101 + ] + ] + }, + { + "uuid": "00002a25-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 56, + 48, + 57 + ] + ] + }, + { + "uuid": "00002a27-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 51 + ] + ] + }, + { + "uuid": "00002a26-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + }, + { + "uuid": "00002a28-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + } + ], + "0000fe59-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "8ec90003-f315-4f60-9fb8-838830daea50", + "properties": [ + "write", + "indicate" + ], + "data": [ + [] + ] + } + ], + "347b0001-7635-408b-8918-8ff3949ce592": [ + { + "uuid": "347b0012-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 30: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0013-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0014-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0016-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 37: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0017-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0019-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 228, + 1, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0030-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 32, + 9, + 155, + 193 + ] + ] + }, + { + "uuid": "347b0031-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 47: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0032-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + }, + { + "uuid": "347b0022-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0, + 100, + 129, + 60 + ] + ] + }, + { + "uuid": "347b0020-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 55: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0021-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + } + ] +} \ No newline at end of file diff --git a/develop/middle_ble_device_data.json b/develop/middle_ble_device_data.json new file mode 100644 index 0000000..1e882f6 --- /dev/null +++ b/develop/middle_ble_device_data.json @@ -0,0 +1,410 @@ +{ + "00001800-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a00-0000-1000-8000-00805f9b34fb", + "properties": [ + "read", + "write" + ], + "data": [ + [ + 82, + 73, + 90, + 69, + 82 + ] + ] + }, + { + "uuid": "00002a01-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0 + ] + ] + }, + { + "uuid": "00002a04-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 24, + 0, + 48, + 0, + 0, + 0, + 144, + 1 + ] + ] + }, + { + "uuid": "00002aa6-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 1 + ] + ] + } + ], + "00001801-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a05-0000-1000-8000-00805f9b34fb", + "properties": [ + "indicate" + ], + "data": [], + "error": "Could not read characteristic handle 11: Protocol Error 0x02: Read Not Permitted" + } + ], + "0000180a-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a29-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 69, + 108, + 105, + 116, + 101 + ] + ] + }, + { + "uuid": "00002a25-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 56, + 48, + 57 + ] + ] + }, + { + "uuid": "00002a27-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 51 + ] + ] + }, + { + "uuid": "00002a26-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + }, + { + "uuid": "00002a28-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + } + ], + "0000fe59-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "8ec90003-f315-4f60-9fb8-838830daea50", + "properties": [ + "write", + "indicate" + ], + "data": [ + [] + ] + } + ], + "347b0001-7635-408b-8918-8ff3949ce592": [ + { + "uuid": "347b0012-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 30: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0013-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0014-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0016-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 37: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0017-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0019-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 228, + 1, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0030-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 224, + 231, + 167, + 192 + ] + ] + }, + { + "uuid": "347b0031-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 47: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0032-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + }, + { + "uuid": "347b0022-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0, + 100, + 129, + 60 + ] + ] + }, + { + "uuid": "347b0020-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 55: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0021-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + } + ] +} \ No newline at end of file diff --git a/develop/read_all_rizer.py b/develop/read_all_rizer.py new file mode 100644 index 0000000..f11ac12 --- /dev/null +++ b/develop/read_all_rizer.py @@ -0,0 +1,136 @@ +import asyncio +from bleak import BleakClient, exc, BleakScanner +import socket +import time +import json + +class TestRizer: + + #BLE constant + DEVICE_NAME = "RIZER" + DEVICE_UUID = "fc:12:65:28:cb:44" + + SERVICE_STEERING_UUID = "347b0001-7635-408b-8918-8ff3949ce592" + CHARACTERISTICS_STEERING_UUID = "347b0030-7635-408b-8918-8ff3949ce592" + + client_is_connected = False + listCharacteristic = [] + + listen_data = {} + + +#---------------------------BLE functions-------------------------------- + #function to initialize the BLE connection with the Rizer + async def start_notification(self, characteristic): + print("characteristic: ", characteristic) + if self.client_is_connected: + try: + #for characteristic in self.listCharacteristic: + await self.client.start_notify( + characteristic, + self.on_steering_changed + ) + print("Started notification for steering characteristic") + except Exception as e: + print(f"Failed to start notification: {e}") + else: + print("Not connected to the device") + + async def stop_notification(self): + if self.client_is_connected: + try: + await self.client.stop_notify() + print("Stopped notification for steering characteristic") + except Exception as e: + print(f"Failed to stop notification: {e}") + + + async def on_steering_changed(self, sender, data): + data = bytearray(data) + print(f"") + print(f"Sender {sender}: \n Steering changed: {[byte for byte in data]}") + + async def on_steering_changed_to_json(self, sender, data): + + data = bytearray(data) + print(f"") + print(f"Sender {sender}: \n Steering changed: {[byte for byte in data]}") + + + def save_to_json(self, filename="ble_device_data.json"): + with open(filename, 'w') as f: + json.dump(self.data, f, indent=2) + print(f"Data saved to {filename}") + + async def connect_rizer(self): + print("Start async init") + while not self.client_is_connected: + try: + self.client = BleakClient(self.DEVICE_UUID, timeout=90) + print("Trying to connect") + await self.client.connect() + self.client_is_connected = True + print(f"Client connected to {self.DEVICE_UUID}") + + # Initialize data structure + self.data = {} + + for service in self.client.services: + service_uuid = str(service.uuid) + self.data[service_uuid] = [] + + for characteristic in service.characteristics: + char_uuid = str(characteristic.uuid) + char_data = { + "uuid": char_uuid, + "properties": characteristic.properties, + "data": [] + } + self.data[service_uuid].append(char_data) + + + print(f"=== Trying to read Data for {char_uuid}:") + + try: + value = await self.client.read_gatt_char(characteristic.uuid) + data = bytearray(value) + print(f"=== Data: {[byte for byte in data]}") + + # Store the data + char_data["data"].append([byte for byte in data]) + except exc.BleakError as e: + print(f"=== Failed to read Data!") + char_data["error"] = str(e) + + print("\n") + + except exc.BleakError as e: + print(f"Failed to connect/discover services of {self.DEVICE_UUID}: {e}") + + return self.data + + + + +rizer = TestRizer() + +async def main(): + + config = "middle" + await rizer.connect_rizer() + + rizer.save_to_json(filename= config +"_ble_device_data.json") + + + + ''' + for charc in rizer.listCharacteristic: + + await rizer.start_notification(characteristic = charc) + + # Keep the connection alive for a while + await asyncio.sleep(60) + + await rizer.stop_notification()''' + +asyncio.run(main()) diff --git a/develop/right_ble_device_data.json b/develop/right_ble_device_data.json new file mode 100644 index 0000000..f902479 --- /dev/null +++ b/develop/right_ble_device_data.json @@ -0,0 +1,410 @@ +{ + "00001800-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a00-0000-1000-8000-00805f9b34fb", + "properties": [ + "read", + "write" + ], + "data": [ + [ + 82, + 73, + 90, + 69, + 82 + ] + ] + }, + { + "uuid": "00002a01-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0 + ] + ] + }, + { + "uuid": "00002a04-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 24, + 0, + 48, + 0, + 0, + 0, + 144, + 1 + ] + ] + }, + { + "uuid": "00002aa6-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 1 + ] + ] + } + ], + "00001801-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a05-0000-1000-8000-00805f9b34fb", + "properties": [ + "indicate" + ], + "data": [], + "error": "Could not read characteristic handle 11: Protocol Error 0x02: Read Not Permitted" + } + ], + "0000180a-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "00002a29-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 69, + 108, + 105, + 116, + 101 + ] + ] + }, + { + "uuid": "00002a25-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 56, + 48, + 57 + ] + ] + }, + { + "uuid": "00002a27-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 51 + ] + ] + }, + { + "uuid": "00002a26-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + }, + { + "uuid": "00002a28-0000-1000-8000-00805f9b34fb", + "properties": [ + "read" + ], + "data": [ + [ + 50, + 52 + ] + ] + } + ], + "0000fe59-0000-1000-8000-00805f9b34fb": [ + { + "uuid": "8ec90003-f315-4f60-9fb8-838830daea50", + "properties": [ + "write", + "indicate" + ], + "data": [ + [] + ] + } + ], + "347b0001-7635-408b-8918-8ff3949ce592": [ + { + "uuid": "347b0012-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 30: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0013-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0014-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0016-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 37: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0017-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 0 + ] + ] + }, + { + "uuid": "347b0019-7635-408b-8918-8ff3949ce592", + "properties": [ + "read" + ], + "data": [ + [ + 0, + 228, + 1, + 0, + 0 + ] + ] + }, + { + "uuid": "347b0030-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 168, + 166, + 39, + 193 + ] + ] + }, + { + "uuid": "347b0031-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 47: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0032-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + }, + { + "uuid": "347b0022-7635-408b-8918-8ff3949ce592", + "properties": [ + "notify" + ], + "data": [ + [ + 255, + 100, + 129, + 60 + ] + ] + }, + { + "uuid": "347b0020-7635-408b-8918-8ff3949ce592", + "properties": [ + "write" + ], + "data": [], + "error": "Could not read characteristic handle 55: Protocol Error 0x02: Read Not Permitted" + }, + { + "uuid": "347b0021-7635-408b-8918-8ff3949ce592", + "properties": [ + "indicate" + ], + "data": [ + [] + ] + } + ] +} \ No newline at end of file From 17bbe49a958d1de800912c92d7624493eee97171 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Tue, 10 Sep 2024 10:53:51 +0200 Subject: [PATCH 64/65] Reading Bytes from Rizer in Develop folder --- .../Brake_Sensor/{ => src}/test/README | 22 +- develop/Figure_1.png | Bin 0 -> 325610 bytes develop/plot_json.py | 53 + develop/read_all_rizer.py | 35 +- develop/rizer_data.json | 3638 +++++++++++++++++ 5 files changed, 3725 insertions(+), 23 deletions(-) rename collector_scripts/sensor_scripts/Brake_Sensor/{ => src}/test/README (97%) create mode 100644 develop/Figure_1.png create mode 100644 develop/plot_json.py create mode 100644 develop/rizer_data.json diff --git a/collector_scripts/sensor_scripts/Brake_Sensor/test/README b/collector_scripts/sensor_scripts/Brake_Sensor/src/test/README similarity index 97% rename from collector_scripts/sensor_scripts/Brake_Sensor/test/README rename to collector_scripts/sensor_scripts/Brake_Sensor/src/test/README index c3b0ed6..df5066e 100644 --- a/collector_scripts/sensor_scripts/Brake_Sensor/test/README +++ b/collector_scripts/sensor_scripts/Brake_Sensor/src/test/README @@ -1,11 +1,11 @@ - -This directory is intended for PIO Unit Testing and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PIO Unit Testing: -- https://docs.platformio.org/page/plus/unit-testing.html + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/develop/Figure_1.png b/develop/Figure_1.png new file mode 100644 index 0000000000000000000000000000000000000000..d125f71c53c56bcaa50e0fbae58cf1ad57b1121b GIT binary patch literal 325610 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

UW=t-t^Y3^#wx7VqUilxBd$aAl70 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 R0#O7$m(?^?vz1N#{|B1Fu)qKS literal 0 HcmV?d00001 diff --git a/develop/plot_json.py b/develop/plot_json.py new file mode 100644 index 0000000..d214322 --- /dev/null +++ b/develop/plot_json.py @@ -0,0 +1,53 @@ +import json +import matplotlib.pyplot as plt +import numpy as np + +def load_and_plot(): + # Load the JSON data + with open('rizer_data.json', 'r') as f: + data = json.load(f) + + # Extract the first element from each inner list for all four categories + single_y_values_1 = [inner_list[0] for inner_list in data] + single_y_values_2 = [inner_list[1] for inner_list in data] + single_y_values_3 = [inner_list[2] for inner_list in data] + single_y_values_4 = [inner_list[3] for inner_list in data] + + # Create a 2x2 grid of subplots + fig, axs = plt.subplots(2, 2, figsize=(15, 12)) + + # Plot Category 1 + axs[0, 0].plot(range(len(data)), single_y_values_1, color='blue', label='First Value') + axs[0, 0].set_title('Category 1') + axs[0, 0].set_xlabel('Index') + axs[0, 0].set_ylabel('Value') + axs[0, 0].legend() + + # Plot Category 2 + axs[0, 1].plot(range(len(data)), single_y_values_2, color='green', label='Second Value') + axs[0, 1].set_title('Category 2') + axs[0, 1].set_xlabel('Index') + axs[0, 1].set_ylabel('Value') + axs[0, 1].legend() + + # Plot Category 3 + axs[1, 0].plot(range(len(data)), single_y_values_3, color='red', label='Third Value') + axs[1, 0].plot(range(len(data)), single_y_values_4, color='purple', label='Fourth Value') + axs[1, 0].set_title('Category 3') + axs[1, 0].set_xlabel('Index') + axs[1, 0].set_ylabel('Value') + axs[1, 0].legend() + + # Plot Category 4 + axs[1, 1].plot(range(len(data)), single_y_values_4, color='purple', label='Fourth Value') + axs[1, 1].set_title('Category 4') + axs[1, 1].set_xlabel('Index') + axs[1, 1].set_ylabel('Value') + axs[1, 1].legend() + + # Adjust layout + plt.tight_layout() + plt.show() + +if __name__ == "__main__": + load_and_plot() diff --git a/develop/read_all_rizer.py b/develop/read_all_rizer.py index f11ac12..02d7cbd 100644 --- a/develop/read_all_rizer.py +++ b/develop/read_all_rizer.py @@ -18,6 +18,8 @@ class TestRizer: listen_data = {} + data_list = [] + #---------------------------BLE functions-------------------------------- #function to initialize the BLE connection with the Rizer @@ -47,13 +49,8 @@ async def stop_notification(self): async def on_steering_changed(self, sender, data): data = bytearray(data) - print(f"") - print(f"Sender {sender}: \n Steering changed: {[byte for byte in data]}") - - async def on_steering_changed_to_json(self, sender, data): - - data = bytearray(data) - print(f"") + self.data_list.append([byte for byte in data]) + print(f"Sender {sender}: \n Steering changed: {data}") print(f"Sender {sender}: \n Steering changed: {[byte for byte in data]}") @@ -80,6 +77,9 @@ async def connect_rizer(self): self.data[service_uuid] = [] for characteristic in service.characteristics: + if (characteristic.uuid == self.CHARACTERISTICS_STEERING_UUID): + self.listCharacteristic.append(characteristic) + char_uuid = str(characteristic.uuid) char_data = { "uuid": char_uuid, @@ -116,14 +116,14 @@ async def connect_rizer(self): async def main(): - config = "middle" + #config = "middle" await rizer.connect_rizer() - rizer.save_to_json(filename= config +"_ble_device_data.json") - + #rizer.save_to_json(filename= config +"_ble_device_data.json") + - ''' + for charc in rizer.listCharacteristic: await rizer.start_notification(characteristic = charc) @@ -131,6 +131,17 @@ async def main(): # Keep the connection alive for a while await asyncio.sleep(60) - await rizer.stop_notification()''' + await rizer.stop_notification() + + print(rizer.data_list) + # Specify the file path and name + file_path = 'rizer_data.json' + + # Open the file in write mode + with open(file_path, 'w') as f: + # Use json.dump() to serialize the data to JSON format + json.dump(rizer.data_list, f, indent=4) + + print(f"Data saved to {file_path}") asyncio.run(main()) diff --git a/develop/rizer_data.json b/develop/rizer_data.json new file mode 100644 index 0000000..5becd8a --- /dev/null +++ b/develop/rizer_data.json @@ -0,0 +1,3638 @@ +[ + [ + 0, + 161, + 4, + 192 + ], + [ + 224, + 246, + 2, + 192 + ], + [ + 64, + 131, + 253, + 191 + ], + [ + 160, + 100, + 11, + 192 + ], + [ + 192, + 67, + 17, + 192 + ], + [ + 224, + 32, + 23, + 192 + ], + [ + 0, + 124, + 21, + 192 + ], + [ + 0, + 245, + 13, + 192 + ], + [ + 192, + 67, + 17, + 192 + ], + [ + 64, + 214, + 8, + 192 + ], + [ + 128, + 179, + 14, + 192 + ], + [ + 224, + 246, + 2, + 192 + ], + [ + 128, + 53, + 8, + 191 + ], + [ + 64, + 99, + 180, + 63 + ], + [ + 49, + 38, + 124, + 64 + ], + [ + 207, + 176, + 217, + 64 + ], + [ + 216, + 240, + 30, + 65 + ], + [ + 108, + 167, + 69, + 65 + ], + [ + 164, + 250, + 102, + 65 + ], + [ + 172, + 173, + 103, + 65 + ], + [ + 79, + 177, + 100, + 65 + ], + [ + 164, + 250, + 102, + 65 + ], + [ + 79, + 41, + 106, + 65 + ], + [ + 40, + 249, + 105, + 65 + ], + [ + 88, + 69, + 105, + 65 + ], + [ + 88, + 69, + 105, + 65 + ], + [ + 12, + 97, + 104, + 65 + ], + [ + 12, + 97, + 104, + 65 + ], + [ + 4, + 48, + 104, + 65 + ], + [ + 4, + 48, + 104, + 65 + ], + [ + 4, + 48, + 104, + 65 + ], + [ + 40, + 249, + 105, + 65 + ], + [ + 4, + 228, + 104, + 65 + ], + [ + 12, + 97, + 104, + 65 + ], + [ + 12, + 97, + 104, + 65 + ], + [ + 212, + 254, + 103, + 65 + ], + [ + 216, + 200, + 105, + 65 + ], + [ + 4, + 228, + 104, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 212, + 254, + 103, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 228, + 254, + 100, + 65 + ], + [ + 152, + 102, + 99, + 65 + ], + [ + 24, + 61, + 93, + 65 + ], + [ + 52, + 3, + 81, + 65 + ], + [ + 40, + 71, + 65, + 65 + ], + [ + 184, + 38, + 43, + 65 + ], + [ + 60, + 88, + 25, + 65 + ], + [ + 120, + 79, + 10, + 65 + ], + [ + 15, + 178, + 243, + 64 + ], + [ + 136, + 124, + 205, + 64 + ], + [ + 232, + 80, + 175, + 64 + ], + [ + 248, + 68, + 154, + 64 + ], + [ + 241, + 225, + 117, + 64 + ], + [ + 160, + 3, + 15, + 64 + ], + [ + 128, + 43, + 41, + 63 + ], + [ + 128, + 162, + 43, + 191 + ], + [ + 0, + 196, + 204, + 191 + ], + [ + 32, + 74, + 6, + 192 + ], + [ + 192, + 22, + 2, + 192 + ], + [ + 0, + 201, + 251, + 191 + ], + [ + 224, + 55, + 1, + 192 + ], + [ + 64, + 26, + 245, + 191 + ], + [ + 0, + 161, + 4, + 192 + ], + [ + 192, + 114, + 248, + 191 + ], + [ + 192, + 22, + 2, + 192 + ], + [ + 192, + 22, + 2, + 192 + ], + [ + 192, + 22, + 2, + 192 + ], + [ + 0, + 161, + 4, + 192 + ], + [ + 224, + 251, + 28, + 192 + ], + [ + 128, + 136, + 93, + 192 + ], + [ + 128, + 157, + 163, + 192 + ], + [ + 32, + 72, + 210, + 192 + ], + [ + 80, + 61, + 2, + 193 + ], + [ + 112, + 124, + 29, + 193 + ], + [ + 136, + 15, + 54, + 193 + ], + [ + 40, + 118, + 82, + 193 + ], + [ + 96, + 132, + 117, + 193 + ], + [ + 216, + 196, + 145, + 193 + ], + [ + 72, + 138, + 148, + 193 + ], + [ + 188, + 246, + 147, + 193 + ], + [ + 28, + 69, + 150, + 193 + ], + [ + 104, + 204, + 150, + 193 + ], + [ + 88, + 31, + 150, + 193 + ], + [ + 216, + 71, + 151, + 193 + ], + [ + 216, + 71, + 151, + 193 + ], + [ + 148, + 179, + 150, + 193 + ], + [ + 28, + 167, + 150, + 193 + ], + [ + 4, + 192, + 150, + 193 + ], + [ + 148, + 179, + 150, + 193 + ], + [ + 140, + 56, + 150, + 193 + ], + [ + 140, + 56, + 150, + 193 + ], + [ + 88, + 31, + 150, + 193 + ], + [ + 184, + 164, + 149, + 193 + ], + [ + 40, + 139, + 149, + 193 + ], + [ + 248, + 3, + 149, + 193 + ], + [ + 148, + 43, + 148, + 193 + ], + [ + 240, + 63, + 145, + 193 + ], + [ + 232, + 140, + 129, + 193 + ], + [ + 79, + 171, + 65, + 193 + ], + [ + 224, + 37, + 246, + 192 + ], + [ + 177, + 73, + 197, + 192 + ], + [ + 143, + 250, + 238, + 192 + ], + [ + 48, + 1, + 11, + 193 + ], + [ + 56, + 167, + 28, + 193 + ], + [ + 176, + 70, + 27, + 193 + ], + [ + 160, + 208, + 19, + 193 + ], + [ + 224, + 254, + 18, + 193 + ], + [ + 24, + 25, + 20, + 193 + ], + [ + 96, + 97, + 20, + 193 + ], + [ + 96, + 97, + 20, + 193 + ], + [ + 88, + 235, + 20, + 193 + ], + [ + 128, + 129, + 16, + 193 + ], + [ + 200, + 250, + 15, + 193 + ], + [ + 64, + 106, + 7, + 193 + ], + [ + 64, + 197, + 246, + 192 + ], + [ + 49, + 66, + 200, + 192 + ], + [ + 240, + 101, + 146, + 192 + ], + [ + 96, + 239, + 29, + 192 + ], + [ + 0, + 62, + 215, + 190 + ], + [ + 192, + 89, + 208, + 63 + ], + [ + 192, + 110, + 126, + 64 + ], + [ + 56, + 97, + 199, + 64 + ], + [ + 76, + 20, + 1, + 65 + ], + [ + 143, + 6, + 41, + 65 + ], + [ + 160, + 210, + 77, + 65 + ], + [ + 68, + 25, + 100, + 65 + ], + [ + 4, + 178, + 101, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 177, + 103, + 105, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 4, + 178, + 101, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 96, + 151, + 102, + 65 + ], + [ + 44, + 77, + 101, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 104, + 51, + 102, + 65 + ], + [ + 236, + 74, + 103, + 65 + ], + [ + 120, + 205, + 103, + 65 + ], + [ + 120, + 205, + 103, + 65 + ], + [ + 4, + 179, + 99, + 65 + ], + [ + 212, + 255, + 98, + 65 + ], + [ + 168, + 135, + 78, + 65 + ], + [ + 136, + 201, + 43, + 65 + ], + [ + 113, + 59, + 252, + 64 + ], + [ + 160, + 170, + 89, + 64 + ], + [ + 0, + 192, + 241, + 191 + ], + [ + 192, + 204, + 170, + 192 + ], + [ + 177, + 55, + 190, + 192 + ], + [ + 0, + 196, + 139, + 192 + ], + [ + 128, + 96, + 55, + 192 + ], + [ + 224, + 32, + 23, + 192 + ], + [ + 32, + 204, + 35, + 192 + ], + [ + 64, + 196, + 78, + 192 + ], + [ + 0, + 47, + 77, + 192 + ], + [ + 128, + 4, + 83, + 192 + ], + [ + 32, + 191, + 69, + 192 + ], + [ + 160, + 32, + 51, + 192 + ], + [ + 128, + 90, + 54, + 192 + ], + [ + 0, + 175, + 68, + 192 + ], + [ + 192, + 178, + 85, + 192 + ], + [ + 160, + 144, + 57, + 192 + ], + [ + 64, + 87, + 71, + 192 + ], + [ + 96, + 198, + 97, + 192 + ], + [ + 80, + 65, + 144, + 192 + ], + [ + 128, + 190, + 188, + 192 + ], + [ + 192, + 220, + 224, + 192 + ], + [ + 248, + 183, + 5, + 193 + ], + [ + 80, + 82, + 30, + 193 + ], + [ + 8, + 103, + 63, + 193 + ], + [ + 168, + 193, + 86, + 193 + ], + [ + 113, + 75, + 107, + 193 + ], + [ + 79, + 210, + 124, + 193 + ], + [ + 84, + 177, + 141, + 193 + ], + [ + 56, + 99, + 147, + 193 + ], + [ + 148, + 43, + 148, + 193 + ], + [ + 112, + 233, + 147, + 193 + ], + [ + 132, + 248, + 146, + 193 + ], + [ + 20, + 126, + 147, + 193 + ], + [ + 56, + 17, + 148, + 193 + ], + [ + 172, + 112, + 147, + 193 + ], + [ + 132, + 248, + 146, + 193 + ], + [ + 228, + 87, + 146, + 193 + ], + [ + 100, + 164, + 148, + 193 + ], + [ + 248, + 234, + 146, + 193 + ], + [ + 104, + 177, + 148, + 193 + ], + [ + 132, + 248, + 146, + 193 + ], + [ + 180, + 35, + 145, + 193 + ], + [ + 248, + 234, + 146, + 193 + ], + [ + 132, + 248, + 146, + 193 + ], + [ + 160, + 101, + 146, + 193 + ], + [ + 44, + 100, + 130, + 193 + ], + [ + 120, + 220, + 61, + 193 + ], + [ + 96, + 151, + 221, + 192 + ], + [ + 96, + 200, + 170, + 192 + ], + [ + 64, + 115, + 204, + 192 + ], + [ + 192, + 83, + 1, + 193 + ], + [ + 72, + 195, + 21, + 193 + ], + [ + 184, + 149, + 22, + 193 + ], + [ + 168, + 77, + 14, + 193 + ], + [ + 88, + 126, + 13, + 193 + ], + [ + 144, + 200, + 13, + 193 + ], + [ + 144, + 200, + 13, + 193 + ], + [ + 168, + 77, + 14, + 193 + ], + [ + 104, + 3, + 14, + 193 + ], + [ + 184, + 158, + 12, + 193 + ], + [ + 160, + 158, + 4, + 193 + ], + [ + 143, + 250, + 238, + 192 + ], + [ + 177, + 241, + 205, + 192 + ], + [ + 79, + 51, + 167, + 192 + ], + [ + 16, + 14, + 132, + 192 + ], + [ + 32, + 26, + 76, + 192 + ], + [ + 0, + 192, + 241, + 191 + ], + [ + 0, + 146, + 86, + 191 + ], + [ + 0, + 16, + 170, + 62 + ], + [ + 64, + 99, + 180, + 63 + ], + [ + 160, + 73, + 53, + 64 + ], + [ + 240, + 98, + 143, + 64 + ], + [ + 15, + 112, + 213, + 64 + ], + [ + 24, + 239, + 7, + 65 + ], + [ + 88, + 154, + 42, + 65 + ], + [ + 108, + 206, + 82, + 65 + ], + [ + 236, + 74, + 103, + 65 + ], + [ + 4, + 178, + 101, + 65 + ], + [ + 104, + 51, + 102, + 65 + ], + [ + 104, + 51, + 102, + 65 + ], + [ + 44, + 77, + 101, + 65 + ], + [ + 44, + 77, + 101, + 65 + ], + [ + 148, + 102, + 100, + 65 + ], + [ + 136, + 153, + 100, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 124, + 231, + 102, + 65 + ], + [ + 136, + 153, + 100, + 65 + ], + [ + 136, + 153, + 100, + 65 + ], + [ + 116, + 51, + 100, + 65 + ], + [ + 60, + 230, + 99, + 65 + ], + [ + 84, + 232, + 96, + 65 + ], + [ + 136, + 159, + 77, + 65 + ], + [ + 60, + 11, + 60, + 65 + ], + [ + 244, + 150, + 31, + 65 + ], + [ + 184, + 33, + 249, + 64 + ], + [ + 208, + 5, + 135, + 64 + ], + [ + 0, + 50, + 236, + 62 + ], + [ + 224, + 43, + 103, + 192 + ], + [ + 32, + 91, + 169, + 192 + ], + [ + 32, + 147, + 145, + 192 + ], + [ + 192, + 178, + 85, + 192 + ], + [ + 128, + 42, + 34, + 192 + ], + [ + 160, + 29, + 50, + 192 + ], + [ + 192, + 235, + 81, + 192 + ], + [ + 96, + 112, + 64, + 192 + ], + [ + 224, + 54, + 69, + 192 + ], + [ + 224, + 100, + 63, + 192 + ], + [ + 224, + 8, + 66, + 192 + ], + [ + 64, + 186, + 51, + 192 + ], + [ + 32, + 56, + 96, + 192 + ], + [ + 208, + 4, + 160, + 192 + ], + [ + 15, + 199, + 203, + 192 + ], + [ + 143, + 69, + 254, + 192 + ], + [ + 56, + 171, + 28, + 193 + ], + [ + 8, + 145, + 55, + 193 + ], + [ + 56, + 55, + 80, + 193 + ], + [ + 152, + 1, + 112, + 193 + ], + [ + 128, + 116, + 135, + 193 + ], + [ + 56, + 99, + 147, + 193 + ], + [ + 56, + 99, + 147, + 193 + ], + [ + 104, + 177, + 148, + 193 + ], + [ + 72, + 138, + 148, + 193 + ], + [ + 156, + 55, + 149, + 193 + ], + [ + 20, + 126, + 147, + 193 + ], + [ + 56, + 17, + 148, + 193 + ], + [ + 24, + 166, + 147, + 193 + ], + [ + 188, + 246, + 147, + 193 + ], + [ + 12, + 6, + 147, + 193 + ], + [ + 216, + 196, + 145, + 193 + ], + [ + 92, + 3, + 139, + 193 + ], + [ + 40, + 100, + 111, + 193 + ], + [ + 56, + 55, + 80, + 193 + ], + [ + 88, + 213, + 40, + 193 + ], + [ + 32, + 32, + 4, + 193 + ], + [ + 160, + 104, + 197, + 192 + ], + [ + 176, + 242, + 134, + 192 + ], + [ + 64, + 214, + 8, + 192 + ], + [ + 0, + 160, + 83, + 190 + ], + [ + 64, + 141, + 216, + 63 + ], + [ + 15, + 192, + 89, + 64 + ], + [ + 136, + 226, + 185, + 64 + ], + [ + 44, + 42, + 5, + 65 + ], + [ + 104, + 99, + 40, + 65 + ], + [ + 0, + 59, + 66, + 65 + ], + [ + 15, + 25, + 98, + 65 + ], + [ + 212, + 254, + 103, + 65 + ], + [ + 96, + 151, + 102, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 236, + 74, + 103, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 60, + 230, + 99, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 148, + 102, + 100, + 65 + ], + [ + 120, + 96, + 89, + 65 + ], + [ + 236, + 211, + 68, + 65 + ], + [ + 208, + 200, + 28, + 65 + ], + [ + 56, + 222, + 227, + 64 + ], + [ + 72, + 45, + 155, + 64 + ], + [ + 177, + 151, + 83, + 64 + ], + [ + 128, + 69, + 185, + 63 + ], + [ + 0, + 100, + 215, + 189 + ], + [ + 64, + 131, + 253, + 191 + ], + [ + 64, + 187, + 87, + 192 + ], + [ + 240, + 183, + 155, + 192 + ], + [ + 96, + 157, + 209, + 192 + ], + [ + 192, + 83, + 1, + 193 + ], + [ + 240, + 87, + 25, + 193 + ], + [ + 120, + 147, + 53, + 193 + ], + [ + 207, + 217, + 76, + 193 + ], + [ + 152, + 35, + 90, + 193 + ], + [ + 49, + 148, + 102, + 193 + ], + [ + 184, + 161, + 126, + 193 + ], + [ + 76, + 215, + 142, + 193 + ], + [ + 104, + 204, + 150, + 193 + ], + [ + 120, + 177, + 149, + 193 + ], + [ + 184, + 164, + 149, + 193 + ], + [ + 56, + 244, + 151, + 193 + ], + [ + 160, + 81, + 150, + 193 + ], + [ + 140, + 56, + 150, + 193 + ], + [ + 140, + 56, + 150, + 193 + ], + [ + 116, + 139, + 147, + 193 + ], + [ + 104, + 177, + 148, + 193 + ], + [ + 100, + 164, + 148, + 193 + ], + [ + 40, + 173, + 144, + 193 + ], + [ + 216, + 186, + 136, + 193 + ], + [ + 96, + 30, + 120, + 193 + ], + [ + 177, + 151, + 92, + 193 + ], + [ + 200, + 145, + 68, + 193 + ], + [ + 248, + 88, + 51, + 193 + ], + [ + 48, + 155, + 33, + 193 + ], + [ + 56, + 221, + 22, + 193 + ], + [ + 96, + 81, + 6, + 193 + ], + [ + 224, + 251, + 223, + 192 + ], + [ + 241, + 51, + 172, + 192 + ], + [ + 48, + 214, + 137, + 192 + ], + [ + 160, + 69, + 43, + 192 + ], + [ + 64, + 120, + 159, + 191 + ], + [ + 0, + 80, + 50, + 62 + ], + [ + 128, + 57, + 184, + 63 + ], + [ + 79, + 56, + 47, + 64 + ], + [ + 96, + 29, + 116, + 64 + ], + [ + 15, + 185, + 168, + 64 + ], + [ + 15, + 112, + 213, + 64 + ], + [ + 224, + 163, + 2, + 65 + ], + [ + 240, + 251, + 24, + 65 + ], + [ + 116, + 185, + 40, + 65 + ], + [ + 136, + 190, + 61, + 65 + ], + [ + 228, + 88, + 77, + 65 + ], + [ + 92, + 100, + 92, + 65 + ], + [ + 60, + 230, + 99, + 65 + ], + [ + 4, + 178, + 101, + 65 + ], + [ + 76, + 51, + 99, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 252, + 129, + 104, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 177, + 103, + 105, + 65 + ], + [ + 104, + 51, + 102, + 65 + ], + [ + 44, + 77, + 101, + 65 + ], + [ + 236, + 155, + 103, + 65 + ], + [ + 36, + 156, + 101, + 65 + ], + [ + 84, + 152, + 98, + 65 + ], + [ + 24, + 61, + 93, + 65 + ], + [ + 24, + 229, + 74, + 65 + ], + [ + 164, + 195, + 53, + 65 + ], + [ + 248, + 36, + 28, + 65 + ], + [ + 56, + 169, + 10, + 65 + ], + [ + 136, + 245, + 230, + 64 + ], + [ + 232, + 162, + 192, + 64 + ], + [ + 168, + 88, + 155, + 64 + ], + [ + 96, + 33, + 129, + 64 + ], + [ + 113, + 98, + 61, + 64 + ], + [ + 0, + 120, + 241, + 63 + ], + [ + 0, + 112, + 221, + 62 + ], + [ + 128, + 204, + 55, + 191 + ], + [ + 192, + 63, + 255, + 191 + ], + [ + 128, + 90, + 54, + 192 + ], + [ + 0, + 158, + 101, + 192 + ], + [ + 16, + 22, + 137, + 192 + ], + [ + 32, + 172, + 173, + 192 + ], + [ + 207, + 229, + 198, + 192 + ], + [ + 241, + 224, + 229, + 192 + ], + [ + 32, + 103, + 247, + 192 + ], + [ + 48, + 137, + 14, + 193 + ], + [ + 32, + 241, + 28, + 193 + ], + [ + 88, + 213, + 40, + 193 + ], + [ + 8, + 26, + 51, + 193 + ], + [ + 15, + 101, + 61, + 193 + ], + [ + 192, + 44, + 74, + 193 + ], + [ + 8, + 54, + 89, + 193 + ], + [ + 49, + 251, + 105, + 193 + ], + [ + 116, + 136, + 128, + 193 + ], + [ + 48, + 108, + 140, + 193 + ], + [ + 192, + 85, + 147, + 193 + ], + [ + 188, + 246, + 147, + 193 + ], + [ + 100, + 164, + 148, + 193 + ], + [ + 156, + 55, + 149, + 193 + ], + [ + 220, + 29, + 149, + 193 + ], + [ + 248, + 3, + 149, + 193 + ], + [ + 140, + 56, + 150, + 193 + ], + [ + 144, + 59, + 151, + 193 + ], + [ + 156, + 154, + 150, + 193 + ], + [ + 244, + 151, + 149, + 193 + ], + [ + 244, + 151, + 149, + 193 + ], + [ + 248, + 43, + 150, + 193 + ], + [ + 96, + 190, + 148, + 193 + ], + [ + 56, + 17, + 148, + 193 + ], + [ + 40, + 173, + 144, + 193 + ], + [ + 232, + 76, + 137, + 193 + ], + [ + 40, + 245, + 125, + 193 + ], + [ + 88, + 76, + 97, + 193 + ], + [ + 88, + 238, + 71, + 193 + ], + [ + 0, + 123, + 50, + 193 + ], + [ + 176, + 165, + 33, + 193 + ], + [ + 144, + 29, + 15, + 193 + ], + [ + 79, + 135, + 240, + 192 + ], + [ + 192, + 32, + 195, + 192 + ], + [ + 240, + 121, + 146, + 192 + ], + [ + 0, + 246, + 55, + 192 + ], + [ + 64, + 186, + 177, + 191 + ], + [ + 0, + 200, + 54, + 190 + ], + [ + 0, + 82, + 120, + 63 + ], + [ + 160, + 234, + 22, + 64 + ], + [ + 160, + 243, + 101, + 64 + ], + [ + 72, + 132, + 155, + 64 + ], + [ + 248, + 78, + 194, + 64 + ], + [ + 40, + 43, + 237, + 64 + ], + [ + 164, + 31, + 12, + 65 + ], + [ + 216, + 200, + 33, + 65 + ], + [ + 152, + 103, + 46, + 65 + ], + [ + 28, + 185, + 63, + 65 + ], + [ + 49, + 21, + 75, + 65 + ], + [ + 15, + 209, + 87, + 65 + ], + [ + 120, + 154, + 97, + 65 + ], + [ + 68, + 25, + 100, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 152, + 102, + 99, + 65 + ], + [ + 248, + 76, + 98, + 65 + ], + [ + 60, + 230, + 99, + 65 + ], + [ + 236, + 74, + 103, + 65 + ], + [ + 76, + 204, + 100, + 65 + ], + [ + 76, + 204, + 100, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 136, + 153, + 100, + 65 + ], + [ + 60, + 230, + 99, + 65 + ], + [ + 192, + 206, + 101, + 65 + ], + [ + 44, + 77, + 101, + 65 + ], + [ + 148, + 102, + 100, + 65 + ], + [ + 241, + 49, + 97, + 65 + ], + [ + 177, + 191, + 90, + 65 + ], + [ + 143, + 39, + 70, + 65 + ], + [ + 177, + 108, + 44, + 65 + ], + [ + 16, + 134, + 27, + 65 + ], + [ + 56, + 169, + 10, + 65 + ], + [ + 113, + 63, + 240, + 64 + ], + [ + 15, + 182, + 202, + 64 + ], + [ + 240, + 118, + 163, + 64 + ], + [ + 128, + 248, + 123, + 64 + ], + [ + 177, + 40, + 41, + 64 + ], + [ + 0, + 134, + 30, + 63 + ], + [ + 128, + 65, + 110, + 191 + ], + [ + 64, + 7, + 40, + 192 + ], + [ + 64, + 89, + 126, + 192 + ], + [ + 224, + 163, + 168, + 192 + ], + [ + 160, + 185, + 196, + 192 + ], + [ + 241, + 177, + 227, + 192 + ], + [ + 232, + 55, + 3, + 193 + ], + [ + 248, + 237, + 10, + 193 + ], + [ + 192, + 131, + 24, + 193 + ], + [ + 168, + 125, + 34, + 193 + ], + [ + 40, + 118, + 52, + 193 + ], + [ + 216, + 251, + 64, + 193 + ], + [ + 192, + 188, + 73, + 193 + ], + [ + 113, + 169, + 91, + 193 + ], + [ + 192, + 178, + 110, + 193 + ], + [ + 143, + 158, + 127, + 193 + ], + [ + 132, + 102, + 136, + 193 + ], + [ + 48, + 130, + 144, + 193 + ], + [ + 192, + 42, + 149, + 193 + ], + [ + 92, + 151, + 148, + 193 + ], + [ + 112, + 233, + 147, + 193 + ], + [ + 44, + 125, + 148, + 193 + ], + [ + 240, + 16, + 149, + 193 + ], + [ + 120, + 177, + 149, + 193 + ], + [ + 56, + 17, + 148, + 193 + ], + [ + 188, + 246, + 147, + 193 + ], + [ + 112, + 233, + 147, + 193 + ], + [ + 188, + 246, + 147, + 193 + ], + [ + 0, + 4, + 148, + 193 + ], + [ + 56, + 17, + 148, + 193 + ], + [ + 20, + 126, + 147, + 193 + ], + [ + 28, + 74, + 146, + 193 + ], + [ + 88, + 83, + 142, + 193 + ], + [ + 176, + 133, + 135, + 193 + ], + [ + 113, + 205, + 125, + 193 + ], + [ + 168, + 45, + 112, + 193 + ], + [ + 56, + 234, + 93, + 193 + ], + [ + 192, + 44, + 74, + 193 + ], + [ + 32, + 71, + 58, + 193 + ], + [ + 192, + 79, + 43, + 193 + ], + [ + 104, + 82, + 17, + 193 + ], + [ + 177, + 88, + 233, + 192 + ], + [ + 208, + 71, + 149, + 192 + ], + [ + 64, + 204, + 61, + 192 + ], + [ + 0, + 250, + 119, + 191 + ], + [ + 0, + 103, + 111, + 63 + ], + [ + 113, + 30, + 43, + 64 + ], + [ + 224, + 45, + 114, + 64 + ], + [ + 232, + 215, + 165, + 64 + ], + [ + 15, + 112, + 213, + 64 + ], + [ + 84, + 229, + 8, + 65 + ], + [ + 0, + 128, + 27, + 65 + ], + [ + 40, + 5, + 53, + 65 + ], + [ + 44, + 6, + 78, + 65 + ], + [ + 68, + 25, + 100, + 65 + ], + [ + 96, + 151, + 102, + 65 + ], + [ + 236, + 74, + 103, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 124, + 26, + 101, + 65 + ], + [ + 120, + 101, + 102, + 65 + ], + [ + 20, + 179, + 104, + 65 + ], + [ + 4, + 228, + 104, + 65 + ], + [ + 212, + 254, + 103, + 65 + ], + [ + 252, + 129, + 104, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 148, + 102, + 100, + 65 + ], + [ + 76, + 25, + 103, + 65 + ], + [ + 124, + 231, + 102, + 65 + ], + [ + 84, + 152, + 98, + 65 + ], + [ + 172, + 127, + 101, + 65 + ], + [ + 44, + 77, + 101, + 65 + ], + [ + 160, + 127, + 99, + 65 + ], + [ + 15, + 76, + 99, + 65 + ], + [ + 76, + 102, + 97, + 65 + ], + [ + 52, + 241, + 88, + 65 + ], + [ + 92, + 80, + 73, + 65 + ], + [ + 88, + 141, + 57, + 65 + ], + [ + 248, + 22, + 39, + 65 + ], + [ + 216, + 86, + 20, + 65 + ], + [ + 40, + 245, + 247, + 64 + ], + [ + 88, + 214, + 196, + 64 + ], + [ + 88, + 31, + 152, + 64 + ], + [ + 128, + 237, + 107, + 64 + ], + [ + 64, + 219, + 8, + 64 + ], + [ + 128, + 3, + 47, + 63 + ], + [ + 0, + 68, + 60, + 191 + ], + [ + 64, + 131, + 253, + 191 + ], + [ + 128, + 250, + 108, + 192 + ], + [ + 64, + 213, + 175, + 192 + ], + [ + 192, + 220, + 224, + 192 + ], + [ + 143, + 204, + 251, + 192 + ], + [ + 32, + 31, + 10, + 193 + ], + [ + 32, + 246, + 18, + 193 + ], + [ + 56, + 97, + 33, + 193 + ], + [ + 49, + 7, + 45, + 193 + ], + [ + 120, + 54, + 65, + 193 + ], + [ + 192, + 21, + 88, + 193 + ], + [ + 200, + 109, + 108, + 193 + ], + [ + 120, + 172, + 129, + 193 + ], + [ + 216, + 196, + 145, + 193 + ], + [ + 44, + 190, + 149, + 193 + ], + [ + 220, + 29, + 149, + 193 + ], + [ + 240, + 16, + 149, + 193 + ], + [ + 4, + 192, + 150, + 193 + ], + [ + 28, + 69, + 150, + 193 + ], + [ + 200, + 216, + 150, + 193 + ], + [ + 124, + 108, + 151, + 193 + ], + [ + 48, + 232, + 151, + 193 + ], + [ + 28, + 69, + 150, + 193 + ], + [ + 240, + 16, + 149, + 193 + ], + [ + 92, + 151, + 148, + 193 + ], + [ + 20, + 126, + 147, + 193 + ], + [ + 20, + 126, + 147, + 193 + ], + [ + 132, + 248, + 146, + 193 + ], + [ + 44, + 186, + 140, + 193 + ], + [ + 132, + 8, + 131, + 193 + ], + [ + 248, + 33, + 109, + 193 + ], + [ + 136, + 213, + 85, + 193 + ], + [ + 79, + 221, + 59, + 193 + ], + [ + 192, + 5, + 35, + 193 + ], + [ + 128, + 26, + 12, + 193 + ], + [ + 177, + 145, + 249, + 192 + ], + [ + 177, + 73, + 197, + 192 + ], + [ + 224, + 188, + 145, + 192 + ], + [ + 96, + 160, + 67, + 192 + ], + [ + 64, + 26, + 245, + 191 + ], + [ + 0, + 109, + 22, + 191 + ], + [ + 0, + 109, + 45, + 63 + ], + [ + 192, + 162, + 4, + 64 + ], + [ + 143, + 213, + 101, + 64 + ], + [ + 152, + 217, + 178, + 64 + ], + [ + 128, + 253, + 249, + 64 + ], + [ + 204, + 17, + 29, + 65 + ], + [ + 220, + 90, + 51, + 65 + ], + [ + 40, + 233, + 76, + 65 + ], + [ + 120, + 154, + 97, + 65 + ], + [ + 40, + 1, + 102, + 65 + ] +] \ No newline at end of file From 1f1595f1ce77baafb1edf54ed9f670a995daa943 Mon Sep 17 00:00:00 2001 From: unitylab-dev Date: Mon, 21 Oct 2024 11:40:14 +0200 Subject: [PATCH 65/65] changes to dectect this old version being used in unity --- collector_scripts/master_collector.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/collector_scripts/master_collector.py b/collector_scripts/master_collector.py index 2e9e2ee..2754336 100644 --- a/collector_scripts/master_collector.py +++ b/collector_scripts/master_collector.py @@ -11,6 +11,7 @@ def __init__(self): self.brake_value = 0 self.bno_value = 0 self.roll_value = 0 + self.usingOldVersion = 0 self.udp_unity_send_ip = "127.0.0.2" # IP of the computer running Unity (just the localhost ip if the script is running on the same computer than the simulation) # self.udp_unity_send_ip = "10.30.77.221" # IP of the computer running Unity self.udp_unity_send_port = 1337 @@ -30,7 +31,7 @@ def collect_bno(self, bno): def collect_roll(self, roll): self.roll_value = roll - def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, roll_data): + def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, roll_data, usingOldVesion): # Create a dictionary with the required parameters @@ -39,7 +40,8 @@ def send_unity_data_udp(self, speed_data, steering_data, brake_data, bno_data, r "rizerSteering": float(steering_data), "espBno": float(bno_data), "espBrake": float(brake_data), - "espRoll": float(roll_data) + "espRoll": float(roll_data), + "usingOldVersion": int(usingOldVesion) } print(data) # Convert dictionary to JSON string @@ -177,7 +179,7 @@ def stop_udp_listener(self): while True: # print("udp_direto_socket: ", udp_direto_socket) - data_sender.send_unity_data_udp(data_sender.speed_value, data_sender.steering_value, data_sender.brake_value, data_sender.bno_value, data_sender.roll_value) + data_sender.send_unity_data_udp(data_sender.speed_value, data_sender.steering_value, data_sender.brake_value, data_sender.bno_value, data_sender.roll_value, usingOldVesion = 1) readable, _, _ = select.select([udp_rizer_socket, udp_direto_socket, udp_brake_socket, udp_bno_socket, udp_roll_socket], [], []) for sock in readable: