mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 23:41:50 +01:00
Compare commits
33 Commits
version-2.
...
dircon-cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26ce3da489 | ||
|
|
fa5677d550 | ||
|
|
489dd20cc7 | ||
|
|
64da80add0 | ||
|
|
090bb8ec95 | ||
|
|
0216ce1170 | ||
|
|
0f2c9a7c6b | ||
|
|
882b3779a0 | ||
|
|
26430cf970 | ||
|
|
388fedb727 | ||
|
|
4b9ce8a74f | ||
|
|
8e39da88a2 | ||
|
|
b251b23d78 | ||
|
|
bbc930dc7c | ||
|
|
00d1a691b5 | ||
|
|
2a3a08380a | ||
|
|
064dfbf5fb | ||
|
|
8fac949eba | ||
|
|
9921796b6c | ||
|
|
90d5db2e92 | ||
|
|
699695e4fd | ||
|
|
ac7b5a136f | ||
|
|
e408d89847 | ||
|
|
9c25c90758 | ||
|
|
51183bd6ed | ||
|
|
9d924e7199 | ||
|
|
ee4ec2df89 | ||
|
|
d36c16f87f | ||
|
|
c5ee3355ac | ||
|
|
1d6b7c0db1 | ||
|
|
4ae7550144 | ||
|
|
f502ccf2a5 | ||
|
|
c29a5d394c |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -7,6 +7,6 @@ ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: cagnulein
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://www.buymeacoffee.com/cagnulein'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
||||
1520
.github/workflows/main.yml
vendored
1520
.github/workflows/main.yml
vendored
File diff suppressed because it is too large
Load Diff
9
.gitignore
vendored
9
.gitignore
vendored
@@ -25,6 +25,7 @@ src/secret.h
|
||||
|
||||
build-qdomyos-zwift-Android_Qt_5_15_2_Clang_Multi_Abi-Debug/*
|
||||
**/node_modules/*
|
||||
*.pro.user
|
||||
|
||||
template-examples/youtube-viewer/node_modules/*
|
||||
template-examples/youtube-viewer/*.json
|
||||
@@ -39,15 +40,7 @@ template-examples/train-program-saver/.eslintrc.js
|
||||
template-examples/train-program-saver/.jshintrc
|
||||
template-examples/train-program-saver/debug.js
|
||||
|
||||
google_test/*
|
||||
|
||||
# Qt-es
|
||||
*.pro.user
|
||||
*build-*
|
||||
!build-qdomyos-zwift-Qt_*_for_iOS-Debug # Needed for Apple Watch
|
||||
src/inner_templates/googlemaps/cesium-key.js
|
||||
*.autosave
|
||||
.vscode/settings.json
|
||||
/tst/Devices/.vs
|
||||
src/inner_templates/googlemaps/cesium-key.js
|
||||
src/qdomyos-zwift.pro.user.49de507
|
||||
|
||||
14
.gitmodules
vendored
14
.gitmodules
vendored
@@ -3,20 +3,8 @@
|
||||
url = https://github.com/KDAB/android_openssl.git
|
||||
[submodule "src/smtpclient"]
|
||||
path = src/smtpclient
|
||||
url = https://github.com/cagnulein/SmtpClient-for-Qt.git
|
||||
branch = cagnulein-patch-2
|
||||
url = https://github.com/bluetiger9/SmtpClient-for-Qt.git
|
||||
[submodule "src/qmdnsengine"]
|
||||
path = src/qmdnsengine
|
||||
url = https://github.com/cagnulein/qmdnsengine.git
|
||||
branch = zwift
|
||||
[submodule "tst/googletest"]
|
||||
path = tst/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
tag = release-1.12.1
|
||||
[submodule "src/qthttpserver"]
|
||||
path = src/qthttpserver
|
||||
url = https://github.com/qt-labs/qthttpserver
|
||||
[submodule "zwiftplay"]
|
||||
path = zwiftplay
|
||||
url = https://github.com/cagnulein/zwiftplay.git
|
||||
branch = lib
|
||||
|
||||
16
.vscode/launch.json
vendored
16
.vscode/launch.json
vendored
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "(Windows) Launch",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.exe",
|
||||
"symbolSearchPath": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.pdb",
|
||||
"sourceFileMap": {
|
||||
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/",
|
||||
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
roberto.viola83@gmail.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -1,437 +0,0 @@
|
||||
/** NimBLE_Server Demo:
|
||||
*
|
||||
This is working to broadcast Power and Cadence under the Cycling Power Service Profile
|
||||
Data tested against Edge and Phone
|
||||
*
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
short powerInstantaneous = 0;
|
||||
short cadenceInstantaneous = 0;
|
||||
short speedInstantaneous = 0;
|
||||
float powerScale = 1.28; // incoming power is multiplied by this value for correction
|
||||
short resistance = 0; //Not currently doing anything with this value after receiving it
|
||||
bool notify = false;
|
||||
|
||||
// Define stuff for the Client that will receive data from Fitness Machine
|
||||
// The remote service we wish to connect to.
|
||||
static BLEUUID serviceUUID("1826"); // Fitness Machine
|
||||
// The characteristic of the remote service we are interested in.
|
||||
static BLEUUID charUUID("2ad2"); // Indoor Bike (Fitness Machine)
|
||||
|
||||
|
||||
static BLEUUID HRserviceUUID("180D"); // HR Service
|
||||
static BLEUUID HRcharUUID("2a37"); // HR Measuremente
|
||||
|
||||
static boolean doConnect = false;
|
||||
static boolean connected = false;
|
||||
static boolean doScan = false;
|
||||
static BLERemoteCharacteristic *pRemoteCharacteristic;
|
||||
static BLEAdvertisedDevice *myDevice;
|
||||
/*
|
||||
* Server Stuff
|
||||
*/
|
||||
static NimBLEServer *pServer;
|
||||
/** None of these are required as they will be handled by the library with defaults. **
|
||||
** Remove as you see fit for your needs */
|
||||
class ServerCallbacks : public NimBLEServerCallbacks
|
||||
{
|
||||
void onConnect(NimBLEServer *pServer)
|
||||
{
|
||||
Serial.println("Client connected");
|
||||
Serial.println("Multi-connect support: start advertising");
|
||||
NimBLEDevice::startAdvertising();
|
||||
};
|
||||
/** Alternative onConnect() method to extract details of the connection.
|
||||
* See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
|
||||
*/
|
||||
void onConnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
|
||||
{
|
||||
Serial.print("Client address: ");
|
||||
Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
|
||||
/** We can use the connection handle here to ask for different connection parameters.
|
||||
* Args: connection handle, min connection interval, max connection interval
|
||||
* latency, supervision timeout.
|
||||
* Units; Min/Max Intervals: 1.25 millisecond increments.
|
||||
* Latency: number of intervals allowed to skip.
|
||||
* Timeout: 10 millisecond increments, try for 5x interval time for best results.
|
||||
*/
|
||||
pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60);
|
||||
};
|
||||
void onDisconnect(NimBLEServer *pServer)
|
||||
{
|
||||
Serial.println("Client disconnected - start advertising");
|
||||
NimBLEDevice::startAdvertising();
|
||||
};
|
||||
void onMTUChange(uint16_t MTU, ble_gap_conn_desc *desc)
|
||||
{
|
||||
Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
|
||||
};
|
||||
};
|
||||
|
||||
/** Handler class for characteristic actions */
|
||||
class CharacteristicCallbacks : public NimBLECharacteristicCallbacks
|
||||
{
|
||||
void onRead(NimBLECharacteristic *pCharacteristic)
|
||||
{
|
||||
Serial.print(pCharacteristic->getUUID().toString().c_str());
|
||||
Serial.print(": onRead(), value: ");
|
||||
Serial.println(pCharacteristic->getValue().c_str());
|
||||
};
|
||||
|
||||
void onWrite(NimBLECharacteristic *pCharacteristic)
|
||||
{
|
||||
Serial.print(pCharacteristic->getUUID().toString().c_str());
|
||||
Serial.print(": onWrite(), value: ");
|
||||
Serial.println(pCharacteristic->getValue().c_str());
|
||||
};
|
||||
/** Called before notification or indication is sent,
|
||||
* the value can be changed here before sending if desired.
|
||||
*/
|
||||
void onNotify(NimBLECharacteristic *pCharacteristic)
|
||||
{
|
||||
Serial.println("Sending notification to clients");
|
||||
};
|
||||
|
||||
/** The status returned in status is defined in NimBLECharacteristic.h.
|
||||
* The value returned in code is the NimBLE host return code.
|
||||
*/
|
||||
void onStatus(NimBLECharacteristic *pCharacteristic, Status status, int code)
|
||||
{
|
||||
String str = ("Notification/Indication status code: ");
|
||||
str += status;
|
||||
str += ", return code: ";
|
||||
str += code;
|
||||
str += ", ";
|
||||
str += NimBLEUtils::returnCodeToString(code);
|
||||
Serial.println(str);
|
||||
};
|
||||
|
||||
void onSubscribe(NimBLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue)
|
||||
{
|
||||
String str = "Client ID: ";
|
||||
str += desc->conn_handle;
|
||||
str += " Address: ";
|
||||
str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
|
||||
if (subValue == 0)
|
||||
{
|
||||
str += " Unsubscribed to ";
|
||||
}
|
||||
else if (subValue == 1)
|
||||
{
|
||||
str += " Subscribed to notifications for ";
|
||||
}
|
||||
else if (subValue == 2)
|
||||
{
|
||||
str += " Subscribed to indications for ";
|
||||
}
|
||||
else if (subValue == 3)
|
||||
{
|
||||
str += " Subscribed to notifications and indications for ";
|
||||
}
|
||||
str += std::string(pCharacteristic->getUUID()).c_str();
|
||||
|
||||
Serial.println(str);
|
||||
};
|
||||
};
|
||||
|
||||
/** Handler class for descriptor actions */
|
||||
class DescriptorCallbacks : public NimBLEDescriptorCallbacks
|
||||
{
|
||||
void onWrite(NimBLEDescriptor *pDescriptor)
|
||||
{
|
||||
std::string dscVal((char *)pDescriptor->getValue(), pDescriptor->getLength());
|
||||
Serial.print("Descriptor witten value:");
|
||||
Serial.println(dscVal.c_str());
|
||||
};
|
||||
|
||||
void onRead(NimBLEDescriptor *pDescriptor)
|
||||
{
|
||||
Serial.print(pDescriptor->getUUID().toString().c_str());
|
||||
Serial.println(" Descriptor read");
|
||||
};
|
||||
};
|
||||
/*
|
||||
* Client Stuff
|
||||
*/
|
||||
// This callback is for when data is received from Server
|
||||
static void notifyCallback(
|
||||
BLERemoteCharacteristic *pBLERemoteCharacteristic,
|
||||
uint8_t *pData,
|
||||
size_t length,
|
||||
bool isNotify)
|
||||
{
|
||||
powerInstantaneous = pData[8] | pData[9] << 8; // 2 bytes of power
|
||||
cadenceInstantaneous = 60; //(pData[4] | pData[5] << 8) / 2; // 2 bytes of power in 0.5 resolution RPM, convert to RPM
|
||||
resistance = pData[6]; // 1 byte of resistance
|
||||
Serial.printf("Power = %d | Cadence = %d | Resistance = %d\n", powerInstantaneous, cadenceInstantaneous, resistance);
|
||||
}
|
||||
|
||||
/** None of these are required as they will be handled by the library with defaults. **
|
||||
** Remove as you see fit for your needs */
|
||||
class MyClientCallback : public BLEClientCallbacks
|
||||
{
|
||||
void onConnect(BLEClient *pclient)
|
||||
{
|
||||
}
|
||||
|
||||
void onDisconnect(BLEClient *pclient)
|
||||
{
|
||||
connected = false;
|
||||
Serial.println("onDisconnect");
|
||||
}
|
||||
};
|
||||
|
||||
bool connectToServer()
|
||||
{
|
||||
Serial.print("Forming a connection to ");
|
||||
Serial.println(myDevice->getAddress().toString().c_str());
|
||||
|
||||
BLEClient *pClient = BLEDevice::createClient();
|
||||
Serial.println(" - Created client");
|
||||
|
||||
pClient->setClientCallbacks(new MyClientCallback());
|
||||
|
||||
// Connect to the remove BLE Server.
|
||||
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
|
||||
Serial.println(" - Connected to server");
|
||||
|
||||
// Obtain a reference to the service we are after in the remote BLE server.
|
||||
BLERemoteService *pRemoteService = pClient->getService(serviceUUID);
|
||||
if (pRemoteService == nullptr)
|
||||
{
|
||||
Serial.print("Failed to find our service UUID: ");
|
||||
Serial.println(serviceUUID.toString().c_str());
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
Serial.println(" - Found our service");
|
||||
|
||||
// Obtain a reference to the characteristic in the service of the remote BLE server.
|
||||
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
|
||||
if (pRemoteCharacteristic == nullptr)
|
||||
{
|
||||
Serial.print("Failed to find our characteristic UUID: ");
|
||||
Serial.println(charUUID.toString().c_str());
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
Serial.println(" - Found our characteristic");
|
||||
|
||||
// Read the value of the characteristic.
|
||||
if (pRemoteCharacteristic->canRead())
|
||||
{
|
||||
std::string value = pRemoteCharacteristic->readValue();
|
||||
Serial.print("The characteristic value was: ");
|
||||
Serial.println(value.c_str());
|
||||
}
|
||||
|
||||
if (pRemoteCharacteristic->canNotify())
|
||||
pRemoteCharacteristic->registerForNotify(notifyCallback);
|
||||
|
||||
connected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan for BLE servers and find the first one that advertises the service we are looking for.
|
||||
*/
|
||||
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
|
||||
{
|
||||
/**
|
||||
* Called for each advertising BLE server.
|
||||
*/
|
||||
|
||||
/*** Only a reference to the advertised device is passed now
|
||||
void onResult(BLEAdvertisedDevice advertisedDevice) { **/
|
||||
void onResult(BLEAdvertisedDevice *advertisedDevice)
|
||||
{
|
||||
Serial.print("BLE Advertised Device found: ");
|
||||
Serial.println(advertisedDevice->toString().c_str());
|
||||
|
||||
// We have found a device, let us now see if it contains the service we are looking for.
|
||||
/********************************************************************************
|
||||
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
|
||||
********************************************************************************/
|
||||
if (advertisedDevice->haveServiceUUID() && advertisedDevice->isAdvertisingService(serviceUUID))
|
||||
{
|
||||
|
||||
BLEDevice::getScan()->stop();
|
||||
/*******************************************************************
|
||||
myDevice = new BLEAdvertisedDevice(advertisedDevice);
|
||||
*******************************************************************/
|
||||
myDevice = advertisedDevice; /** Just save the reference now, no need to copy the object */
|
||||
doConnect = true;
|
||||
doScan = true;
|
||||
|
||||
} // Found our server
|
||||
} // onResult
|
||||
}; // MyAdvertisedDeviceCallbacks
|
||||
|
||||
//delays for X ms, should not block execution
|
||||
void softDelay(unsigned long delayTime)
|
||||
{
|
||||
unsigned long startTime = millis();
|
||||
while ((millis() - startTime) < delayTime)
|
||||
{
|
||||
//wait
|
||||
}
|
||||
}
|
||||
|
||||
/** Define callback instances globally to use for multiple Characteristics \ Descriptors */
|
||||
// This section is for the Server that will broadcast the data as Cycling Power
|
||||
static DescriptorCallbacks dscCallbacks;
|
||||
static CharacteristicCallbacks chrCallbacks;
|
||||
NimBLECharacteristic *CyclingPowerFeature = NULL;
|
||||
NimBLECharacteristic *CyclingPowerMeasurement = NULL;
|
||||
NimBLECharacteristic *CyclingPowerSensorLocation = NULL;
|
||||
NimBLECharacteristic *HRMeasurement = NULL;
|
||||
unsigned char bleBuffer[8];
|
||||
unsigned char slBuffer[1];
|
||||
unsigned char fBuffer[4];
|
||||
unsigned short revolutions = 0;
|
||||
unsigned short timestamp = 0;
|
||||
unsigned short flags = 0x20;
|
||||
byte sensorlocation = 0x0D;
|
||||
long lastNotify = 0;
|
||||
long lastRevolution = 0;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.println("Starting NimBLE Server");
|
||||
|
||||
/** sets device name */
|
||||
NimBLEDevice::init("QZESP");
|
||||
/** Optional: set the transmit power, default is 3db */
|
||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
|
||||
|
||||
pServer = NimBLEDevice::createServer();
|
||||
pServer->setCallbacks(new ServerCallbacks());
|
||||
|
||||
fBuffer[0] = 0x00;
|
||||
fBuffer[1] = 0x00;
|
||||
fBuffer[2] = 0x00;
|
||||
fBuffer[3] = 0x08;
|
||||
|
||||
slBuffer[0] = sensorlocation & 0xff;
|
||||
|
||||
NimBLEService *pDeadService = pServer->createService("1818");
|
||||
CyclingPowerFeature = pDeadService->createCharacteristic(
|
||||
"2A65",
|
||||
NIMBLE_PROPERTY::READ);
|
||||
CyclingPowerSensorLocation = pDeadService->createCharacteristic(
|
||||
"2A5D",
|
||||
NIMBLE_PROPERTY::READ);
|
||||
CyclingPowerMeasurement = pDeadService->createCharacteristic(
|
||||
"2A63",
|
||||
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
|
||||
|
||||
CyclingPowerFeature->setValue(fBuffer, 4);
|
||||
CyclingPowerSensorLocation->setValue(slBuffer, 1);
|
||||
CyclingPowerMeasurement->setValue(slBuffer, 1);
|
||||
|
||||
/** Start the services when finished creating all Characteristics and Descriptors */
|
||||
pDeadService->start();
|
||||
|
||||
#if 0
|
||||
// HR service
|
||||
NimBLEService *pHRService = pServer->createService("180D");
|
||||
HRMeasurement = pHRService->createCharacteristic(
|
||||
"2A37",
|
||||
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
|
||||
|
||||
HRMeasurement->setValue(fBuffer, 2);
|
||||
|
||||
/** Start the services when finished creating all Characteristics and Descriptors */
|
||||
pHRService->start();
|
||||
#endif
|
||||
|
||||
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
|
||||
/** Add the services to the advertisement data **/
|
||||
// pAdvertising->addServiceUUID(pHRService->getUUID());
|
||||
pAdvertising->addServiceUUID(pDeadService->getUUID());
|
||||
pAdvertising->setScanResponse(true);
|
||||
pAdvertising->start();
|
||||
|
||||
Serial.println("Advertising Started");
|
||||
|
||||
Serial.println("Starting Arduino BLE Client application...");
|
||||
BLEDevice::init("");
|
||||
|
||||
// Retrieve a Scanner and set the callback we want to use to be informed when we
|
||||
// have detected a new device. Specify that we want active scanning and start the
|
||||
// scan to run for 5 seconds.
|
||||
BLEScan *pBLEScan = BLEDevice::getScan();
|
||||
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
|
||||
pBLEScan->setInterval(1349);
|
||||
pBLEScan->setWindow(449);
|
||||
pBLEScan->setActiveScan(true);
|
||||
pBLEScan->start(5, false);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// If the flag "doConnect" is true then we have scanned for and found the desired
|
||||
// BLE Server with which we wish to connect. Now we connect to it. Once we are
|
||||
// connected we set the connected flag to be true.
|
||||
if (doConnect == true)
|
||||
{
|
||||
if (connectToServer())
|
||||
{
|
||||
Serial.println("We are now connected to the BLE Server.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("We have failed to connect to the server; there is nothing more we will do.");
|
||||
}
|
||||
doConnect = false;
|
||||
}
|
||||
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
|
||||
// with the current time since boot.
|
||||
if (connected)
|
||||
{
|
||||
//Stuff to do when connected to Client
|
||||
}
|
||||
else if (doScan)
|
||||
{
|
||||
BLEDevice::getScan()->start(0); // this is just sample to start scan after disconnect, most likely there is better way to do it in arduino
|
||||
}
|
||||
|
||||
// convert RPM to timestamp
|
||||
if (cadenceInstantaneous != 0 && (millis()) >= (lastRevolution + (60000 / cadenceInstantaneous)))
|
||||
{
|
||||
revolutions++; // One crank revolution should have passed, add one revolution
|
||||
timestamp = (unsigned short)(((millis() * 1024) / 1000) % 65536); // create timestamp and format
|
||||
lastRevolution = millis();
|
||||
}
|
||||
|
||||
if (millis() - lastNotify >= 1000) // do this every second
|
||||
{
|
||||
//if (pServer->getConnectedCount() > 0)
|
||||
{
|
||||
bleBuffer[0] = flags & 0xff;
|
||||
bleBuffer[1] = (flags >> 8) & 0xff;
|
||||
bleBuffer[2] = powerInstantaneous & 0xff;
|
||||
bleBuffer[3] = (powerInstantaneous >> 8) & 0xff;
|
||||
bleBuffer[4] = revolutions & 0xff;
|
||||
bleBuffer[5] = (revolutions >> 8) & 0xff;
|
||||
bleBuffer[6] = timestamp & 0xff;
|
||||
bleBuffer[7] = (timestamp >> 8) & 0xff;
|
||||
CyclingPowerMeasurement->setValue(bleBuffer, 8);
|
||||
CyclingPowerMeasurement->notify();
|
||||
|
||||
/*bleBuffer[0] = 0;
|
||||
bleBuffer[1] = powerInstantaneous;
|
||||
|
||||
HRMeasurement->setValue(bleBuffer, 2);
|
||||
HRMeasurement->notify();*/
|
||||
lastNotify = millis();
|
||||
}
|
||||
}
|
||||
/*if (pServer->getConnectedCount() == 0)
|
||||
{
|
||||
powerInstantaneous = 0;
|
||||
}*/
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
#define INDOOR_BIKE_DATA_UUID "00002AD2-0000-1000-8000-00805f9b34fb"
|
||||
#define CUSTOM_SERVICE_UUID "ce060000-43e5-11e4-916c-0800200c9a66"
|
||||
|
||||
NimBLEServer* pServer = nullptr;
|
||||
NimBLECharacteristic* pIndoorBikeDataChar = nullptr;
|
||||
|
||||
class ServerCallbacks: public NimBLEServerCallbacks {
|
||||
void onConnect(NimBLEServer* pServer) {
|
||||
Serial.println("Client connected");
|
||||
};
|
||||
|
||||
void onDisconnect(NimBLEServer* pServer) {
|
||||
Serial.println("Client disconnected");
|
||||
}
|
||||
};
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Starting NimBLE Server");
|
||||
|
||||
NimBLEDevice::init("PM5 431431183 Row");
|
||||
|
||||
pServer = NimBLEDevice::createServer();
|
||||
pServer->setCallbacks(new ServerCallbacks());
|
||||
|
||||
NimBLEService* pFtmService = pServer->createService("1826");
|
||||
//NimBLEService* pCustomService = pServer->createService(CUSTOM_SERVICE_UUID);
|
||||
|
||||
pIndoorBikeDataChar = pFtmService->createCharacteristic(
|
||||
INDOOR_BIKE_DATA_UUID,
|
||||
NIMBLE_PROPERTY::READ |
|
||||
NIMBLE_PROPERTY::NOTIFY
|
||||
);
|
||||
|
||||
pFtmService->start();
|
||||
//pCustomService->start();
|
||||
|
||||
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
|
||||
pAdvertising->addServiceUUID(pFtmService->getUUID());
|
||||
//pAdvertising->addServiceUUID(CUSTOM_SERVICE_UUID);
|
||||
const std::string data = { 0x01, 0x10, 0x00 }; // Imposta i valori desiderati
|
||||
pAdvertising->setServiceData(pFtmService->getUUID(), data);
|
||||
pAdvertising->start();
|
||||
|
||||
Serial.println("Advertising started");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Metti qui il tuo codice principale, da eseguire ripetutamente
|
||||
// Ad esempio, potresti aggiornare il valore della caratteristica Indoor Bike Data
|
||||
}
|
||||
131
README.md
131
README.md
@@ -7,125 +7,58 @@ Zwift bridge for Treadmills and Bike!
|
||||
[<img src="docs/img/app_store.png">](https://apps.apple.com/app/id1543684531?fbclid=IwAR10H6y3mEgwkTlGJON3e8voYOh2wt3kLFOpFzoIXaYZ_N0y0pDvKxHMUaM)
|
||||
<a href="https://www.buymeacoffee.com/cagnulein" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot1.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot2.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot3.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot4.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot5.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||

|
||||
|
||||
[](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
|
||||
|
||||

|
||||

|
||||
|
||||
UI on Linux
|
||||
|
||||

|
||||
|
||||
UI on MacOS
|
||||
|
||||
### Features
|
||||
|
||||
# UI Features
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Tiles Customization|X|X|X|X|Order and visibility of each tile|
|
||||
|Profiles|X|X|X|X|Different user or different fitness device profiles|
|
||||
|UI Zoom Customization|X|X|X|X||
|
||||
|
||||
# Peloton Features
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Bike metrics on the peloton app|X||X|||
|
||||
|Power zone with auto resistance|X|||||
|
||||
|Peloton real-time resistance conversion|X||X||with the possibility to customize it|
|
||||
|Peloton real-time auto-resistance|X||X||with the possibility to customize it|
|
||||
|Peloton auto speed and auto inclination||X|X||with the possibility to customize it|
|
||||
|
||||
# Heart Rate Features
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Heart Rate support|X|X|X|X|Apple Watch, ANT+ devices and Bluetooth devices|
|
||||
|Heart Rate Zones Customizations|X|X|X|X||
|
||||
|Ability to calculate Wattage from HR and Cadence|X||||for the bikes that doesn't have a power sensor|
|
||||
|
||||
# 3rd Apps Compatibility
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Zwift Compatibility|X|X|X|X||
|
||||
|Zwift Auto resistance|X||X|||
|
||||
|Zwift Auto inclination and speed||X|X||https://www.youtube.com/watch?v=KTQ2n7yeDbo|
|
||||
|Wahoo RGT Compatibility|X|X|X|X||
|
||||
|VzFit Compatibility|X|X|X|X||
|
||||
|Rouvy Compatibility|X|X|X|X||
|
||||
|IFIT app Compatibility|X|||||
|
||||
|Echelon app Compatibility|X|||||
|
||||
|Wahoo Dircon Compatibility|X|X|X|X|in order to send data to Zwift or RGT with Wifi only!|
|
||||
|One device only support for Zwift and Wahoo RGT|X|X|X|X|using Wahoo Dircon https://www.youtube.com/watch?v=gYYUXNWFAok|
|
||||
|BitGym Compatibility|X|X|X|X||
|
||||
|
||||
# Training Program
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Builtin video support (Kinomap like)|X|X|X|X|Files could be local or on the cloud!|
|
||||
|GPX auto following|X|X|X|X||
|
||||
|2D/3D maps for GPX|X|X|X|X||
|
||||
|ZWO (Zwift workout file) compatibility|X|X|X|X||
|
||||
|XML Workout file compatibility|X|X|X|X||
|
||||
|Auto follow workout based on your heart rate|X|X|X|X||
|
||||
|Random workout|X|X|X|X||
|
||||
|
||||
|
||||
# Statistics
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|E-Mail report|X|X|X|X|at the end of the workout|
|
||||
|Strava integration|X|X|X|X|press stop at the end of the workout to auto upload it|
|
||||
|
||||
# Misc
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Resistance shifting with bluetooth remote|X||X|||
|
||||
|TTS support|X|X|X|X||
|
||||
|Zwift Play & Click support|X|||||
|
||||
|MQTT integration|X|X|X|X||
|
||||
|OpenSoundControl integration|X|X|X|X||
|
||||
1. Domyos compatible
|
||||
2. Toorx TRX Route Key compatible
|
||||
3. Echelon Connect Sport compatible
|
||||
4. Zwift compatible
|
||||
5. Create, load and save train programs
|
||||
6. Measure distance, elevation gain and watts
|
||||
7. Gpx import (with difficulty slider)
|
||||
8. Realtime Charts
|
||||
|
||||

|
||||
|
||||
### Installation
|
||||
|
||||
You can install it on multiple platforms.
|
||||
Read the [installation procedure](docs/10_Installation.md)
|
||||
You can install on multiple platforms.
|
||||
Read the [installation procedure](docs/10_Installation.md)
|
||||
|
||||
|
||||
### Tested on
|
||||
|
||||
The QDomyos-Zwift application can run on [Macintosh or Linux devices](docs/10_Installation.md) iOS, and Android.
|
||||
It supports any [FTMS-compatible application](docs/20_supported_devices_and_applications.md) software and most [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
|
||||
You can run the app on [Macintosh or Linux devices](docs/10_Installation.md). IOS and Android are also supported.
|
||||
|
||||
### No GUI version
|
||||
QDomyos-Zwift works on every [FTMS-compatible application](docs/20_supported_devices_and_applications.md), and virtually any [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
|
||||
|
||||
### No gui version
|
||||
|
||||
run as
|
||||
|
||||
$ sudo ./qdomyos-zwift -no-gui
|
||||
$ sudo ./qdomyos-zwift -no-gui
|
||||
|
||||
### Reference
|
||||
|
||||
=> GitHub Repository: [QDomyos-Zwift on GitHub](https://github.com/ProH4Ck/treadmill-bridge)
|
||||
https://github.com/ProH4Ck/treadmill-bridge
|
||||
|
||||
=> Treadmill Incline Reference: [What Is 10 Degrees in Incline on a Treadmill?](https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/)
|
||||
https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/
|
||||
|
||||
=> Icon Attribution: Icons used in this documentation are from [Flaticon.com](https://www.flaticon.com)
|
||||
Icons used in this documentation comes from [flaticon.com](https://www.flaticon.com)
|
||||
|
||||
### Blog
|
||||
|
||||
=> Related Blog: [Roberto Viola's Blog](https://robertoviola.cloud)
|
||||
https://robertoviola.cloud
|
||||
|
||||
@@ -2,4 +2,3 @@
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "swiftDebug.h"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,17 +53,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_accessibility_support_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bodymovin_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bootstrap_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_clipboard_support_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_devicediscovery_support_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_edid_support_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_eventdispatcher_support_private.pri \
|
||||
@@ -76,9 +71,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_gui_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_httpserver.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_macextras.pri \
|
||||
@@ -103,8 +95,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioning_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmldebug_private.pri \
|
||||
@@ -117,16 +107,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmlworkerscript_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2_private.pri \
|
||||
@@ -140,21 +120,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_remoteobjects_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sslserver.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_testlib.pri \
|
||||
@@ -165,8 +136,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uiplugin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel_private.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_websockets.pri \
|
||||
@@ -225,26 +194,15 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtiff.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmedia_audioengine.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmultimedia_m3u.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpassthrucanbus.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpeakcanbus.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_cl.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_positionpoll.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_plugin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_shakeplugin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_generic.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_ios.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qttinycanbus.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtuiotouchplugin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualcanbus.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_hangul.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_openwnn.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_pinyin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_tcime.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_thai.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboardplugin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtwebview_darwin.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwbmp.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebgl.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebp.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_scene2d.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/qt_functions.prf \
|
||||
@@ -261,9 +219,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/features/default_pre.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/mac/default_pre.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_pre.prf \
|
||||
../defaults.pri \
|
||||
../src/purchasing/purchasing.pri \
|
||||
../src/qdomyos-zwift.pri \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/resolve_config.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/resolve_config.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/default_post.prf \
|
||||
@@ -271,9 +226,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_post.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/macx-ios-clang/features/default_post.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/mac/objective_c.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/qmltypes.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/metatypes.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/ltcg.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/qml_debug.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/mac/mac.prf \
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/bitcode.prf \
|
||||
@@ -312,18 +264,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/lib/libqtharfbuzz_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Core_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libqtpcre2_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfmediaplayer_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_esri_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_itemsoverlay_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapbox_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapboxgl_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_nokia_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_osm_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfcamera_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/mediaservice/libqtmedia_audioengine_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/audio/libqtaudio_coreaudio_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/playlistformats/libqtmultimedia_m3u_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/imageformats/libqgif_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/imageformats/libqicns_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/imageformats/libqico_debug.prl \
|
||||
@@ -348,51 +288,32 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_server_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_tcp_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/bearer/libqgenericbearer_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios_debug.prl \
|
||||
../../Qt/5.15.2/ios/plugins/sqldrivers/libqsqlite_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5HttpServer_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5SslServer_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Charts_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Widgets_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Location_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5PositioningQuick_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5QuickControls2_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Quick_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Multimedia_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5WebView_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Bluetooth_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Xml_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Positioning_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5QmlModels_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Qml_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5NetworkAuth_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5WebSockets_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Network_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5TextToSpeech_debug.prl \
|
||||
../../Qt/5.15.2/ios/lib/libQt5Concurrent_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick.2/libqtquick2plugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/libqtquickcontrols2plugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQml/libqmlplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Templates.2/libqtquicktemplates2plugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQml/Models.2/libmodelsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQml/WorkerScript.2/libworkerscriptplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtWebView/libdeclarative_webview_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtCharts/libqtchartsqml2_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/libdialogplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtPositioning/libdeclarative_positioning_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtLocation/libdeclarative_location_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/Private/libdialogsprivateplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls/libqtquickcontrolsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/PrivateWidgets/libwidgetsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/platform/libqtlabsplatformplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtMultimedia/libdeclarative_multimedia_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Fusion/libqtquickcontrols2fusionstyleplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Universal/libqtquickcontrols2universalstyleplugin_debug.prl \
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Imagine/libqtquickcontrols2imaginestyleplugin_debug.prl
|
||||
@@ -440,17 +361,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_accessibility_support_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bluetooth_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bodymovin_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_bootstrap_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_charts_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_clipboard_support_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_concurrent_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_core_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_datavisualization_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_devicediscovery_support_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_edid_support_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_eventdispatcher_support_private.pri:
|
||||
@@ -463,9 +379,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_gui_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_help_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_httpserver_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_httpserver.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_location_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_macextras.pri:
|
||||
@@ -490,8 +403,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioning_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_positioningquick_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_purchasing_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qml_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmldebug_private.pri:
|
||||
@@ -504,16 +415,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qmlworkerscript_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_qtmultimediaquicktools_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3d_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dassetimport_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3drender_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3druntimerender_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick3dutils_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quick_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_quickcontrols2_private.pri:
|
||||
@@ -527,21 +428,12 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_remoteobjects_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_repparser_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_script_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scripttools_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_scxml_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sensors_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_serialbus_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sql_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules-inst/qt_lib_sslserver_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_sslserver.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_svg_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_testlib.pri:
|
||||
@@ -552,8 +444,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uiplugin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_uitools_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_virtualkeyboard_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_webchannel_private.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_lib_websockets.pri:
|
||||
@@ -612,26 +502,15 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtiff.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmedia_audioengine.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtmultimedia_m3u.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpassthrucanbus.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtpeakcanbus.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_cl.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtposition_positionpoll.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_plugin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensorgestures_shakeplugin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_generic.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtsensors_ios.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qttinycanbus.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtuiotouchplugin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualcanbus.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_hangul.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_openwnn.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_pinyin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_tcime.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboard_thai.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtvirtualkeyboardplugin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qtwebview_darwin.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwbmp.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebgl.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_qwebp.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/modules/qt_plugin_scene2d.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/qt_functions.prf:
|
||||
@@ -648,9 +527,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/features/default_pre.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/mac/default_pre.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_pre.prf:
|
||||
../defaults.pri:
|
||||
../src/purchasing/purchasing.pri:
|
||||
../src/qdomyos-zwift.pri:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/resolve_config.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/resolve_config.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/default_post.prf:
|
||||
@@ -658,9 +534,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/default_post.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/macx-ios-clang/features/default_post.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/mac/objective_c.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/qmltypes.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/metatypes.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/ltcg.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/qml_debug.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/mac/mac.prf:
|
||||
../../Qt/5.15.2/ios/mkspecs/features/uikit/bitcode.prf:
|
||||
@@ -699,18 +572,6 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/lib/libqtharfbuzz_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Core_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libqtpcre2_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfmediaplayer_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_esri_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_itemsoverlay_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapbox_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_mapboxgl_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_nokia_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/geoservices/libqtgeoservices_osm_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/mediaservice/libqavfcamera_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/mediaservice/libqtmedia_audioengine_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/audio/libqtaudio_coreaudio_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/playlistformats/libqtmultimedia_m3u_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/imageformats/libqgif_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/imageformats/libqicns_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/imageformats/libqico_debug.prl:
|
||||
@@ -735,51 +596,32 @@ qdomyoszwift.xcodeproj/project.pbxproj: ../src/qdomyos-zwift.pro ../../Qt/5.15.2
|
||||
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_server_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/qmltooling/libqmldbg_tcp_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/bearer/libqgenericbearer_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios_debug.prl:
|
||||
../../Qt/5.15.2/ios/plugins/sqldrivers/libqsqlite_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5HttpServer_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5SslServer_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Charts_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Widgets_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Location_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5PositioningQuick_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5QuickControls2_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Quick_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Multimedia_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5WebView_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Bluetooth_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Xml_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Positioning_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5QmlModels_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Qml_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5NetworkAuth_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5WebSockets_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Network_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5TextToSpeech_debug.prl:
|
||||
../../Qt/5.15.2/ios/lib/libQt5Concurrent_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick.2/libqtquick2plugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/libqtquickcontrols2plugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQml/libqmlplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Templates.2/libqtquicktemplates2plugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQml/Models.2/libmodelsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQml/WorkerScript.2/libworkerscriptplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Window.2/libwindowplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Material/libqtquickcontrols2materialstyleplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtWebView/libdeclarative_webview_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtCharts/libqtchartsqml2_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/libdialogplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtPositioning/libdeclarative_positioning_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtLocation/libdeclarative_location_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/folderlistmodel/libqmlfolderlistmodelplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/settings/libqmlsettingsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Dialogs/Private/libdialogsprivateplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls/libqtquickcontrolsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/PrivateWidgets/libwidgetsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/libqtgraphicaleffectsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/Qt/labs/platform/libqtlabsplatformplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtMultimedia/libdeclarative_multimedia_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtGraphicalEffects/private/libqtgraphicaleffectsprivate_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Layouts/libqquicklayoutsplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Fusion/libqtquickcontrols2fusionstyleplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Universal/libqtquickcontrols2universalstyleplugin_debug.prl:
|
||||
../../Qt/5.15.2/ios/qml/QtQuick/Controls.2/Imagine/libqtquickcontrols2imaginestyleplugin_debug.prl:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,10 +54,8 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/qdomyoszwift">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "876E4E102594747F00BD5714"
|
||||
@@ -65,7 +63,7 @@
|
||||
BlueprintName = "watchkit"
|
||||
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -73,10 +71,8 @@
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<RemoteRunnable
|
||||
runnableDebuggingMode = "2"
|
||||
BundleIdentifier = "com.apple.Carousel"
|
||||
RemotePath = "/qdomyoszwift">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "876E4E102594747F00BD5714"
|
||||
@@ -84,14 +80,7 @@
|
||||
BlueprintName = "watchkit"
|
||||
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</RemoteRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableName = "watchkit.app"
|
||||
BlueprintName = "watchkit"
|
||||
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
|
||||
@@ -17,33 +17,16 @@ class MainController: WKInterfaceController {
|
||||
@IBOutlet weak var distanceLabel: WKInterfaceLabel!
|
||||
@IBOutlet weak var heartRateLabel: WKInterfaceLabel!
|
||||
@IBOutlet weak var startButton: WKInterfaceButton!
|
||||
@IBOutlet weak var cmbSports: WKInterfacePicker!
|
||||
static var start: Bool! = false
|
||||
let pedometer = CMPedometer()
|
||||
var sport: Int = 0
|
||||
|
||||
override func awake(withContext context: Any?) {
|
||||
super.awake(withContext: context)
|
||||
let sports: [WKPickerItem] = [WKPickerItem(),WKPickerItem(),WKPickerItem(),WKPickerItem(),WKPickerItem()]
|
||||
sports[0].title = "Bike"
|
||||
sports[1].title = "Run"
|
||||
sports[2].title = "Walk"
|
||||
sports[3].title = "Elliptical"
|
||||
sports[4].title = "Rowing"
|
||||
cmbSports.setItems(sports)
|
||||
sport = UserDefaults.standard.value(forKey: "sport") as? Int ?? 0
|
||||
cmbSports.setSelectedItemIndex(sport)
|
||||
|
||||
// Configure interface objects here.
|
||||
print("AWAKE")
|
||||
}
|
||||
|
||||
@IBAction func changeSport(_ value: Int) {
|
||||
self.sport = value
|
||||
UserDefaults.standard.set(value, forKey: "sport")
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
override func willActivate() {
|
||||
// This method is called when watch view controller is about to be visible to user
|
||||
super.willActivate()
|
||||
@@ -74,7 +57,6 @@ extension MainController {
|
||||
MainController.start = true
|
||||
startButton.setTitle("Stop")
|
||||
WorkoutTracking.authorizeHealthKit()
|
||||
WorkoutTracking.shared.setSport(sport)
|
||||
WorkoutTracking.shared.startWorkOut()
|
||||
WorkoutTracking.shared.delegate = self
|
||||
|
||||
@@ -104,16 +86,8 @@ extension MainController: WorkoutTrackingDelegate {
|
||||
"\(heartRate)" as AnyObject])
|
||||
WorkoutTracking.distance = WatchKitConnection.distance
|
||||
WorkoutTracking.kcal = WatchKitConnection.kcal
|
||||
WorkoutTracking.speed = WatchKitConnection.speed
|
||||
WorkoutTracking.power = WatchKitConnection.power
|
||||
WorkoutTracking.cadence = WatchKitConnection.cadence
|
||||
WorkoutTracking.steps = WatchKitConnection.steps
|
||||
|
||||
if Locale.current.measurementSystem != "Metric" {
|
||||
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance))")
|
||||
} else {
|
||||
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance * 1.60934))")
|
||||
}
|
||||
|
||||
self.distanceLabel.setText("Distance \(Double(WorkoutTracking.distance))")
|
||||
self.caloriesLabel.setText("KCal \(Int(WorkoutTracking.kcal))")
|
||||
//WorkoutTracking.cadenceSteps = pedometer.
|
||||
}
|
||||
@@ -131,11 +105,3 @@ extension MainController: WatchKitConnectionDelegate {
|
||||
userNameLabel.setText(userName)
|
||||
}
|
||||
}
|
||||
|
||||
extension Locale
|
||||
{
|
||||
var measurementSystem : String?
|
||||
{
|
||||
return (self as NSLocale).object(forKey: NSLocale.Key.measurementSystem) as? String
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -24,10 +24,6 @@ class WatchKitConnection: NSObject {
|
||||
public static var distance = 0.0
|
||||
public static var kcal = 0.0
|
||||
public static var stepCadence = 0
|
||||
public static var speed = 0.0
|
||||
public static var cadence = 0.0
|
||||
public static var power = 0.0
|
||||
public static var steps = 0
|
||||
weak var delegate: WatchKitConnectionDelegate?
|
||||
|
||||
private override init() {
|
||||
@@ -70,17 +66,6 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
|
||||
WatchKitConnection.distance = dDistance
|
||||
let dKcal = Double(result["kcal"] as! Double)
|
||||
WatchKitConnection.kcal = dKcal
|
||||
|
||||
let dSpeed = Double(result["speed"] as! Double)
|
||||
WatchKitConnection.speed = dSpeed
|
||||
let dPower = Double(result["power"] as! Double)
|
||||
WatchKitConnection.power = dPower
|
||||
let dCadence = Double(result["cadence"] as! Double)
|
||||
WatchKitConnection.cadence = dCadence
|
||||
if let stepsDouble = result["steps"] as? Double {
|
||||
let iSteps = Int(stepsDouble)
|
||||
WatchKitConnection.steps = iSteps
|
||||
}
|
||||
}, errorHandler: { (error) in
|
||||
print(error)
|
||||
})
|
||||
|
||||
@@ -31,12 +31,6 @@ class WorkoutTracking: NSObject {
|
||||
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
|
||||
public static var cadenceLastSteps = Double()
|
||||
public static var cadenceSteps = 0
|
||||
public static var speed = Double()
|
||||
public static var power = Double()
|
||||
public static var steps = Int()
|
||||
public static var cadence = Double()
|
||||
public static var lastDateMetric = Date()
|
||||
var sport: Int = 0
|
||||
let healthStore = HKHealthStore()
|
||||
let configuration = HKWorkoutConfiguration()
|
||||
var workoutSession: HKWorkoutSession!
|
||||
@@ -107,23 +101,8 @@ extension WorkoutTracking {
|
||||
}
|
||||
}
|
||||
|
||||
func setSport(_ sport: Int) {
|
||||
self.sport = sport
|
||||
}
|
||||
|
||||
private func configWorkout() {
|
||||
var activityType = HKWorkoutActivityType.cycling
|
||||
if self.sport == 1 {
|
||||
activityType = HKWorkoutActivityType.running
|
||||
} else if self.sport == 2 {
|
||||
activityType = HKWorkoutActivityType.walking
|
||||
} else if self.sport == 3 {
|
||||
activityType = HKWorkoutActivityType.elliptical
|
||||
} else if self.sport == 4 {
|
||||
activityType = HKWorkoutActivityType.rowing
|
||||
}
|
||||
|
||||
configuration.activityType = activityType
|
||||
configuration.activityType = .cycling
|
||||
configuration.locationType = .indoor
|
||||
|
||||
do {
|
||||
@@ -151,37 +130,13 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
|
||||
var infoToShare: Set<HKSampleType> = []
|
||||
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningStrideLength)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningVerticalOscillation)!,
|
||||
HKSampleType.quantityType(forIdentifier: .walkingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
}
|
||||
let infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
|
||||
HKHealthStore().requestAuthorization(toShare: infoToShare, read: infoToRead) { (success, error) in
|
||||
if success {
|
||||
@@ -196,7 +151,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
}
|
||||
|
||||
func startWorkOut() {
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
print("Start workout")
|
||||
configWorkout()
|
||||
workoutSession.startActivity(with: Date())
|
||||
@@ -205,10 +159,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
if self.sport > 0 {
|
||||
self.workoutBuilder.dataSource?.enableCollection(for: HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!, predicate: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,170 +183,29 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sample]) {(success, error) in}
|
||||
|
||||
guard let quantityTypeDistance = HKQuantityType.quantityType(
|
||||
forIdentifier: .distanceCycling) else {
|
||||
return
|
||||
}
|
||||
|
||||
let unitDistance = HKUnit.mile()
|
||||
let miles = WorkoutTracking.distance
|
||||
let quantityMiles = HKQuantity(unit: unitDistance,
|
||||
doubleValue: miles)
|
||||
|
||||
if(sport == 0) {
|
||||
|
||||
guard let quantityTypeDistance = HKQuantityType.quantityType(
|
||||
forIdentifier: .distanceCycling) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
|
||||
quantity: quantityMiles,
|
||||
start: workoutSession.startDate!,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sampleDistance]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.finishWorkout{ (workout, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
workout?.setValue(quantityMiles, forKey: "totalDistance")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(sport == 4) { // Rowing
|
||||
// Guard to check if steps quantity type is available
|
||||
guard let quantityTypeSteps = HKQuantityType.quantityType(
|
||||
forIdentifier: .stepCount) else {
|
||||
return
|
||||
}
|
||||
|
||||
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
|
||||
|
||||
// Create a sample for total steps
|
||||
let sampleSteps = HKCumulativeQuantitySeriesSample(
|
||||
type: quantityTypeSteps,
|
||||
quantity: stepsQuantity,
|
||||
start: workoutSession.startDate!,
|
||||
end: Date())
|
||||
|
||||
// Add the steps sample to workout builder
|
||||
workoutBuilder.add([sampleSteps]) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Per il rowing, HealthKit utilizza un tipo specifico di distanza
|
||||
// Se non esiste un tipo specifico per il rowing, possiamo usare un tipo generico di distanza
|
||||
var quantityTypeDistance: HKQuantityType?
|
||||
|
||||
// In watchOS 10 e versioni successive, possiamo usare un tipo specifico se disponibile
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
// Verifica se esiste un tipo specifico per il rowing, altrimenti utilizza un tipo generico
|
||||
quantityTypeDistance = HKQuantityType.quantityType(forIdentifier: .distanceSwimming)
|
||||
} else {
|
||||
// Nelle versioni precedenti, usa il tipo generico
|
||||
quantityTypeDistance = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)
|
||||
}
|
||||
|
||||
guard let typeDistance = quantityTypeDistance else {
|
||||
return
|
||||
}
|
||||
|
||||
let sampleDistance = HKCumulativeQuantitySeriesSample(type: typeDistance,
|
||||
quantity: quantityMiles,
|
||||
start: workoutSession.startDate!,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sampleDistance]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.finishWorkout{ (workout, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
workout?.setValue(quantityMiles, forKey: "totalDistance")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Guard to check if steps quantity type is available
|
||||
guard let quantityTypeSteps = HKQuantityType.quantityType(
|
||||
forIdentifier: .stepCount) else {
|
||||
return
|
||||
}
|
||||
|
||||
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
|
||||
|
||||
// Create a sample for total steps
|
||||
let sampleSteps = HKCumulativeQuantitySeriesSample(
|
||||
type: quantityTypeSteps,
|
||||
quantity: stepsQuantity, // Use your steps quantity here
|
||||
start: workoutSession.startDate!,
|
||||
end: Date())
|
||||
|
||||
// Add the steps sample to workout builder
|
||||
workoutBuilder.add([sampleSteps]) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
// End the data collection
|
||||
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
// Finish the workout and save total steps
|
||||
self.workoutBuilder.finishWorkout { (workout, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
workout?.setValue(stepsQuantity, forKey: "totalSteps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let quantityTypeDistance = HKQuantityType.quantityType(
|
||||
forIdentifier: .distanceWalkingRunning) else {
|
||||
return
|
||||
}
|
||||
|
||||
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
|
||||
quantity: quantityMiles,
|
||||
start: workoutSession.startDate!,
|
||||
end: Date())
|
||||
|
||||
workoutBuilder.add([sampleDistance]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
self.workoutBuilder.finishWorkout{ (workout, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
workout?.setValue(quantityMiles, forKey: "totalDistance")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
|
||||
quantity: quantityMiles,
|
||||
start: workoutSession.startDate!,
|
||||
end: Date())
|
||||
|
||||
|
||||
workoutBuilder.add([sample]) {(success, error) in}
|
||||
workoutBuilder.add([sampleDistance]) {(success, error) in}
|
||||
|
||||
workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
|
||||
|
||||
}
|
||||
workoutBuilder.finishWorkout{ (success, error) in }
|
||||
}
|
||||
|
||||
func fetchStepCounts() {
|
||||
@@ -439,135 +248,6 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
|
||||
handleSendStatisticsData(statistics)
|
||||
}
|
||||
}
|
||||
|
||||
if(sport == 0) {
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
|
||||
doubleValue: WorkoutTracking.power)
|
||||
|
||||
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let powerType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingPower) else {
|
||||
return
|
||||
}
|
||||
let wattPerIntervalSample = HKQuantitySample(type: powerType,
|
||||
quantity: wattPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.cadence / 60.0)
|
||||
|
||||
guard let cadenceType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingCadence) else {
|
||||
return
|
||||
}
|
||||
let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
|
||||
quantity: cadencePerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} else if(sport == 1) {
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
|
||||
doubleValue: WorkoutTracking.power)
|
||||
|
||||
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let powerType = HKQuantityType.quantityType(
|
||||
forIdentifier: .runningPower) else {
|
||||
return
|
||||
}
|
||||
let wattPerIntervalSample = HKQuantitySample(type: powerType,
|
||||
quantity: wattPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .runningSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} else if(sport == 2) {
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .walkingSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
}
|
||||
|
||||
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="20037" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Tpn-rd-UUX">
|
||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="19529" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Tpn-rd-UUX">
|
||||
<device id="watch38"/>
|
||||
<dependencies>
|
||||
<deployment identifier="watchOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="20006"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="19514"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Main-->
|
||||
@@ -22,15 +22,9 @@
|
||||
<label width="136" alignment="left" text="Step Counts" id="HpA-e9-6YV"/>
|
||||
<label width="136" alignment="left" text="Calories" id="Szi-Jp-J3S"/>
|
||||
<label width="136" alignment="left" text="Distance" id="eRf-NJ-6If"/>
|
||||
<picker height="100" alignment="left" id="OTR-HF-vYb">
|
||||
<connections>
|
||||
<action selector="changeSport:" destination="Tpn-rd-UUX" id="3vY-lq-IhZ"/>
|
||||
</connections>
|
||||
</picker>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="caloriesLabel" destination="Szi-Jp-J3S" id="trd-YS-bJy"/>
|
||||
<outlet property="cmbSports" destination="OTR-HF-vYb" id="Ws5-w9-ZT8"/>
|
||||
<outlet property="distanceLabel" destination="eRf-NJ-6If" id="ZE2-OB-jqN"/>
|
||||
<outlet property="heartRateLabel" destination="Nda-m1-XRw" id="1la-8R-3jG"/>
|
||||
<outlet property="startButton" destination="vZg-X8-uY5" id="pJc-09-kfV"/>
|
||||
|
||||
15
defaults.pri
15
defaults.pri
@@ -1,15 +0,0 @@
|
||||
QT += gui bluetooth widgets xml positioning quick networkauth websockets texttospeech location multimedia
|
||||
QTPLUGIN += qavfmediaplayer
|
||||
QT+= charts
|
||||
|
||||
unix:android: QT += androidextras gui-private
|
||||
|
||||
android: include(android_openssl/openssl.pri)
|
||||
|
||||
INCLUDEPATH += $$PWD/src/qmdnsengine/src/include
|
||||
|
||||
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/src/android
|
||||
|
||||
ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64
|
||||
|
||||
#QMAKE_CXXFLAGS += -Werror=suggest-override
|
||||
@@ -1,96 +0,0 @@
|
||||
# Define build image
|
||||
FROM ubuntu:latest AS build
|
||||
|
||||
# Install essential build dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
git \
|
||||
ca-certificates \
|
||||
qtquickcontrols2-5-dev \
|
||||
qtconnectivity5-dev \
|
||||
qtbase5-private-dev \
|
||||
qtpositioning5-dev \
|
||||
libqt5charts5-dev \
|
||||
libqt5networkauth5-dev \
|
||||
libqt5websockets5-dev \
|
||||
qml-module* \
|
||||
libqt5texttospeech5-dev \
|
||||
qtlocation5-dev \
|
||||
qtmultimedia5-dev \
|
||||
g++ \
|
||||
make \
|
||||
wget \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Define runtime image
|
||||
FROM ubuntu:latest AS runtime
|
||||
|
||||
# Install essential runtime dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
libqt5bluetooth5 \
|
||||
libqt5widgets5 \
|
||||
libqt5positioning5 \
|
||||
libqt5xml5 \
|
||||
libqt5charts5 \
|
||||
qt5-assistant \
|
||||
libqt5networkauth5 \
|
||||
libqt5websockets5 \
|
||||
qml-module* \
|
||||
libqt5texttospeech5 \
|
||||
libqt5location5-plugins \
|
||||
libqt5multimediawidgets5 \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5multimedia5 \
|
||||
qml-module-qtquick-controls2 \
|
||||
libqt5location5 \
|
||||
bluez \
|
||||
dbus \
|
||||
tigervnc-standalone-server \
|
||||
tigervnc-tools \
|
||||
libgl1-mesa-dri \
|
||||
xfonts-base \
|
||||
x11-xserver-utils \
|
||||
tigervnc-common \
|
||||
net-tools \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Stage 1: Build
|
||||
FROM build AS builder
|
||||
|
||||
# Clone the project and build it
|
||||
WORKDIR /usr/local/src
|
||||
RUN git clone --recursive https://github.com/cagnulein/qdomyos-zwift.git
|
||||
WORKDIR /usr/local/src/qdomyos-zwift
|
||||
RUN git submodule update --init src/smtpclient/ \
|
||||
&& git submodule update --init src/qmdnsengine/ \
|
||||
&& git submodule update --init tst/googletest/
|
||||
WORKDIR /usr/local/src/qdomyos-zwift/src
|
||||
RUN qmake qdomyos-zwift.pro \
|
||||
&& make -j4
|
||||
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM runtime
|
||||
|
||||
# Copy the built binary to /usr/local/bin
|
||||
COPY --from=builder /usr/local/src/qdomyos-zwift/src/qdomyos-zwift /usr/local/bin/qdomyos-zwift
|
||||
|
||||
# VNC configuration
|
||||
RUN mkdir -p ~/.vnc && \
|
||||
echo "securepassword" | vncpasswd -f > ~/.vnc/passwd && \
|
||||
chmod 600 ~/.vnc/passwd
|
||||
|
||||
# .Xauthority configuration
|
||||
RUN touch /root/.Xauthority
|
||||
ENV DISPLAY=:99
|
||||
|
||||
# Start VNC server with authentication
|
||||
CMD vncserver :99 -depth 24 -localhost no -xstartup qdomyos-zwift && \
|
||||
sleep infinity
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
docker build -t qdomyos-zwift-vnc .
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
qdomyos-zwift-vnc:
|
||||
image: qdomyos-zwift-vnc
|
||||
container_name: qdomyos-zwift-vnc
|
||||
privileged: true # Required for Bluetooth functionality
|
||||
network_mode: "host" # Used to access host Bluetooth and D-Bus
|
||||
volumes:
|
||||
- /dev:/dev # Forward host devices (for Bluetooth)
|
||||
- /run/dbus:/run/dbus # Forward D-Bus for Bluetooth interaction
|
||||
restart: "no" # Do not restart the container automatically
|
||||
@@ -1,95 +0,0 @@
|
||||
# Define build image
|
||||
FROM ubuntu:latest AS build
|
||||
|
||||
# Install essential build dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
git \
|
||||
ca-certificates \
|
||||
qtquickcontrols2-5-dev \
|
||||
qtconnectivity5-dev \
|
||||
qtbase5-private-dev \
|
||||
qtpositioning5-dev \
|
||||
libqt5charts5-dev \
|
||||
libqt5networkauth5-dev \
|
||||
libqt5websockets5-dev \
|
||||
qml-module* \
|
||||
libqt5texttospeech5-dev \
|
||||
qtlocation5-dev \
|
||||
qtmultimedia5-dev \
|
||||
g++ \
|
||||
make \
|
||||
wget \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Define runtime image
|
||||
FROM ubuntu:latest AS runtime
|
||||
|
||||
# Install essential runtime dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
libqt5bluetooth5 \
|
||||
libqt5widgets5 \
|
||||
libqt5positioning5 \
|
||||
libqt5xml5 \
|
||||
libqt5charts5 \
|
||||
qt5-assistant \
|
||||
libqt5networkauth5 \
|
||||
libqt5websockets5 \
|
||||
qml-module* \
|
||||
libqt5texttospeech5 \
|
||||
libqt5location5-plugins \
|
||||
libqt5multimediawidgets5 \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5multimedia5 \
|
||||
qml-module-qtquick-controls2 \
|
||||
libqt5location5 \
|
||||
bluez \
|
||||
dbus \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Stage 1: Build
|
||||
FROM build AS builder
|
||||
|
||||
# Define variables for Qt versions
|
||||
ARG QT_VERSION=5.15
|
||||
ARG QT_SUBVERSION=5.15.13
|
||||
ARG QT_WEBPLUGIN_NAME=qtwebglplugin-everywhere-opensource-src
|
||||
|
||||
# Build WebGL plugin
|
||||
WORKDIR /usr/local/src
|
||||
RUN wget https://download.qt.io/official_releases/qt/${QT_VERSION}/${QT_SUBVERSION}/submodules/${QT_WEBPLUGIN_NAME}-${QT_SUBVERSION}.zip \
|
||||
&& unzip ${QT_WEBPLUGIN_NAME}-${QT_SUBVERSION}.zip \
|
||||
&& mv *-${QT_SUBVERSION} qtwebglplugin-everywhere \
|
||||
&& cd qtwebglplugin-everywhere \
|
||||
&& qmake \
|
||||
&& make
|
||||
|
||||
# Clone the project and build it
|
||||
WORKDIR /usr/local/src
|
||||
RUN git clone --recursive https://github.com/cagnulein/qdomyos-zwift.git
|
||||
WORKDIR /usr/local/src/qdomyos-zwift
|
||||
RUN git submodule update --init src/smtpclient/ \
|
||||
&& git submodule update --init src/qmdnsengine/ \
|
||||
&& git submodule update --init tst/googletest/
|
||||
WORKDIR /usr/local/src/qdomyos-zwift/src
|
||||
RUN qmake qdomyos-zwift.pro \
|
||||
&& make -j4
|
||||
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM runtime
|
||||
|
||||
# Copy the built binary to /usr/local/bin
|
||||
COPY --from=builder /usr/local/src/qdomyos-zwift/src/qdomyos-zwift /usr/local/bin/qdomyos-zwift
|
||||
|
||||
# Copy WebGL plugin to the appropriate location
|
||||
COPY --from=builder /usr/local/src/qtwebglplugin-everywhere/plugins/platforms/libqwebgl.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/libqwebgl.so
|
||||
|
||||
# Set the default command to run the application with WebGL
|
||||
CMD ["qdomyos-zwift", "-qml", "-platform", "webgl:port=8080"]
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
docker build -t qdomyos-zwift-webgl .
|
||||
@@ -1,19 +0,0 @@
|
||||
services:
|
||||
qdomyos-zwift-webgl:
|
||||
image: qdomyos-zwift-webgl
|
||||
container_name: qdomyos-zwift-webgl
|
||||
privileged: true
|
||||
network_mode: "host"
|
||||
environment:
|
||||
- DISPLAY=${DISPLAY}
|
||||
volumes:
|
||||
- /dev:/dev
|
||||
- /run/dbus:/run/dbus
|
||||
- ./.config:/root/.config
|
||||
- /tmp/.X11-unix:/tmp/.X11-unix
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: "no"
|
||||
# command: qdomyos-zwift -qml -platform webgl:port=8080
|
||||
# command: ["qdomyos-zwift", "-no-gui"]
|
||||
|
||||
@@ -4,20 +4,17 @@ QDomyos-Zwift can be installed from source on MacOs, Linux, Android and IOS.
|
||||
|
||||
Once you've installed QDomyos-Zwift, you can access the [operation guide](30_usage.md) for more information.
|
||||
|
||||
These instructions build the app itself, not the test project.
|
||||
|
||||
## On a Linux System (from source)
|
||||
|
||||
```buildoutcfg
|
||||
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
|
||||
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
|
||||
$ sudo sudo apt install git qt5quickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev
|
||||
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
|
||||
$ cd qdomyos-zwift
|
||||
$ git submodule update --init src/smtpclient/
|
||||
$ git submodule update --init src/qmdnsengine/
|
||||
$ git submodule update --init tst/googletest/
|
||||
$ cd src
|
||||
$ qmake qdomyos-zwift.pro
|
||||
$ qmake
|
||||
$ make -j4
|
||||
$ sudo ./qdomyos-zwift
|
||||
```
|
||||
@@ -28,13 +25,13 @@ $ sudo ./qdomyos-zwift
|
||||
You will need to (at a minimum) to install the xcode Command Line Tools (CLI) thanks to @richardwait
|
||||
https://developer.apple.com/download/more/?=xcode
|
||||
|
||||
Download and install https://download.qt.io/archive/qt/5.12/5.12.12/qt-opensource-mac-x64-5.12.12.dmg and simply run the qdomyos-zwift release for MacOs
|
||||
Download and install http://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-mac-x64-5.12.9.dmg and simply run the qdomyos-zwift relase for MacOs
|
||||
|
||||
## On Raspberry Pi Zero W
|
||||
|
||||

|
||||
|
||||
This guide will walk you through steps to setup an autonomous, headless raspberry bridge.
|
||||
This guide will walk you through steps to setup an autonomous, headless raspberry brigde.
|
||||
|
||||
|
||||
### Initial System Preparation
|
||||
@@ -77,7 +74,7 @@ Apply the changes `sudo systemctl restart dhcpcd.service` and ensure you have in
|
||||
|
||||
#### Enable SSH access
|
||||
|
||||
You might want to access your raspberry remotely while it is attached to your fitness equipment.
|
||||
You might want to access your raspberry remotely while it is attached to your fitness equipement.
|
||||
|
||||
`sudo raspi-config` > `Interface Options` > `SSH`
|
||||
|
||||
@@ -105,22 +102,15 @@ This operation takes a moment to complete.
|
||||
|
||||
#### Install qdomyos-zwift from sources
|
||||
|
||||
```bash
|
||||
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
|
||||
git clone https://github.com/cagnulein/qdomyos-zwift.git
|
||||
cd qdomyos-zwift
|
||||
git submodule update --init src/smtpclient/
|
||||
git submodule update --init src/qmdnsengine/
|
||||
git submodule update --init tst/googletest/
|
||||
cd src
|
||||
qmake qdomyos-zwift.pro
|
||||
make
|
||||
```
|
||||
`sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev`
|
||||
|
||||
If you need GUI also do a
|
||||
```
|
||||
apt install qml-module*
|
||||
```
|
||||
`git clone https://github.com/cagnulein/qdomyos-zwift.git`
|
||||
`cd qdomyos-zwift`
|
||||
`git submodule update --init src/smtpclient/`
|
||||
`git submodule update --init src/qmdnsengine/`
|
||||
`cd src`
|
||||
`qmake`
|
||||
`make`
|
||||
|
||||
Please note :
|
||||
- Don't build the application with `-j4` option (this will fail)
|
||||
@@ -177,155 +167,10 @@ If everything is working as expected, **enable your service at boot time** :
|
||||
|
||||
Then reboot to check operations (`sudo reboot`)
|
||||
|
||||
### (optional) Treadmill Auto-Detection and Service Management
|
||||
This section provides a reliable way to manage the QZ service based on the treadmill's power state. Using a `bluetoothctl`-based Bash script, this solution ensures the QZ service starts when the treadmill is detected and stops when it is not.
|
||||
|
||||
- **Bluetooth Discovery**: Monitors treadmill availability via `bluetoothctl`.
|
||||
- **Service Control**: Automatically starts and stops the QZ service.
|
||||
- **Logging**: Tracks treadmill status and actions in a log file.
|
||||
|
||||
**Notes:**
|
||||
- Ensure `bluetoothctl` is installed and working on your system.
|
||||
- Replace `I_TL` in the script with your treadmill's Bluetooth name. You can find your device name via `bluetoothctl scan on`
|
||||
- Adjust the sleep interval (`sleep 30`) in the script as needed for your use case.
|
||||
|
||||
Step 1: Save the following script as `/root/qz-treadmill-monitor.sh`:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
LOG_FILE="/var/log/qz-treadmill-monitor.log"
|
||||
TARGET_DEVICE="I_TL"
|
||||
SCAN_INTERVAL=30 # Time in seconds between checks
|
||||
SERVICE_NAME="qz"
|
||||
DEBUG_LOG_DIR="/var/log" # Directory where QZ debug logs are stored
|
||||
ERROR_MESSAGE="BTLE stateChanged InvalidService"
|
||||
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
is_service_running() {
|
||||
systemctl is-active --quiet "$SERVICE_NAME"
|
||||
return $?
|
||||
}
|
||||
|
||||
scan_for_device() {
|
||||
log "Starting Bluetooth scan for $TARGET_DEVICE..."
|
||||
|
||||
# Run bluetoothctl scan in the background and capture output
|
||||
bluetoothctl scan on &>/dev/null &
|
||||
SCAN_PID=$!
|
||||
|
||||
# Allow some time for devices to appear
|
||||
sleep 5
|
||||
|
||||
# Check if the target device appears in the list
|
||||
bluetoothctl devices | grep -q "$TARGET_DEVICE"
|
||||
DEVICE_FOUND=$?
|
||||
|
||||
# Stop scanning
|
||||
kill "$SCAN_PID"
|
||||
bluetoothctl scan off &>/dev/null
|
||||
|
||||
if [ $DEVICE_FOUND -eq 0 ]; then
|
||||
log "Device '$TARGET_DEVICE' found."
|
||||
return 0
|
||||
else
|
||||
log "Device '$TARGET_DEVICE' not found."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restart_qz_on_error() {
|
||||
# Get the current date
|
||||
CURRENT_DATE=$(date '+%a_%b_%d')
|
||||
|
||||
# Find the latest QZ debug log file for today
|
||||
LATEST_LOG=$(ls -t "$DEBUG_LOG_DIR"/debug-"$CURRENT_DATE"_*.log 2>/dev/null | head -n 1)
|
||||
|
||||
if [ -z "$LATEST_LOG" ]; then
|
||||
log "No QZ debug log found for today."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Checking latest log file: $LATEST_LOG for errors..."
|
||||
|
||||
# Search the latest log for the error message
|
||||
if grep -q "$ERROR_MESSAGE" "$LATEST_LOG"; then
|
||||
log "***** Error detected in QZ log: $ERROR_MESSAGE *****"
|
||||
log "Restarting QZ service..."
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
else
|
||||
log "No errors detected in $LATEST_LOG."
|
||||
fi
|
||||
}
|
||||
|
||||
manage_service() {
|
||||
local device_found=$1
|
||||
if $device_found; then
|
||||
if ! is_service_running; then
|
||||
log "***** Starting QZ service... *****"
|
||||
systemctl start "$SERVICE_NAME"
|
||||
else
|
||||
log "QZ service is already running."
|
||||
restart_qz_on_error # Check the log for errors when QZ is already running
|
||||
fi
|
||||
else
|
||||
if is_service_running; then
|
||||
log "***** Stopping QZ service... *****"
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
else
|
||||
log "QZ service is already stopped."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while true; do
|
||||
log "Checking for treadmill status..."
|
||||
if scan_for_device; then
|
||||
manage_service true
|
||||
else
|
||||
manage_service false
|
||||
fi
|
||||
log "Waiting for $SCAN_INTERVAL seconds before next check..."
|
||||
sleep "$SCAN_INTERVAL"
|
||||
done
|
||||
```
|
||||
|
||||
Step2: To ensure the script runs continuously, create a systemd service file at `/etc/systemd/system/qz-treadmill-monitor.service`
|
||||
```bash
|
||||
[Unit]
|
||||
Description=QZ Treadmill Monitor Service
|
||||
After=bluetooth.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/root/qz-treadmill-monitor.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Step 3: Enable and Start the Service
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable qz-treadmill-monitor
|
||||
sudo systemctl start qz-treadmill-monitor
|
||||
```
|
||||
|
||||
Monitor logs are written to `/var/log/qz-treadmill-monitor.log`. Use the following command to check logs in real-time:
|
||||
```bash
|
||||
sudo tail -f /var/log/qz-treadmill-monitor.log
|
||||
```
|
||||
|
||||
|
||||
|
||||
### (optional) Enable overlay FS
|
||||
|
||||
Once that everything is working as expected, and if you dedicate your Raspberry pi to this usage, you might want to enable the read-only overlay FS.
|
||||
Once that everything is working as expected, and if you dedicate your raspeberry pi to this usage, you might want to enable the read-only overlay FS.
|
||||
|
||||
By enabling the overlay read-only system, your SD card will be read-only only and every file written will be to RAM.
|
||||
Then at each reboot the RAM is erased and you'll revert to the initial status of the overlay file-system.
|
||||
|
||||
@@ -36,7 +36,7 @@ An android device is required for this operation.
|
||||
8. Disable the option Enable Bluetooth HCI snoop log
|
||||
9. in Developer Options: Bug report->Full report
|
||||
10. wait a random amount of time (10-20 seconds)
|
||||
11. A notification will appear at the top of the device. Click on it, share, email it to yourself. If it doesn't appear you need to use ADB to pull the file from the phone itself
|
||||
11. A notification will appear at the top of the device. Click on it, share, email it to yourself
|
||||
12. You'll get a zip file with the entire report. In the FS/Data/Log/bt directory of the zipfile is the file you want.
|
||||
13. attach the log file in a new issue with a short description of the steps you did in the app when you used it
|
||||
|
||||
|
||||
@@ -26,5 +26,5 @@ You can have ae true 4k video stream while you ride ("extreme quality" setting)
|
||||
The application do not read the FTMS value. It is required to start the application with `-heart-service` or `bike_heartrate_service: true` in settings.
|
||||
|
||||
### Resistance management
|
||||
You can adjust resistance using arrows [up and down](img/21_zwift-resistance-buttons.jpg) rom the riding screen.
|
||||
You can adjust resistence using arrows [up and down](img/21_zwift-resistance-buttons.jpg) rom the riding screen.
|
||||
|
||||
|
||||
@@ -18,15 +18,14 @@ Please refer to this article for more information under [QML Operations](https:/
|
||||
|
||||
## Configuration in NativeQT mode
|
||||
|
||||
This is the list of settings available in the application. These settings need to be appended to the binary command line.
|
||||
This is the list of settings available in the application. These settings needs to be appended to the binary command line.
|
||||
*Example :* `sudo ./qdomyos-zwift -no-gui` for disabling any graphical interface.
|
||||
|
||||
| **Option** | **Type** | **Default** | **Function** |
|
||||
|:------------------------------|:---------|:------------|:-----------------------------------------------------------------------------|
|
||||
| -no-gui | Boolean | False | Disable GUI |
|
||||
| -qml | Boolean | True | Enables the QML interface |
|
||||
| -noqml | Boolean | False | Enables the NativeQT interface |
|
||||
| -miles | Boolean | False | Switches to Imperial Units System |
|
||||
| -qml | Boolean | False | Enables the QML interface |
|
||||
| -miles | Boolean | False | Swithes to Imperial Units System |
|
||||
| -no-console | Boolean | False | Not in use |
|
||||
| -test-resistance | Boolean | False | |
|
||||
| -no-log | Boolean | False | Disable Logging |
|
||||
@@ -35,18 +34,18 @@ This is the list of settings available in the application. These settings need t
|
||||
| -heart-service | Boolean | True | Simulate HR service (required for applications not reading FTMS) |
|
||||
| -only-virtualbike | Boolean | False | |
|
||||
| -only-virtualtreadmill | Boolean | False | |
|
||||
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipment if enabled |
|
||||
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipment |
|
||||
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipement if enabled |
|
||||
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipement |
|
||||
| -bike-cadence-sensor | Boolean | False | |
|
||||
| -bike-power-sensor | Boolean | False | |
|
||||
| -battery-service | Boolean | False | |
|
||||
| -service-changed | Boolean | False | |
|
||||
| -bike-wheel-revs | Boolean | False | |
|
||||
| -run-cadence-sensor | Boolean | False | |
|
||||
| -nordictrack-10-treadmill | Boolean | False | Enable NordicTrack compatibility mode |
|
||||
| -nordictrack-10-treadmill | Boolean | False | Enable NordicTrack compatibility mode |
|
||||
| -train | String | | Force training program |
|
||||
| -name | String | | Force bluetooth device name (if QZ struggles to find your fitness equipment) |
|
||||
| -poll-device-time | Int | 200 (ms) | Frequency to refresh information from QZ to Fitness equipment |
|
||||
| -name | String | | Force bluetooth device name (if QZ struggles finding your fitness equipment) |
|
||||
| -poll-device-time | Int | 200 (ms) | Frequency to refresh informations from QZ to Fitness equipment |
|
||||
| -bike-resistance-gain | Int | | Adjust resistance from the fitness application |
|
||||
| -bike-resistance-offset | Int | | Set another resistance point than default |
|
||||
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
# QDomyos-Zwift Guide to Writing Unit Tests
|
||||
|
||||
## About
|
||||
|
||||
The testing project tst/qdomyos-zwift-tests.pro contains test code that uses the Google Test library.
|
||||
|
||||
## Adding a new device
|
||||
|
||||
New devices are added to the main QZ application by creating or modifying a subclass of the bluetoothdevice class.
|
||||
|
||||
At minimum, each device has a corresponding BluetoothDeviceTestData object constructed in the DeviceTestDataIndex class in the test project, which is coded to provide information to the test framework to generate tests for device detection and potentially other things.
|
||||
|
||||
In the test project
|
||||
* add a new device name constant to the DeviceIndex class.
|
||||
* locate the implementation of DeviceTestDataindex::Initialize and build the test data from a call to DeviceTestDataIndex::RegisterNewDeviceTestData(...)
|
||||
* pass the device name constant defined in the DeviceIndex class to the call to DeviceTestDataIndex::RegisterNewDeviceTestData(...).
|
||||
|
||||
The tests are not organised around real devices that are handled, but the bluetoothdevice subclass that handles them - the "driver" of sorts.
|
||||
|
||||
You need to provide the following:
|
||||
- patterns for valid names (e.g. equals a value, starts with a value, case sensitivity, specific length)
|
||||
- invalid names to ensure the device is not identified when the name is invalid
|
||||
- configuration settings that are required for the device to be detected, including bluetooth device information configuration
|
||||
- invalid configurations to test that the device is not detected, e.g. when it's disabled in the settings, but the name is correct
|
||||
- exclusion devices: for example if a device with the same name but of a higher priority type is detected, this device should not be detected
|
||||
|
||||
## Tools in the Test Framework
|
||||
|
||||
### TestSettings
|
||||
|
||||
The detection of many devices depends on settings that are accessed programmatically using the QSettings class and the constants in the QZSettings namespace. The TestSettings class stores a QSettings object with what is intended to be a unique application and organisation name, to keep the configuration it represents seperate from others in the system. It also makes the stored QSettings object the default by setting the QCoreApplication's organisation and application names to those of the QSettings object. The original values are restored by calling the deactivate() function or on object destruction.
|
||||
|
||||
i.e. a test will
|
||||
* apply a configuration from a TestSettings object
|
||||
* perform device detection
|
||||
* use the TestSettings object to restore the previous settings either directly or by letting its destructor be called.
|
||||
|
||||
### DeviceDiscoveryInfo
|
||||
|
||||
This class:
|
||||
* stores values for a specific subset of the QZSettings keys.
|
||||
* provides methods to read and write the values it knows about from and to a QSettings object.
|
||||
* provides a QBluetoothDeviceInfo object configured with the device name currently being tested.
|
||||
|
||||
It is used in conjunction with a TestSettings object to write a configuration during a test.
|
||||
|
||||
|
||||
## Writing a device detection test
|
||||
|
||||
Because of the way the BluetoothDeviceTestDataBuilder currently works, it may be necessary to define multiple test data objects to cover the various cases.
|
||||
For example, if any of a list of names is enough to identify a device, or another group of names but with a certain service in the bluetooth device info, that will require multiple test data objects.
|
||||
|
||||
### Recognition by Name
|
||||
|
||||
Consider the detection code for the Domyos Bike:
|
||||
|
||||
```
|
||||
} else if (b.name().startsWith(QStringLiteral("Domyos-Bike")) &&
|
||||
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && filter) {
|
||||
|
||||
```
|
||||
|
||||
Reading this, to identify this device:
|
||||
- bluetooth name should start with "Domyos-Bike" using a case sensitive comparison
|
||||
- bluetooth name should NOT start with "DomyosBridge", also using a case sensitive comparison
|
||||
- there should not have been a device using the corresponding device class detected already (i.e. domyos)
|
||||
- filter has not been activated (this isn't tested)
|
||||
|
||||
In this case, we are not testing the last two, but can test the first two.
|
||||
|
||||
In deviceindex.h:
|
||||
|
||||
```
|
||||
static const QString DomyosBike;
|
||||
```
|
||||
|
||||
In deviceindex.cpp:
|
||||
|
||||
```
|
||||
DEFINE_DEVICE(DomyosBike, "Domyos Bike");
|
||||
```
|
||||
|
||||
This pair adds the "friendly name" for the device as a constant, and also adds the key/value pair to an index.
|
||||
|
||||
In DeviceTestDataIndex::Initialize():
|
||||
|
||||
```
|
||||
// Domyos bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::DomyosBike)
|
||||
->expectDevice<domyosbike>()
|
||||
->acceptDeviceName("Domyos-Bike", DeviceNameComparison::StartsWith)
|
||||
->rejectDeviceName("DomyosBridge", DeviceNameComparison::StartsWith);
|
||||
```
|
||||
|
||||
This set of instructions adds a valid device name, and an invalid one. Various overloads of these methods, other methods, and other members of the comparison enumeration provide other capabilities for specifying test data. If you add a valid device name that says the name should start with a value, additional names will be added automatically to the valid list with additional characters to test that it is in fact a "starts with" relationship. Also, valid and invalid names will be generated based on whether the comparison is case sensitive or not.
|
||||
|
||||
### Configuration Settings
|
||||
|
||||
Consider the CompuTrainer bike. This device is not detected by name, but only by whether or not it is enabled in the settings.
|
||||
To specify this in the test data, we use one of the BluetoothDeviceTestData::configureSettingsWith(...) methods, the one for the simple case where there is a single QZSetting with a specific enabling and disabling value.
|
||||
|
||||
Settings from QSettings that contribute to tests should be put into the DeviceDiscoveryInfo class.
|
||||
|
||||
For example, for the Computrainer Bike, the "computrainer_serialport" value from the QSettings determines if the bike should be detected or not.
|
||||
|
||||
The computrainer_serialport QZSettings key should be registered in devicediscoveryinfo.cpp
|
||||
|
||||
In devicediscoveryinfo.cpp:
|
||||
```
|
||||
void InitializeTrackedSettings() {
|
||||
|
||||
...
|
||||
|
||||
trackedSettings.insert(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport);
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
For this test data,
|
||||
* if enabling configurations are requested, the computrainer_serialport setting will be populated with "COMX"
|
||||
* if disabling configurations are requested, the computrainer_serialport setting will be populated with ""
|
||||
|
||||
DeviceTestDataIndex::Initialize():
|
||||
|
||||
```
|
||||
// Computrainer Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::ComputrainerBike)
|
||||
->expectDevice<computrainerbike>()
|
||||
->acceptDeviceName("", DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->configureSettingsWith(QZSettings::computrainer_serialport, "COMX", "");
|
||||
```
|
||||
|
||||
|
||||
Similarly, the Pafers Bike has a simple configuration setting:
|
||||
|
||||
```
|
||||
// Pafers Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::PafersBike)
|
||||
->expectDevice<pafersbike>()
|
||||
->acceptDeviceName("PAFERS_", DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->configureSettingsWith(QZSettings::pafers_treadmill,false);
|
||||
```
|
||||
|
||||
In that case, ```configureSettingsWith(QZSettings::pafers_treadmill,false)``` indicates that the pafers_treadmill setting will be false for enabling configurations and true for disabling ones.
|
||||
|
||||
A more complicated example is the Pafers Treadmill. It involves a name match, but also some configuration settings obtained earlier...
|
||||
|
||||
```
|
||||
bool pafers_treadmill = settings.value(QZSettings::pafers_treadmill, QZSettings::default_pafers_treadmill).toBool();
|
||||
...
|
||||
|
||||
bool pafers_treadmill_bh_iboxster_plus =
|
||||
settings
|
||||
.value(QZSettings::pafers_treadmill_bh_iboxster_plus, QZSettings::default_pafers_treadmill_bh_iboxster_plus)
|
||||
.toBool();
|
||||
...
|
||||
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("PAFERS_")) && !pafersTreadmill &&
|
||||
(pafers_treadmill || pafers_treadmill_bh_iboxster_plus) && filter) {
|
||||
```
|
||||
|
||||
Here the device could be activated due to a name match and various combinations of settings.
|
||||
For this, the configureSettingsWith(...) function that takes a lambda function which consumes a vector of DeviceDiscoveryInfo objects which is populated with configurations that lead to the specified result (enable = detected, !enable=not detected).
|
||||
|
||||
```
|
||||
// Pafers Treadmill
|
||||
RegisterNewDeviceTestData(DeviceIndex::PafersTreadmill)
|
||||
->expectDevice<paferstreadmill>()
|
||||
->acceptDeviceName("PAFERS_", DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->configureSettingsWith( [](const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations)->void {
|
||||
DeviceDiscoveryInfo config(info);
|
||||
|
||||
if (enable) {
|
||||
for(int x = 1; x<=3; x++) {
|
||||
config.setValue(QZSettings::pafers_treadmill, x & 1);
|
||||
config.setValue(QZSettings::pafers_treadmill_bh_iboxster_plus, x & 2);
|
||||
configurations.push_back(config);
|
||||
}
|
||||
} else {
|
||||
config.setValue(QZSettings::pafers_treadmill, false);
|
||||
config.setValue(QZSettings::pafers_treadmill_bh_iboxster_plus, false);
|
||||
configurations.push_back(config);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Considering Extra QBluetoothDeviceInfo Content
|
||||
|
||||
Detection of some devices requires some specific bluetooth device information.
|
||||
|
||||
Supplying enabling and disabling QBluetoothDeviceInfo objects is done by accessing the QBluetoothDeviceInfo member of the DeviceDiscoveryInfo object.
|
||||
For example, the M3iBike requires specific manufacturer information, using the simpler of the lambda functions accepted by the configureSettingsWith function.
|
||||
|
||||
```
|
||||
// M3I Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::M3IBike)
|
||||
->expectDevice<m3ibike>()
|
||||
->acceptDeviceName("M3", DeviceNameComparison::StartsWith)
|
||||
->configureSettingsWith(
|
||||
[](DeviceDiscoveryInfo& info, bool enable)->void
|
||||
{
|
||||
// The M3I bike detector looks into the manufacturer data.
|
||||
if(!enable) {
|
||||
info.DeviceInfo()->setManufacturerData(1, QByteArray("Invalid manufacturer data."));
|
||||
return;
|
||||
}
|
||||
|
||||
int key=0;
|
||||
info.DeviceInfo()->setManufacturerData(key++, hex2bytes("02010639009F00000000000000000014008001"));
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
The test framework populates the incoming QBluetoothDeviceInfo object with a UUID and the name (generated from the acceptDeviceName and rejectDeviceName calls) currently being tested.
|
||||
This is expected to have nothing else defined.
|
||||
Another example is one of the test data definitions for detecting a device that uses the stagesbike class:
|
||||
|
||||
Detection code from bluetooth.cpp:
|
||||
|
||||
```
|
||||
((b.name().toUpper().startsWith("KICKR CORE")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
|
||||
```
|
||||
|
||||
This condition is actually extracted from a more complicated example where the BluetoothDeviceTestData class can't cover all the detection criteria with one instance.
|
||||
|
||||
```
|
||||
// Stages Bike General
|
||||
auto stagesBikeExclusions = { GetTypeId<ftmsbike>() };
|
||||
|
||||
//
|
||||
// ... other stages bike variants
|
||||
//
|
||||
|
||||
// Stages Bike (KICKR CORE)
|
||||
RegisterNewDeviceTestData(DeviceIndex::StagesBike_KICKRCORE)
|
||||
->expectDevice<stagesbike>()
|
||||
->acceptDeviceName("KICKR CORE", DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->excluding(stagesBikeExclusions)
|
||||
->configureSettingsWith(
|
||||
[](const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations)->void
|
||||
{
|
||||
// The condition, if the name is acceptable, is:
|
||||
// !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
|
||||
|
||||
if(enable) {
|
||||
DeviceDiscoveryInfo result = info;
|
||||
result.addBluetoothService(QBluetoothUuid((quint16)0x1818));
|
||||
result.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
configurations.push_back(result);
|
||||
} else {
|
||||
DeviceDiscoveryInfo hasNeither = info;
|
||||
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1818));
|
||||
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
|
||||
DeviceDiscoveryInfo hasInvalid = info;
|
||||
hasInvalid.addBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
DeviceDiscoveryInfo hasBoth = hasInvalid;
|
||||
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1818));
|
||||
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
|
||||
configurations.push_back(info); // has neither
|
||||
configurations.push_back(hasInvalid);
|
||||
configurations.push_back(hasBoth);
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
In this case, it populates the vector with the single enabling configuration if that's what's been requested, otherwise 3 disabling ones.
|
||||
|
||||
### Exclusions
|
||||
|
||||
Sometimes there might be ambiguity when multiple devices are available, and the detection code may specify that if the other conditions match, but certain specific kinds of devices (the exclusion devices) have already been detected, the newly matched device should be ignored.
|
||||
|
||||
The test data object can be made to cover this by calling the excluding(...) functions to add type identifiers for the bluetoothdevice classes for the exclusion devices to the object's internal list of exclusions.
|
||||
|
||||
Detection code:
|
||||
|
||||
```
|
||||
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride &&
|
||||
!echelonConnectSport && filter) {
|
||||
```
|
||||
The excluding<T>() template function is called to specify the exclusion device type. Note that the test for a previously detected device of the same type is not included.
|
||||
|
||||
```
|
||||
// Echelon Connect Sport Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::EchelonConnectSportBike)
|
||||
->expectDevice<echelonconnectsport>()
|
||||
->acceptDeviceName("ECH", DeviceNameComparison::StartsWith)
|
||||
->excluding<echelonrower>()
|
||||
->excluding<echelonstride>();
|
||||
|
||||
```
|
||||
|
||||
### When a single test data object can't cover all the conditions
|
||||
|
||||
Detection code:
|
||||
|
||||
```
|
||||
QString powerSensorName =
|
||||
settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name).toString();
|
||||
...
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("STAGES ")) ||
|
||||
(b.name().toUpper().startsWith("TACX SATORI")) ||
|
||||
((b.name().toUpper().startsWith("KICKR CORE")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818))) ||
|
||||
(b.name().toUpper()==QStringLiteral("QD")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
|
||||
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
|
||||
!stagesBike && !ftmsBike && filter) {
|
||||
```
|
||||
|
||||
This presents 3 scenarios for the current test framework.
|
||||
1. Match names only (starts with:"STAGES ", starts with: "TACX SATORI", equals: "QD")
|
||||
2. Match the name "KICKR CORE", presence and absence of specific service ids
|
||||
3. Match the name "ASSIOMA" and the power sensor name setting starts with "Disabled"
|
||||
|
||||
The framework is not currently capable of specifying all these scenarios in a single test data object, without checking the name of the supplied QBluetoothDeviceInfo object against name conditions specified and constructing extra configurations based on that.
|
||||
The generated test data is approximately the combinations of these lists: names * settings * exclusions.
|
||||
If a combination should not exist, separate test data objects should be used.
|
||||
|
||||
In the example of the Stages Bike test data, the exclusions, which apply to all situations, are implemented in an array of type ids:
|
||||
|
||||
|
||||
```
|
||||
// Stages Bike General
|
||||
auto stagesBikeExclusions = { GetTypeId<ftmsbike>() };
|
||||
```
|
||||
|
||||
The name-match only in one test data instance:
|
||||
|
||||
```
|
||||
// Stages Bike
|
||||
RegisterNewDeviceTestData(DeviceIndex::StagesBike)
|
||||
->expectDevice<stagesbike>()
|
||||
->acceptDeviceNames({"STAGES ", "TACX SATORI"}, DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->acceptDeviceName("QD", DeviceNameComparison::IgnoreCase)
|
||||
->excluding(stagesBikeExclusions);
|
||||
```
|
||||
|
||||
The name and setting match in another instance:
|
||||
|
||||
```
|
||||
// Stages Bike Stages Bike (Assioma / Power Sensor disabled
|
||||
RegisterNewDeviceTestData(DeviceIndex::StagesBike_Assioma_PowerSensorDisabled)
|
||||
->expectDevice<stagesbike>()
|
||||
->acceptDeviceName("ASSIOMA", DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->configureSettingsWith(QZSettings::power_sensor_name, "DisabledX", "XDisabled")
|
||||
->excluding( stagesBikeExclusions);
|
||||
|
||||
```
|
||||
The name and bluetooth device info configurations in another:
|
||||
|
||||
```
|
||||
// Stages Bike (KICKR CORE)
|
||||
RegisterNewDeviceTestData(DeviceIndex::StagesBike_KICKRCORE)
|
||||
->expectDevice<stagesbike>()
|
||||
->acceptDeviceName("KICKR CORE", DeviceNameComparison::StartsWithIgnoreCase)
|
||||
->excluding(stagesBikeExclusions)
|
||||
->configureSettingsWith(
|
||||
[](const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations)->void
|
||||
{
|
||||
// The condition, if the name is acceptable, is:
|
||||
// !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
|
||||
|
||||
if(enable) {
|
||||
DeviceDiscoveryInfo result = info;
|
||||
result.addBluetoothService(QBluetoothUuid((quint16)0x1818));
|
||||
result.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
configurations.push_back(result);
|
||||
} else {
|
||||
DeviceDiscoveryInfo hasNeither = info;
|
||||
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1818));
|
||||
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
|
||||
DeviceDiscoveryInfo hasInvalid = info;
|
||||
hasInvalid.addBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
DeviceDiscoveryInfo hasBoth = hasInvalid;
|
||||
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1818));
|
||||
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1826));
|
||||
|
||||
configurations.push_back(info); // has neither
|
||||
configurations.push_back(hasInvalid);
|
||||
configurations.push_back(hasBoth);
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## Telling Google Test Where to Look
|
||||
|
||||
The BluetoothDeviceTestSuite configuration specifies that the test data will be obtained from the DeviceTestDataIndex class, so there's nothing more to do.
|
||||
|
||||
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--Copyright 2017 Bluetooth SIG, Inc. All rights reserved.-->
|
||||
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
name="Cross Trainer Data"
|
||||
type="org.bluetooth.characteristic.cross_trainer_data" uuid="2ACE"
|
||||
last-modified="2017-02-14" approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The Cross Trainer Data characteristic is used to send
|
||||
training-related data to the Client from a cross trainer
|
||||
(Server).</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Flags">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>24bit</Format>
|
||||
<BitField>
|
||||
<Bit index="0" size="1" name="More Data">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" requires="C1" />
|
||||
<Enumeration key="1" value="True" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="1" size="1" name="Average Speed present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C2" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="2" size="1" name="Total Distance Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C3" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="3" size="1" name="Step Count present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C4" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="4" size="1" name="Stride Count present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C5" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="5" size="1" name="Elevation Gain present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C6" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="6" size="1"
|
||||
name="Inclination and Ramp Angle Setting present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C7" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="7" size="1" name="Resistance Level Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C8" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="8" size="1" name="Instantaneous Power present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C9" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="9" size="1" name="Average Power present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C10" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="10" size="1" name="Expended Energy present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C11" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="11" size="1" name="Heart Rate present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C12" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="12" size="1"
|
||||
name="Metabolic Equivalent present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C13" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="13" size="1" name="Elapsed Time present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C14" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="14" size="1" name="Remaining Time present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C15" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="15" size="1" name="Movement Direction">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="Forward" />
|
||||
<Enumeration key="1" value="Backward" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<ReservedForFutureUse index="16" size="8" />
|
||||
</BitField>
|
||||
</Field>
|
||||
<Field name="Instantaneous Speed">
|
||||
<InformativeText>Kilometer per hour with a resolution of
|
||||
0.01</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.velocity.kilometre_per_hour</Unit>
|
||||
<DecimalExponent>-2</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Average Speed">
|
||||
<InformativeText>Kilometer per hour with a resolution of
|
||||
0.01</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.velocity.kilometre_per_hour</Unit>
|
||||
<DecimalExponent>-2</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Total Distance">
|
||||
<InformativeText>Meters with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C3</Requirement>
|
||||
<Format>uint24</Format>
|
||||
<Unit>org.bluetooth.unit.length.metre</Unit>
|
||||
</Field>
|
||||
<Field name="Step Per Minute">
|
||||
<InformativeText>Step/minute with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C4</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.step_per_minute</Unit>
|
||||
</Field>
|
||||
<Field name="Average Step Rate">
|
||||
<InformativeText>Step/minute with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C4</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.step_per_minute</Unit>
|
||||
</Field>
|
||||
<Field name="Stride Count">
|
||||
<InformativeText>Unitless with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>C5</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
</Field>
|
||||
<Field name="Positive Elevation Gain">
|
||||
<InformativeText>Meters with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C6</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.length.metre</Unit>
|
||||
</Field>
|
||||
<Field name="Negative Elevation Gain">
|
||||
<InformativeText>Meters with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C6</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.length.metre</Unit>
|
||||
</Field>
|
||||
<Field name="Inclination">
|
||||
<InformativeText>Percent with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>C7</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.percentage</Unit>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Ramp Angle Setting">
|
||||
<InformativeText>Degree with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>C7</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.plane_angle.degree</Unit>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Resistance Level">
|
||||
<InformativeText>Unitless with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>C8</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
</Field>
|
||||
<Field name="Instantaneous Power">
|
||||
<InformativeText>Watts with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C9</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.power.watt</Unit>
|
||||
</Field>
|
||||
<Field name="Average Power">
|
||||
<InformativeText>Watts with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C10</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.power.watt</Unit>
|
||||
</Field>
|
||||
<Field name="Total Energy">
|
||||
<InformativeText>Kilo Calorie with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C11</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.energy.kilogram_calorie</Unit>
|
||||
</Field>
|
||||
<Field name="Energy Per Hour">
|
||||
<InformativeText>Kilo Calorie with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C11</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.energy.kilogram_calorie</Unit>
|
||||
</Field>
|
||||
<Field name="Energy Per Minute">
|
||||
<InformativeText>Kilo Calorie with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C11</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<Unit>org.bluetooth.unit.energy.kilogram_calorie</Unit>
|
||||
</Field>
|
||||
<Field name="Heart Rate">
|
||||
<InformativeText>Beats per minute with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C12</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<Unit>org.bluetooth.unit.period.beats_per_minute</Unit>
|
||||
</Field>
|
||||
<Field name="Metabolic Equivalent">
|
||||
<InformativeText>Metabolic Equivalent with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>C13</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
<Unit>org.bluetooth.unit.metabolic_equivalent</Unit>
|
||||
</Field>
|
||||
<Field name="Elapsed Time">
|
||||
<InformativeText>Second with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C14</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
</Field>
|
||||
<Field name="Remaining Time">
|
||||
<InformativeText>Second with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C15</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
</Field>
|
||||
</Value>
|
||||
<Note>The fields in the above table, reading from top to bottom,
|
||||
are shown in the order of LSO to MSO, where LSO = Least
|
||||
Significant Octet and MSO = Most Significant Octet. The Least
|
||||
Significant Octet represents the eight bits numbered 0 to
|
||||
7.</Note>
|
||||
</Characteristic>
|
||||
@@ -1,6 +0,0 @@
|
||||
copy icons\iOS\iTunesArtwork@2x.png build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release
|
||||
del build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release\qz.appx
|
||||
cd build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release
|
||||
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\makeappx.exe" pack /d . /p qz
|
||||
explorer build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release
|
||||
pause
|
||||
@@ -1,188 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
import re
|
||||
|
||||
@dataclass
|
||||
class HubRidingData:
|
||||
power: Optional[int] = None
|
||||
cadence: Optional[int] = None
|
||||
speed_x100: Optional[int] = None
|
||||
hr: Optional[int] = None
|
||||
unknown1: Optional[int] = None
|
||||
unknown2: Optional[int] = None
|
||||
|
||||
def __str__(self):
|
||||
return (f"Power={self.power}W Cadence={self.cadence}rpm "
|
||||
f"Speed={self.speed_x100/100 if self.speed_x100 else 0:.1f}km/h "
|
||||
f"HR={self.hr}bpm Unknown1={self.unknown1} Unknown2={self.unknown2}")
|
||||
|
||||
def parse_protobuf_varint(data: bytes, offset: int = 0) -> Tuple[int, int]:
|
||||
result = 0
|
||||
shift = 0
|
||||
while offset < len(data):
|
||||
byte = data[offset]
|
||||
result |= (byte & 0x7F) << shift
|
||||
offset += 1
|
||||
if not (byte & 0x80):
|
||||
break
|
||||
shift += 7
|
||||
return result, offset
|
||||
|
||||
def parse_hub_riding_data(data: bytes) -> Optional[HubRidingData]:
|
||||
try:
|
||||
riding_data = HubRidingData()
|
||||
offset = 0
|
||||
while offset < len(data):
|
||||
key, new_offset = parse_protobuf_varint(data, offset)
|
||||
wire_type = key & 0x07
|
||||
field_number = key >> 3
|
||||
offset = new_offset
|
||||
|
||||
if wire_type == 0:
|
||||
value, offset = parse_protobuf_varint(data, offset)
|
||||
if field_number == 1:
|
||||
riding_data.power = value
|
||||
elif field_number == 2:
|
||||
riding_data.cadence = value
|
||||
elif field_number == 3:
|
||||
riding_data.speed_x100 = value
|
||||
elif field_number == 4:
|
||||
riding_data.hr = value
|
||||
elif field_number == 5:
|
||||
riding_data.unknown1 = value
|
||||
elif field_number == 6:
|
||||
riding_data.unknown2 = value
|
||||
return riding_data
|
||||
except Exception as e:
|
||||
print(f"Error parsing protobuf: {e}")
|
||||
return None
|
||||
|
||||
@dataclass
|
||||
class DirconPacket:
|
||||
message_version: int = 1
|
||||
identifier: int = 0xFF
|
||||
sequence_number: int = 0
|
||||
response_code: int = 0
|
||||
length: int = 0
|
||||
uuid: int = 0
|
||||
uuids: List[int] = None
|
||||
additional_data: bytes = b''
|
||||
is_request: bool = False
|
||||
riding_data: Optional[HubRidingData] = None
|
||||
|
||||
def __str__(self):
|
||||
uuids_str = ','.join(f'{u:04x}' for u in (self.uuids or []))
|
||||
base_str = (f"vers={self.message_version} Id={self.identifier} sn={self.sequence_number} "
|
||||
f"resp={self.response_code} len={self.length} req?={self.is_request} "
|
||||
f"uuid={self.uuid:04x} dat={self.additional_data.hex()} uuids=[{uuids_str}]")
|
||||
if self.riding_data:
|
||||
base_str += f"\nRiding Data: {self.riding_data}"
|
||||
return base_str
|
||||
|
||||
def parse_dircon_packet(data: bytes, offset: int = 0) -> Tuple[Optional[DirconPacket], int]:
|
||||
if len(data) - offset < 6:
|
||||
return None, 0
|
||||
|
||||
packet = DirconPacket()
|
||||
packet.message_version = data[offset]
|
||||
packet.identifier = data[offset + 1]
|
||||
packet.sequence_number = data[offset + 2]
|
||||
packet.response_code = data[offset + 3]
|
||||
packet.length = (data[offset + 4] << 8) | data[offset + 5]
|
||||
|
||||
total_length = 6 + packet.length
|
||||
if len(data) - offset < total_length:
|
||||
return None, 0
|
||||
|
||||
curr_offset = offset + 6
|
||||
|
||||
if packet.identifier == 0x01: # DPKT_MSGID_DISCOVER_SERVICES
|
||||
if packet.length == 0:
|
||||
packet.is_request = True
|
||||
elif packet.length % 16 == 0:
|
||||
packet.uuids = []
|
||||
while curr_offset + 16 <= offset + total_length:
|
||||
uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
|
||||
packet.uuids.append(uuid)
|
||||
curr_offset += 16
|
||||
|
||||
elif packet.identifier == 0x02: # DPKT_MSGID_DISCOVER_CHARACTERISTICS
|
||||
if packet.length >= 16:
|
||||
packet.uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
|
||||
if packet.length == 16:
|
||||
packet.is_request = True
|
||||
elif (packet.length - 16) % 17 == 0:
|
||||
curr_offset += 16
|
||||
packet.uuids = []
|
||||
packet.additional_data = b''
|
||||
while curr_offset + 17 <= offset + total_length:
|
||||
uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
|
||||
packet.uuids.append(uuid)
|
||||
packet.additional_data += bytes([data[curr_offset + 16]])
|
||||
curr_offset += 17
|
||||
|
||||
elif packet.identifier in [0x03, 0x04, 0x05, 0x06]: # READ/WRITE/NOTIFY characteristics
|
||||
if packet.length >= 16:
|
||||
packet.uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
|
||||
if packet.length > 16:
|
||||
packet.additional_data = data[curr_offset + 16:offset + total_length]
|
||||
if packet.uuid == 0x0002:
|
||||
packet.riding_data = parse_hub_riding_data(packet.additional_data)
|
||||
if packet.identifier != 0x06:
|
||||
packet.is_request = True
|
||||
|
||||
return packet, total_length
|
||||
|
||||
def extract_bytes_from_c_array(content: str) -> List[Tuple[str, bytes]]:
|
||||
packets = []
|
||||
pattern = r'static const unsigned char (\w+)\[\d+\] = \{([^}]+)\};'
|
||||
matches = re.finditer(pattern, content)
|
||||
|
||||
for match in matches:
|
||||
name = match.group(1)
|
||||
hex_str = match.group(2)
|
||||
|
||||
hex_values = []
|
||||
for line in hex_str.split('\n'):
|
||||
line = line.split('//')[0]
|
||||
values = re.findall(r'0x[0-9a-fA-F]{2}', line)
|
||||
hex_values.extend(values)
|
||||
|
||||
byte_data = bytes([int(x, 16) for x in hex_values])
|
||||
packets.append((name, byte_data))
|
||||
|
||||
return packets
|
||||
|
||||
def get_tcp_payload(data: bytes) -> bytes:
|
||||
ip_header_start = 14 # Skip Ethernet header
|
||||
ip_header_len = (data[ip_header_start] & 0x0F) * 4
|
||||
tcp_header_start = ip_header_start + ip_header_len
|
||||
tcp_header_len = ((data[tcp_header_start + 12] >> 4) & 0x0F) * 4
|
||||
payload_start = tcp_header_start + tcp_header_len
|
||||
return data[payload_start:]
|
||||
|
||||
def parse_file(filename: str):
|
||||
with open(filename, 'r') as f:
|
||||
content = f.read()
|
||||
packets = extract_bytes_from_c_array(content)
|
||||
|
||||
for name, data in packets:
|
||||
print(f"\nPacket {name}:")
|
||||
payload = get_tcp_payload(data)
|
||||
print(f"Dircon payload: {payload.hex()}")
|
||||
|
||||
offset = 0
|
||||
while offset < len(payload):
|
||||
packet, consumed = parse_dircon_packet(payload, offset)
|
||||
if packet is None or consumed == 0:
|
||||
break
|
||||
print(f"Frame: {packet}")
|
||||
offset += consumed
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python script.py <input_file>")
|
||||
sys.exit(1)
|
||||
|
||||
parse_file(sys.argv[1])
|
||||
@@ -1,331 +0,0 @@
|
||||
import sys
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
import random
|
||||
import struct
|
||||
import binascii
|
||||
import time
|
||||
from typing import Any, Union
|
||||
|
||||
# Verificare che siamo su macOS
|
||||
if sys.platform != 'darwin':
|
||||
print("Questo script è progettato specificamente per macOS!")
|
||||
sys.exit(1)
|
||||
|
||||
# Importare bless
|
||||
try:
|
||||
from bless import (
|
||||
BlessServer,
|
||||
BlessGATTCharacteristic,
|
||||
GATTCharacteristicProperties,
|
||||
GATTAttributePermissions,
|
||||
)
|
||||
except ImportError:
|
||||
print("Errore: impossibile importare bless. Installarlo con: pip install bless")
|
||||
sys.exit(1)
|
||||
|
||||
# Configurazione logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Trigger per eventi
|
||||
trigger = threading.Event()
|
||||
|
||||
# Informazioni sul dispositivo
|
||||
DEVICE_NAME = "Wahoo KICKR 51A6"
|
||||
|
||||
# UUID dei servizi standard
|
||||
CYCLING_POWER_SERVICE = "00001818-0000-1000-8000-00805f9b34fb"
|
||||
USER_DATA_SERVICE = "0000181c-0000-1000-8000-00805f9b34fb"
|
||||
FITNESS_MACHINE_SERVICE = "00001826-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
# UUID dei servizi Wahoo personalizzati
|
||||
WAHOO_SERVICE_1 = "a026ee01-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_SERVICE_3 = "a026ee03-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_SERVICE_6 = "a026ee06-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_SERVICE_B = "a026ee0b-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
|
||||
# UUID delle caratteristiche standard
|
||||
CYCLING_POWER_MEASUREMENT = "00002a63-0000-1000-8000-00805f9b34fb"
|
||||
CYCLING_POWER_FEATURE = "00002a65-0000-1000-8000-00805f9b34fb"
|
||||
SENSOR_LOCATION = "00002a5d-0000-1000-8000-00805f9b34fb"
|
||||
CYCLING_POWER_CONTROL_POINT = "00002a66-0000-1000-8000-00805f9b34fb"
|
||||
WEIGHT = "00002a98-0000-1000-8000-00805f9b34fb"
|
||||
FITNESS_MACHINE_FEATURE = "00002acc-0000-1000-8000-00805f9b34fb"
|
||||
TRAINING_STATUS = "00002ad3-0000-1000-8000-00805f9b34fb"
|
||||
FITNESS_MACHINE_CONTROL_POINT = "00002ad9-0000-1000-8000-00805f9b34fb"
|
||||
FITNESS_MACHINE_STATUS = "00002ada-0000-1000-8000-00805f9b34fb"
|
||||
INDOOR_BIKE_DATA = "00002ad2-0000-1000-8000-00805f9b34fb"
|
||||
|
||||
# UUID delle caratteristiche Wahoo personalizzate
|
||||
WAHOO_CUSTOM_CP_CHAR = "a026e005-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_CHAR_1 = "a026e002-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_CHAR_2 = "a026e004-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_CHAR_3 = "a026e00a-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_CHAR_4 = "a026e023-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
WAHOO_CHAR_5 = "a026e037-0a7d-4ab3-97fa-f1500f9feb8b"
|
||||
|
||||
# Stato dispositivo - variabili globali
|
||||
current_power = 120
|
||||
current_cadence = 85
|
||||
current_speed = 25.0
|
||||
current_resistance = 5
|
||||
|
||||
# Funzioni di callback
|
||||
def read_request(characteristic, **kwargs):
|
||||
logger.debug(f"Lettura: {characteristic.value}")
|
||||
return characteristic.value
|
||||
|
||||
def write_request(characteristic, value, **kwargs):
|
||||
uuid_str = str(characteristic.uuid).lower()
|
||||
logger.info(f"Scrittura su caratteristica: {uuid_str}, valore: {binascii.hexlify(value)}")
|
||||
|
||||
# Gestione delle richieste di scrittura
|
||||
if uuid_str == FITNESS_MACHINE_CONTROL_POINT.lower():
|
||||
handle_ftms_control_point(value)
|
||||
elif uuid_str == CYCLING_POWER_CONTROL_POINT.lower():
|
||||
handle_cp_control_point(value)
|
||||
elif uuid_str in [WAHOO_CHAR_1.lower(), WAHOO_CHAR_3.lower(), WAHOO_CHAR_4.lower(), WAHOO_CHAR_5.lower()]:
|
||||
handle_wahoo_char_write(uuid_str, value)
|
||||
|
||||
characteristic.value = value
|
||||
|
||||
# Gestori di richieste di scrittura
|
||||
def handle_ftms_control_point(data):
|
||||
global current_power, current_resistance
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
op_code = data[0]
|
||||
logger.info(f"Comando FTMS Control Point: {op_code:#x}")
|
||||
|
||||
if op_code == 0x05: # Set Target Power (ERG mode)
|
||||
if len(data) >= 3:
|
||||
target_power = int.from_bytes(data[1:3], byteorder='little')
|
||||
logger.info(f"Target power impostato: {target_power}W")
|
||||
current_power = target_power
|
||||
|
||||
def handle_cp_control_point(data):
|
||||
if not data:
|
||||
return
|
||||
|
||||
op_code = data[0]
|
||||
logger.info(f"Comando CP Control Point: {op_code:#x}")
|
||||
|
||||
def handle_wahoo_char_write(uuid_str, data):
|
||||
logger.info(f"Scrittura su caratteristica Wahoo {uuid_str}: {binascii.hexlify(data)}")
|
||||
|
||||
# Funzioni per generare dati
|
||||
def generate_cycling_power_data():
|
||||
global current_power, current_cadence
|
||||
|
||||
# Varia leggermente i valori
|
||||
current_power += random.randint(-3, 3)
|
||||
current_power = max(0, min(2000, current_power))
|
||||
|
||||
current_cadence += random.randint(-1, 1)
|
||||
current_cadence = max(0, min(200, current_cadence))
|
||||
|
||||
# Crea Cycling Power Measurement
|
||||
power_data = bytearray(16)
|
||||
power_data[0:2] = (0x0034).to_bytes(2, byteorder='little')
|
||||
power_data[2:4] = (current_power).to_bytes(2, byteorder='little')
|
||||
power_data[4:8] = (int(current_power * 10)).to_bytes(4, byteorder='little')
|
||||
power_data[8:12] = (0).to_bytes(4, byteorder='little')
|
||||
power_data[12:14] = (current_cadence).to_bytes(2, byteorder='little')
|
||||
power_data[14:16] = (0xBAD8).to_bytes(2, byteorder='little')
|
||||
|
||||
return bytes(power_data)
|
||||
|
||||
def generate_indoor_bike_data():
|
||||
global current_speed, current_cadence
|
||||
|
||||
# Varia leggermente i valori
|
||||
current_speed += random.uniform(-0.2, 0.2)
|
||||
current_speed = max(0, min(60.0, current_speed))
|
||||
|
||||
# Crea Indoor Bike Data
|
||||
bike_data = bytearray(8)
|
||||
bike_data[0:2] = (0x0044).to_bytes(2, byteorder='little')
|
||||
bike_data[2:4] = (int(current_speed * 100)).to_bytes(2, byteorder='little')
|
||||
bike_data[4:6] = (current_cadence).to_bytes(2, byteorder='little')
|
||||
bike_data[6:8] = (0).to_bytes(2, byteorder='little')
|
||||
|
||||
return bytes(bike_data)
|
||||
|
||||
async def run():
|
||||
# Crea server con minimo di parametri
|
||||
server = BlessServer(name=DEVICE_NAME)
|
||||
server.read_request_func = read_request
|
||||
server.write_request_func = write_request
|
||||
|
||||
logger.info(f"Configurazione del simulatore {DEVICE_NAME}...")
|
||||
|
||||
# 1. Servizi standard
|
||||
# Aggiungi Cycling Power Service
|
||||
await server.add_new_service(CYCLING_POWER_SERVICE)
|
||||
await server.add_new_characteristic(
|
||||
CYCLING_POWER_SERVICE,
|
||||
CYCLING_POWER_MEASUREMENT,
|
||||
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
CYCLING_POWER_SERVICE,
|
||||
CYCLING_POWER_FEATURE,
|
||||
GATTCharacteristicProperties.read,
|
||||
None,
|
||||
GATTAttributePermissions.readable
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
CYCLING_POWER_SERVICE,
|
||||
CYCLING_POWER_CONTROL_POINT,
|
||||
GATTCharacteristicProperties.write | GATTCharacteristicProperties.indicate,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
CYCLING_POWER_SERVICE,
|
||||
WAHOO_CUSTOM_CP_CHAR,
|
||||
GATTCharacteristicProperties.write | GATTCharacteristicProperties.indicate,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
|
||||
# Aggiungi Fitness Machine Service
|
||||
await server.add_new_service(FITNESS_MACHINE_SERVICE)
|
||||
await server.add_new_characteristic(
|
||||
FITNESS_MACHINE_SERVICE,
|
||||
INDOOR_BIKE_DATA,
|
||||
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
FITNESS_MACHINE_SERVICE,
|
||||
FITNESS_MACHINE_CONTROL_POINT,
|
||||
GATTCharacteristicProperties.write | GATTCharacteristicProperties.indicate,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
FITNESS_MACHINE_SERVICE,
|
||||
FITNESS_MACHINE_FEATURE,
|
||||
GATTCharacteristicProperties.read,
|
||||
None,
|
||||
GATTAttributePermissions.readable
|
||||
)
|
||||
|
||||
# 2. Servizi Wahoo personalizzati
|
||||
# Wahoo Service 1
|
||||
await server.add_new_service(WAHOO_SERVICE_1)
|
||||
await server.add_new_characteristic(
|
||||
WAHOO_SERVICE_1,
|
||||
WAHOO_CHAR_1,
|
||||
GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
WAHOO_SERVICE_1,
|
||||
WAHOO_CHAR_2,
|
||||
GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable
|
||||
)
|
||||
|
||||
# Wahoo Service 3
|
||||
await server.add_new_service(WAHOO_SERVICE_3)
|
||||
await server.add_new_characteristic(
|
||||
WAHOO_SERVICE_3,
|
||||
WAHOO_CHAR_3,
|
||||
GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
|
||||
# Wahoo Service 6
|
||||
await server.add_new_service(WAHOO_SERVICE_6)
|
||||
await server.add_new_characteristic(
|
||||
WAHOO_SERVICE_6,
|
||||
WAHOO_CHAR_4,
|
||||
GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
|
||||
# Wahoo Service B
|
||||
await server.add_new_service(WAHOO_SERVICE_B)
|
||||
await server.add_new_characteristic(
|
||||
WAHOO_SERVICE_B,
|
||||
WAHOO_CHAR_5,
|
||||
GATTCharacteristicProperties.read | GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
|
||||
None,
|
||||
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
|
||||
)
|
||||
|
||||
logger.info("Configurazione dei servizi completata")
|
||||
|
||||
# Avvia il server
|
||||
await server.start()
|
||||
logger.info(f"{DEVICE_NAME} è ora in fase di advertising")
|
||||
|
||||
# Imposta i valori iniziali DOPO l'avvio del server
|
||||
# Valori per servizi standard
|
||||
server.get_characteristic(CYCLING_POWER_MEASUREMENT).value = generate_cycling_power_data()
|
||||
server.get_characteristic(CYCLING_POWER_FEATURE).value = (0x08).to_bytes(4, byteorder='little')
|
||||
server.get_characteristic(INDOOR_BIKE_DATA).value = generate_indoor_bike_data()
|
||||
server.get_characteristic(FITNESS_MACHINE_FEATURE).value = (0x02C6).to_bytes(4, byteorder='little')
|
||||
|
||||
# Valori per caratteristiche Wahoo
|
||||
server.get_characteristic(WAHOO_CHAR_1).value = bytearray(1)
|
||||
server.get_characteristic(WAHOO_CHAR_2).value = bytearray(1)
|
||||
server.get_characteristic(WAHOO_CHAR_3).value = bytearray(1)
|
||||
server.get_characteristic(WAHOO_CHAR_4).value = bytearray(1)
|
||||
server.get_characteristic(WAHOO_CHAR_5).value = bytearray(1)
|
||||
|
||||
# Loop di aggiornamento
|
||||
try:
|
||||
counter = 0
|
||||
while True:
|
||||
# Aggiorna i dati principali
|
||||
server.get_characteristic(INDOOR_BIKE_DATA).value = generate_indoor_bike_data()
|
||||
server.get_characteristic(CYCLING_POWER_MEASUREMENT).value = generate_cycling_power_data()
|
||||
|
||||
# Invia notifiche
|
||||
server.update_value(FITNESS_MACHINE_SERVICE, INDOOR_BIKE_DATA)
|
||||
server.update_value(CYCLING_POWER_SERVICE, CYCLING_POWER_MEASUREMENT)
|
||||
|
||||
if counter % 10 == 0: # Log ogni 10 cicli
|
||||
logger.info(f"Potenza: {current_power}W, Cadenza: {current_cadence}rpm, Velocità: {current_speed:.1f}km/h")
|
||||
|
||||
counter += 1
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Arresto richiesto dall'utente")
|
||||
except Exception as e:
|
||||
logger.error(f"Errore durante l'esecuzione: {e}")
|
||||
finally:
|
||||
await server.stop()
|
||||
logger.info("Server arrestato")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 80)
|
||||
print(f"Wahoo KICKR 51A6 BLE Simulator per macOS (Versione completa)")
|
||||
print("=" * 80)
|
||||
print(f"Avvio della simulazione di {DEVICE_NAME}")
|
||||
print("Premi Ctrl+C per terminare il server")
|
||||
print("=" * 80)
|
||||
|
||||
try:
|
||||
asyncio.run(run())
|
||||
except KeyboardInterrupt:
|
||||
print("\nSimulazione fermata dall'utente")
|
||||
except Exception as e:
|
||||
print(f"Errore: {e}")
|
||||
print("Potrebbe essere necessario eseguire questo script con sudo")
|
||||
sys.exit(1)
|
||||
143
helpers/winbt.py
143
helpers/winbt.py
@@ -1,143 +0,0 @@
|
||||
import sys
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
import random
|
||||
import struct
|
||||
import binascii
|
||||
|
||||
from typing import Any, Union
|
||||
|
||||
from bless import (
|
||||
BlessServer,
|
||||
BlessGATTCharacteristic,
|
||||
GATTCharacteristicProperties,
|
||||
GATTAttributePermissions,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(name=__name__)
|
||||
|
||||
trigger: Union[asyncio.Event, threading.Event]
|
||||
if sys.platform in ["darwin", "win32"]:
|
||||
trigger = threading.Event()
|
||||
else:
|
||||
trigger = asyncio.Event()
|
||||
|
||||
def read_request(characteristic: BlessGATTCharacteristic, **kwargs) -> bytearray:
|
||||
logger.debug(f"Reading {characteristic.value}")
|
||||
return characteristic.value
|
||||
|
||||
def write_request(characteristic: BlessGATTCharacteristic, value: Any, **kwargs):
|
||||
characteristic.value = value
|
||||
logger.debug(f"Char value set to {characteristic.value}")
|
||||
if characteristic.value == b"\x0f":
|
||||
logger.debug("NICE")
|
||||
trigger.set()
|
||||
|
||||
def generate_indoor_bike_data():
|
||||
# Flags (16 bits)
|
||||
flags = (1 << 2) | (1 << 6) # Instantaneous Cadence and Instantaneous Power present
|
||||
|
||||
speed = random.randint(0, 20000) # 0-20000
|
||||
|
||||
# Instantaneous Cadence (uint16, 0.5 rpm resolution)
|
||||
cadence = random.randint(0, 400) # 0-200 rpm
|
||||
|
||||
# Instantaneous Power (sint16, watts)
|
||||
power = random.randint(10, 50)
|
||||
|
||||
# Pack data into bytes
|
||||
data = struct.pack("<HHHh", flags, speed, cadence, power)
|
||||
|
||||
return data
|
||||
|
||||
def generate_zwift_ride_data():
|
||||
data_str = "2308ffbfffff0f1a04080010001a04080110001a04080210001a0408031000"
|
||||
data = binascii.unhexlify(data_str)
|
||||
return data
|
||||
|
||||
async def update_indoor_bike_data(server, service_uuid, char_uuid):
|
||||
while True:
|
||||
c = server.get_characteristic(char_uuid)
|
||||
c.value = bytes(generate_indoor_bike_data())
|
||||
server.update_value(service_uuid, char_uuid)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def update_zwift_ride_data(server, service_uuid, char_uuid):
|
||||
while True:
|
||||
c = server.get_characteristic(char_uuid)
|
||||
c.value = bytes(generate_zwift_ride_data())
|
||||
server.update_value(service_uuid, char_uuid)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
async def run(loop):
|
||||
trigger.clear()
|
||||
|
||||
# Instantiate the server
|
||||
server = BlessServer(name="FTMS Indoor Bike", loop=loop)
|
||||
server.read_request_func = read_request
|
||||
server.write_request_func = write_request
|
||||
|
||||
# Add Fitness Machine Service
|
||||
ftms_uuid = "00001826-0000-1000-8000-00805f9b34fb"
|
||||
await server.add_new_service(ftms_uuid)
|
||||
|
||||
# Add Indoor Bike Data Characteristic
|
||||
indoor_bike_data_uuid = "00002ad2-0000-1000-8000-00805f9b34fb"
|
||||
char_flags = (
|
||||
GATTCharacteristicProperties.read
|
||||
| GATTCharacteristicProperties.notify
|
||||
)
|
||||
permissions = GATTAttributePermissions.readable
|
||||
await server.add_new_characteristic(
|
||||
ftms_uuid, indoor_bike_data_uuid, char_flags, generate_indoor_bike_data(), permissions
|
||||
)
|
||||
|
||||
zwift_ride_uuid = "00000001-19ca-4651-86e5-fa29dcdd09d1"
|
||||
await server.add_new_service(zwift_ride_uuid)
|
||||
|
||||
syncRxChar = "00000003-19CA-4651-86E5-FA29DCDD09D1"
|
||||
syncRx_flags = (
|
||||
GATTCharacteristicProperties.write
|
||||
)
|
||||
syncRx_permissions = GATTAttributePermissions.writeable
|
||||
|
||||
syncTxChar = "00000004-19CA-4651-86E5-FA29DCDD09D1"
|
||||
syncTx_flags = (
|
||||
GATTCharacteristicProperties.read
|
||||
| GATTCharacteristicProperties.indicate
|
||||
)
|
||||
syncTx_permissions = GATTAttributePermissions.readable
|
||||
|
||||
asyncChar = "00000002-19CA-4651-86E5-FA29DCDD09D1"
|
||||
async_flags = (
|
||||
GATTCharacteristicProperties.read
|
||||
| GATTCharacteristicProperties.notify
|
||||
)
|
||||
async_permissions = GATTAttributePermissions.readable
|
||||
await server.add_new_characteristic(
|
||||
zwift_ride_uuid, syncRxChar, syncRx_flags, generate_indoor_bike_data(), syncRx_permissions
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
zwift_ride_uuid, syncTxChar, syncTx_flags, generate_indoor_bike_data(), syncTx_permissions
|
||||
)
|
||||
await server.add_new_characteristic(
|
||||
zwift_ride_uuid, asyncChar, async_flags, generate_zwift_ride_data(), async_permissions
|
||||
)
|
||||
|
||||
logger.debug(server.get_characteristic(indoor_bike_data_uuid))
|
||||
await server.start()
|
||||
logger.debug("Advertising")
|
||||
logger.info(f"FTMS Indoor Bike is now advertising")
|
||||
|
||||
# Start updating the indoor bike data
|
||||
update_task = asyncio.create_task(update_indoor_bike_data(server, ftms_uuid, indoor_bike_data_uuid))
|
||||
update_task_zwift_ride = asyncio.create_task(update_zwift_ride_data(server, zwift_ride_uuid, asyncChar))
|
||||
|
||||
await asyncio.sleep(99999999)
|
||||
await server.stop()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(run(loop))
|
||||
@@ -1,28 +0,0 @@
|
||||
import json
|
||||
|
||||
def generate_code(hex_string, start_index):
|
||||
hex_pairs = [hex_string[i:i+2] for i in range(0, len(hex_string), 2)]
|
||||
output = ""
|
||||
array_name = f"initData{start_index}"
|
||||
array_elements = ', '.join([f"0x{hex_pair}" for hex_pair in hex_pairs])
|
||||
output += f"uint8_t {array_name}[] = {{{array_elements}}};\n"
|
||||
output += f'writeCharacteristic({array_name}, sizeof({array_name}), QStringLiteral("init"), false, false);\n'
|
||||
output += "QThread::msleep(sleepms);\n\n"
|
||||
return output
|
||||
|
||||
json_file_path = "C:\\Work\\qdomyos-zwift\\helpers\\tmp.json"
|
||||
|
||||
with open(json_file_path, 'r') as file:
|
||||
# Carica i dati JSON
|
||||
json_data = json.load(file)
|
||||
|
||||
|
||||
line = 0
|
||||
|
||||
for item in json_data:
|
||||
try:
|
||||
if(item['_source']['layers']['btatt']['btatt.value_raw'][0] != ''):
|
||||
line = line + 1
|
||||
print(generate_code(item['_source']['layers']['btatt']['btatt.value_raw'][0], line))
|
||||
except:
|
||||
pass
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,449 +0,0 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Also update SONAME in the Makefile whenever you change these. */
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 9
|
||||
#define HTTP_PARSER_VERSION_PATCH 4
|
||||
|
||||
#include <stddef.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
|
||||
#include <BaseTsd.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#elif (defined(__sun) || defined(__sun__)) && defined(__SunOS_5_9)
|
||||
#include <sys/inttypes.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Maximium header size allowed. If the macro is not defined
|
||||
* before including this header then the default is used. To
|
||||
* change the maximum header size, define the macro in the build
|
||||
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
|
||||
* the effective limit on the size of the header, define the macro
|
||||
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
|
||||
*/
|
||||
#ifndef HTTP_MAX_HEADER_SIZE
|
||||
# define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
#endif
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* Returning `2` from on_headers_complete will tell parser that it should not
|
||||
* expect neither a body nor any futher responses on this connection. This is
|
||||
* useful for handling responses to a CONNECT request which may not contain
|
||||
* `Upgrade` or `Connection: upgrade` headers.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be called arbitrarily
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
/* Status Codes */
|
||||
#define HTTP_STATUS_MAP(XX) \
|
||||
XX(100, CONTINUE, Continue) \
|
||||
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
|
||||
XX(102, PROCESSING, Processing) \
|
||||
XX(200, OK, OK) \
|
||||
XX(201, CREATED, Created) \
|
||||
XX(202, ACCEPTED, Accepted) \
|
||||
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
|
||||
XX(204, NO_CONTENT, No Content) \
|
||||
XX(205, RESET_CONTENT, Reset Content) \
|
||||
XX(206, PARTIAL_CONTENT, Partial Content) \
|
||||
XX(207, MULTI_STATUS, Multi-Status) \
|
||||
XX(208, ALREADY_REPORTED, Already Reported) \
|
||||
XX(226, IM_USED, IM Used) \
|
||||
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
|
||||
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
|
||||
XX(302, FOUND, Found) \
|
||||
XX(303, SEE_OTHER, See Other) \
|
||||
XX(304, NOT_MODIFIED, Not Modified) \
|
||||
XX(305, USE_PROXY, Use Proxy) \
|
||||
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
|
||||
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
|
||||
XX(400, BAD_REQUEST, Bad Request) \
|
||||
XX(401, UNAUTHORIZED, Unauthorized) \
|
||||
XX(402, PAYMENT_REQUIRED, Payment Required) \
|
||||
XX(403, FORBIDDEN, Forbidden) \
|
||||
XX(404, NOT_FOUND, Not Found) \
|
||||
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
|
||||
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
|
||||
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
|
||||
XX(408, REQUEST_TIMEOUT, Request Timeout) \
|
||||
XX(409, CONFLICT, Conflict) \
|
||||
XX(410, GONE, Gone) \
|
||||
XX(411, LENGTH_REQUIRED, Length Required) \
|
||||
XX(412, PRECONDITION_FAILED, Precondition Failed) \
|
||||
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
|
||||
XX(414, URI_TOO_LONG, URI Too Long) \
|
||||
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
|
||||
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
|
||||
XX(417, EXPECTATION_FAILED, Expectation Failed) \
|
||||
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
|
||||
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
|
||||
XX(423, LOCKED, Locked) \
|
||||
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
|
||||
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
|
||||
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
|
||||
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
|
||||
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
|
||||
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
|
||||
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
|
||||
XX(501, NOT_IMPLEMENTED, Not Implemented) \
|
||||
XX(502, BAD_GATEWAY, Bad Gateway) \
|
||||
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
|
||||
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
|
||||
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
|
||||
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
|
||||
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
|
||||
XX(508, LOOP_DETECTED, Loop Detected) \
|
||||
XX(510, NOT_EXTENDED, Not Extended) \
|
||||
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
|
||||
|
||||
enum http_status
|
||||
{
|
||||
#define XX(num, name, string) HTTP_STATUS_##name = num,
|
||||
HTTP_STATUS_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* WebDAV */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
XX(16, BIND, BIND) \
|
||||
XX(17, REBIND, REBIND) \
|
||||
XX(18, UNBIND, UNBIND) \
|
||||
XX(19, ACL, ACL) \
|
||||
/* subversion */ \
|
||||
XX(20, REPORT, REPORT) \
|
||||
XX(21, MKACTIVITY, MKACTIVITY) \
|
||||
XX(22, CHECKOUT, CHECKOUT) \
|
||||
XX(23, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(24, MSEARCH, M-SEARCH) \
|
||||
XX(25, NOTIFY, NOTIFY) \
|
||||
XX(26, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(28, PATCH, PATCH) \
|
||||
XX(29, PURGE, PURGE) \
|
||||
/* CalDAV */ \
|
||||
XX(30, MKCALENDAR, MKCALENDAR) \
|
||||
/* RFC-2068, section 19.6.1.2 */ \
|
||||
XX(31, LINK, LINK) \
|
||||
XX(32, UNLINK, UNLINK) \
|
||||
/* icecast */ \
|
||||
XX(33, SOURCE, SOURCE) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_CONNECTION_UPGRADE = 1 << 3
|
||||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
, F_CONTENTLENGTH = 1 << 7
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
XX(CB_status, "the on_status callback failed") \
|
||||
XX(CB_chunk_header, "the on_chunk_header callback failed") \
|
||||
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(UNEXPECTED_CONTENT_LENGTH, \
|
||||
"unexpected content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred") \
|
||||
XX(INVALID_TRANSFER_ENCODING, \
|
||||
"request has invalid transfer-encoding") \
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned int type : 2; /* enum http_parser_type */
|
||||
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned int state : 7; /* enum state from http_parser.c */
|
||||
unsigned int header_state : 7; /* enum header_state from http_parser.c */
|
||||
unsigned int index : 5; /* index into current matcher */
|
||||
unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */
|
||||
unsigned int allow_chunked_length : 1; /* Allow headers with both
|
||||
* `Content-Length` and
|
||||
* `Transfer-Encoding: chunked` set */
|
||||
unsigned int lenient_http_headers : 1;
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one)
|
||||
* if no Content-Length header.
|
||||
*/
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned int status_code : 16; /* responses only */
|
||||
unsigned int method : 8; /* requests only */
|
||||
unsigned int http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned int upgrade : 1;
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_status;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
/* When on_chunk_header is called, the current chunk length is stored
|
||||
* in parser->content_length.
|
||||
*/
|
||||
http_cb on_chunk_header;
|
||||
http_cb on_chunk_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
/* Returns the library version. Bits 16-23 contain the major version number,
|
||||
* bits 8-15 the minor version number and bits 0-7 the patch level.
|
||||
* Usage example:
|
||||
*
|
||||
* unsigned long version = http_parser_version();
|
||||
* unsigned major = (version >> 16) & 255;
|
||||
* unsigned minor = (version >> 8) & 255;
|
||||
* unsigned patch = version & 255;
|
||||
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
|
||||
*/
|
||||
unsigned long http_parser_version(void);
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
/* Initialize http_parser_settings members to 0
|
||||
*/
|
||||
void http_parser_settings_init(http_parser_settings *settings);
|
||||
|
||||
|
||||
/* Executes the parser. Returns number of parsed bytes. Sets
|
||||
* `parser->http_errno` on error. */
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns 0, then this should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Returns a string version of the HTTP status code. */
|
||||
const char *http_status_str(enum http_status s);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
/* Initialize all http_parser_url members to 0 */
|
||||
void http_parser_url_init(struct http_parser_url *u);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
/* Checks if this is the final chunk of the body. */
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
/* Change the maximum header size provided at compile time. */
|
||||
void http_parser_set_max_header_size(uint32_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"list": "cpp",
|
||||
"chrono": "cpp",
|
||||
"complex": "cpp",
|
||||
"functional": "cpp",
|
||||
"optional": "cpp",
|
||||
"system_error": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"xlocnum": "cpp",
|
||||
"xtr1common": "cpp",
|
||||
"qhttpserver": "cpp",
|
||||
"array": "cpp",
|
||||
"deque": "cpp",
|
||||
"map": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"xstring": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"xutility": "cpp",
|
||||
"xlocale": "cpp",
|
||||
"filesystem": "cpp",
|
||||
"bitset": "cpp",
|
||||
"iterator": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xtree": "cpp",
|
||||
"ostream": "cpp",
|
||||
"locale": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
CONFIG+=ordered
|
||||
|
||||
!ios: !android: {
|
||||
SUBDIRS = \
|
||||
src/qdomyos-zwift-lib.pro \
|
||||
src/qdomyos-zwift.pro \
|
||||
tst/qdomyos-zwift-tests.pro
|
||||
|
||||
tst.depends = src/qdomyos-zwift-lib.pro
|
||||
}
|
||||
|
||||
android: {
|
||||
SUBDIRS = \
|
||||
src/qdomyos-zwift.pro
|
||||
}
|
||||
|
||||
ios: {
|
||||
SUBDIRS = \
|
||||
src/qdomyos-zwift-lib.pro \
|
||||
src/qdomyos-zwift.pro
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1018,7 +1018,7 @@ public class QtBluetoothLE {
|
||||
|
||||
/*
|
||||
* Already executed in GattCallback so executed by the HandlerThread. No need to
|
||||
* post it to the Handler.
|
||||
* post it to the Hander.
|
||||
*/
|
||||
private void scheduleMtuExchange() {
|
||||
ReadWriteJob newJob = new ReadWriteJob();
|
||||
|
||||
@@ -385,11 +385,11 @@ QT_USE_NAMESPACE
|
||||
}
|
||||
|
||||
[uuids addObject:nsUuid];
|
||||
// With the latest CoreBluetooth, we can synchronously retrieve peripherals:
|
||||
// With the latest CoreBluetooth, we can synchronously retrive peripherals:
|
||||
QT_BT_MAC_AUTORELEASEPOOL;
|
||||
NSArray *const peripherals = [manager retrievePeripheralsWithIdentifiers:uuids];
|
||||
if (!peripherals || peripherals.count != 1) {
|
||||
qCWarning(QT_BT_OSX) << "failed to retrieve a peripheral";
|
||||
qCWarning(QT_BT_OSX) << "failed to retrive a peripheral";
|
||||
if (notifier)
|
||||
emit notifier->CBManagerError(QLowEnergyController::UnknownRemoteDeviceError);
|
||||
return;
|
||||
@@ -648,7 +648,7 @@ QT_USE_NAMESPACE
|
||||
servicesToDiscoverDetails.removeAll(serviceUuid);
|
||||
|
||||
const NSUInteger nHandles = qt_countGATTEntries(service);
|
||||
Q_ASSERT_X(nHandles, Q_FUNC_INFO, "unexpected number of GATT entries");
|
||||
Q_ASSERT_X(nHandles, Q_FUNC_INFO, "unexpected number of GATT entires");
|
||||
|
||||
const QLowEnergyHandle maxHandle = std::numeric_limits<QLowEnergyHandle>::max();
|
||||
if (nHandles >= maxHandle || lastValidHandle > maxHandle - nHandles) {
|
||||
@@ -1058,7 +1058,7 @@ QT_USE_NAMESPACE
|
||||
|
||||
// TODO: test that we NEVER have the same characteristic twice in array!
|
||||
// At the moment I just protect against this by iterating in a reverse
|
||||
// order (at least avoiding a potential infinite loop with '-indexOfObject:').
|
||||
// order (at least avoiding a potential inifite loop with '-indexOfObject:').
|
||||
NSArray *const cs = service.characteristics;
|
||||
if (cs.count == 1)
|
||||
return nil;
|
||||
@@ -1091,7 +1091,7 @@ QT_USE_NAMESPACE
|
||||
|
||||
// TODO: test that we NEVER have the same characteristic twice in array!
|
||||
// At the moment I just protect against this by iterating in a reverse
|
||||
// order (at least avoiding a potential infinite loop with '-indexOfObject:').
|
||||
// order (at least avoiding a potential inifite loop with '-indexOfObject:').
|
||||
NSArray *const cs = service.characteristics;
|
||||
if (cs.count == 1)
|
||||
return nil;
|
||||
@@ -1597,8 +1597,8 @@ QT_USE_NAMESPACE
|
||||
} else {
|
||||
// This is (probably) the result of update notification.
|
||||
// It's very possible we can have an invalid handle here (0) -
|
||||
// if something else is wrong (we subscribed for a notification),
|
||||
// disconnected (but other application is connected) and still receiving
|
||||
// if something esle is wrong (we subscribed for a notification),
|
||||
// disconnected (but other application is connected) and still receiveing
|
||||
// updated values ...
|
||||
// TODO: this must be properly tested.
|
||||
if (!chHandle) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
QMAKE_PRL_BUILD_DIR = C:/qt-everywhere-src-5.15.2/qtconnectivity/src/bluetooth
|
||||
QMAKE_PRO_INPUT = bluetooth.pro
|
||||
QMAKE_PRL_TARGET = Qt5Bluetooth.lib
|
||||
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin windows prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl flat debug_and_release precompile_header autogen_precompile_source embed_manifest_dll embed_manifest_exe shared shared release no_plugin_manifest win32 msvc copy_dir_files sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd rdseed shani x86SimdAlways prefix_build force_independent utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions release ReleaseBuild Release build_pass c++11 generated_privates relative_qt_rpath target_qt c++11 strict_c++ c++14 c++1z qt_install_headers need_fwd_pri qt_install_module debug_and_release build_all create_cmake skip_target_version_ext release ReleaseBuild Release build_pass have_target dll exclusive_builds debug_info no_autoqmake thread moc resources
|
||||
QMAKE_PRL_VERSION = 5.15.2
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
QMAKE_PRL_BUILD_DIR = C:/qt-everywhere-src-5.15.2/qtconnectivity/src/bluetooth
|
||||
QMAKE_PRO_INPUT = bluetooth.pro
|
||||
QMAKE_PRL_TARGET = Qt5Bluetoothd.lib
|
||||
QMAKE_PRL_CONFIG = lex yacc debug depend_includepath testcase_targets import_plugins import_qpa_plugin windows prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on link_prl flat debug_and_release precompile_header autogen_precompile_source embed_manifest_dll embed_manifest_exe shared shared no_plugin_manifest win32 msvc copy_dir_files sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd rdseed shani x86SimdAlways prefix_build force_independent utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions debug DebugBuild Debug build_pass c++11 generated_privates relative_qt_rpath target_qt c++11 strict_c++ c++14 c++1z qt_install_headers need_fwd_pri qt_install_module debug_and_release build_all create_cmake skip_target_version_ext debug DebugBuild Debug build_pass have_target dll no_plist exclusive_builds debug_info no_autoqmake thread moc resources
|
||||
QMAKE_PRL_VERSION = 5.15.2
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,87 +10,59 @@ ColumnLayout {
|
||||
property alias textFont: accordionText.font.family
|
||||
property alias textFontSize: accordionText.font.pixelSize
|
||||
property alias indicatRectColor: indicatRect.color
|
||||
default property alias accordionContent: contentLoader.sourceComponent
|
||||
|
||||
// Signal emitted when content becomes visible
|
||||
signal contentBecameVisible()
|
||||
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.fillWidth: true;
|
||||
|
||||
Rectangle {
|
||||
id: accordionHeader
|
||||
color: "red"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.fillWidth: true;
|
||||
height: 48
|
||||
|
||||
Rectangle {
|
||||
id: indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
x: 34; y: 13
|
||||
x:34;y:13
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
|
||||
Image {
|
||||
y: 13
|
||||
anchors.right: parent.right
|
||||
y:13
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
width: 30; height: 30
|
||||
id: indicatImg
|
||||
source: "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
rootElement.isOpen = !rootElement.isOpen
|
||||
if(rootElement.isOpen) {
|
||||
if(rootElement.isOpen)
|
||||
{
|
||||
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
|
||||
} else {
|
||||
}else{
|
||||
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loader with enhanced visibility handling
|
||||
Loader {
|
||||
id: contentLoader
|
||||
active: rootElement.isOpen
|
||||
visible: false // Start invisible
|
||||
Layout.fillWidth: true
|
||||
asynchronous: false
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.Layout.fillWidth = true
|
||||
visible = true
|
||||
rootElement.contentBecameVisible()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle visibility changes
|
||||
onVisibleChanged: {
|
||||
if (visible && status === Loader.Ready) {
|
||||
rootElement.contentBecameVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle accordion closing
|
||||
onIsOpenChanged: {
|
||||
if (!isOpen) {
|
||||
contentLoader.visible = false
|
||||
}
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
<Package xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\" xmlns:mp=\"http://schemas.microsoft.com/appx/2014/phone/manifest\" xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\" xmlns:uap3=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/3\" xmlns:mobile=\"http://schemas.microsoft.com/appx/manifest/mobile/windows10\" xmlns:iot=\"http://schemas.microsoft.com/appx/manifest/iot/windows10\" IgnorableNamespaces=\"uap uap3 mp mobile iot\">
|
||||
<Identity Name=\"35433QZdiRobertoViola.QZFitness\" Publisher=\"CN=CA24F902-6882-40DF-B1E3-2E1B81CD730C\" Version=\"2.10.83.0\" ProcessorArchitecture=\"x64\"/>
|
||||
<Properties>
|
||||
<DisplayName>QZ Fitness</DisplayName>
|
||||
<PublisherDisplayName>QZ di Roberto Viola</PublisherDisplayName>
|
||||
<Description>QZ Fitness syncs fitness bikes, ellipticals, rowers, and treadmills to training apps like Echelon, Peloton, Zwift and Strava providing you with an unparalleled level of connectivity that keeps you informed and in control of your workouts</Description>
|
||||
<Logo>50.png</Logo>
|
||||
</Properties>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name=\"Windows.Universal\" MinVersion=\"10.0.19041.0\" MaxVersionTested=\"10.0.19041.0\"/>
|
||||
<PackageDependency Name=\"Microsoft.VCLibs.140.00\" MinVersion=\"14.0.0.0\" Publisher=\"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US\"/>
|
||||
</Dependencies>
|
||||
<Resources>
|
||||
<Resource Language=\"en\"/>
|
||||
</Resources>
|
||||
<Applications>
|
||||
<Application Id=\"App\" Executable=\"qdomyos-zwift.exe\" EntryPoint=\"qdomyos-zwift.App\">
|
||||
<uap:VisualElements DisplayName=\"qdomyos-zwift\" Description=\"Default package description\" BackgroundColor=\"green\" Square150x150Logo=\"150.png\" Square44x44Logo=\"44.png\">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
<Capabilities>
|
||||
<Capability Name=\"internetClient\"/>
|
||||
<Capability Name=\"internetClientServer\"/>
|
||||
<Capability Name=\"privateNetworkClientServer\"/>
|
||||
<DeviceCapability Name=\"bluetooth.genericAttributeProfile\"/>
|
||||
<DeviceCapability Name=\"bluetooth.rfcomm\"/>
|
||||
<DeviceCapability Name=\"location\"/>
|
||||
</Capabilities>
|
||||
</Package>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
|
||||
<Identity Name="35433QZdiRobertoViola.QZFitness" Version="2.10.74.0" Publisher="CN=CA24F902-6882-40DF-B1E3-2E1B81CD730C" ProcessorArchitecture="x64" />
|
||||
<Properties>
|
||||
<DisplayName>QZ Fitness</DisplayName>
|
||||
<PublisherDisplayName>QZ di Roberto Viola</PublisherDisplayName>
|
||||
<Description>QZ Fitness syncs fitness bikes, ellipticals, rowers, and treadmills to training apps like Echelon, Peloton, Zwift and Strava providing you with an unparalleled level of connectivity that keeps you informed and in control of your workouts</Description>
|
||||
<Logo>iTunesArtwork@2x.png</Logo>
|
||||
</Properties>
|
||||
<Resources>
|
||||
<Resource Language="en" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.15063.0" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust"/>
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application Id="QZ"
|
||||
Executable="qdomyos-zwift.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
BackgroundColor="#464646"
|
||||
DisplayName="QZ"
|
||||
Square150x150Logo="iTunesArtwork@2x.png"
|
||||
Square44x44Logo="iTunesArtwork@2x.png"
|
||||
Description="QZ Fitness syncs fitness bikes, ellipticals, rowers, and treadmills to training apps like Echelon, Peloton, Zwift and Strava providing you with an unparalleled level of connectivity that keeps you informed and in control of your workouts" />
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Loader {
|
||||
id: chartFooterLoader
|
||||
sourceComponent: ChartFooterInnerJS
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: CHARTJS ? "ChartFooterInnerJS.qml":"ChartFooterInnerNoJS.qml"
|
||||
onLoaded: {
|
||||
if(CHARTJS) {
|
||||
chartFooterLoader.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
import QtWebView 1.1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
}
|
||||
WebView {
|
||||
id: webView
|
||||
anchors.fill: parent
|
||||
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/chartjs/chartlive.htm"
|
||||
visible: rootItem.chartFooterVisible
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.errorString) {
|
||||
console.error(loadRequest.errorString);
|
||||
console.error("port " + settings.value("template_inner_QZWS_port"));
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
console.log("onVisibleChanged" + visible)
|
||||
if(visible === true) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
}
|
||||
}
|
||||
@@ -19,19 +19,11 @@ ColumnLayout {
|
||||
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/chartjs/chart.htm"
|
||||
visible: true
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.errorString) {
|
||||
if (loadRequest.errorString)
|
||||
console.error(loadRequest.errorString);
|
||||
console.error("port " + settings.value("template_inner_QZWS_port"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: chartJscheckStartFromWeb
|
||||
interval: 200; running: true; repeat: true
|
||||
onTriggered: {if(rootItem.startRequested) {rootItem.startRequested = false; rootItem.stopRequested = false; stackView.pop(); }}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: closeButton
|
||||
height: 50
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AvailableLibraries</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>ConnectIQ.framework/ConnectIQ</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>ConnectIQ.framework</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>BinaryPath</key>
|
||||
<string>ConnectIQ.framework/ConnectIQ</string>
|
||||
<key>LibraryIdentifier</key>
|
||||
<string>ios-arm64_x86_64-simulator</string>
|
||||
<key>LibraryPath</key>
|
||||
<string>ConnectIQ.framework</string>
|
||||
<key>SupportedArchitectures</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>x86_64</string>
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
<key>SupportedPlatformVariant</key>
|
||||
<string>simulator</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XFWK</string>
|
||||
<key>XCFrameworkFormatVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -1,237 +0,0 @@
|
||||
//
|
||||
// ConnectIQ.h
|
||||
// ConnectIQ
|
||||
//
|
||||
// Copyright (c) 2014 Garmin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ConnectIQ/IQConstants.h>
|
||||
#import <ConnectIQ/IQDevice.h>
|
||||
#import <ConnectIQ/IQApp.h>
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - PUBLIC TYPES
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief SendMessage progress callback block
|
||||
///
|
||||
/// @param sentBytes The number of bytes that have been successfully transferred
|
||||
/// to the device so far for this connection.
|
||||
/// @param totalBytes The total number of bytes to transfer for this connection.
|
||||
typedef void (^IQSendMessageProgress)(uint32_t sentBytes, uint32_t totalBytes);
|
||||
|
||||
/// @brief SendMessage completion callback block
|
||||
///
|
||||
/// @param result The result of the SendMessage operation.
|
||||
typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
|
||||
|
||||
/// @brief Conforming to the IQUIOverrideDelegate protocol indicates that an
|
||||
/// object handles one or more events triggered by the ConnectIQ SDK that
|
||||
/// require user input.
|
||||
@protocol IQUIOverrideDelegate <NSObject>
|
||||
@optional
|
||||
/// @brief Called by the ConnectIQ SDK when an action has been requested that
|
||||
/// requires Garmin Connect Mobile to be installed.
|
||||
///
|
||||
/// The receiver should choose whether or not to launch the Apple App
|
||||
/// Store page for GCM, ideally by presenting the user with a choice.
|
||||
///
|
||||
/// If the receiver of this message decides to install GCM, it must call
|
||||
/// showAppStoreForConnectMobile.
|
||||
- (void)needsToInstallConnectMobile;
|
||||
@end
|
||||
|
||||
/// @brief Conforming to the IQDeviceEventDelegate protocol indicates that an
|
||||
/// object handles ConnectIQ device status events.
|
||||
@protocol IQDeviceEventDelegate <NSObject>
|
||||
@optional
|
||||
/// @brief Called by the ConnectIQ SDK when an IQDevice's connection status has
|
||||
/// changed.
|
||||
///
|
||||
/// @param device The IQDevice whose status changed.
|
||||
/// @param status The new status of the device.
|
||||
- (void)deviceStatusChanged:(IQDevice *)device status:(IQDeviceStatus)status;
|
||||
@end
|
||||
|
||||
/// @brief Conforming to the IQAppMessageDelegate protocol indicates that an
|
||||
/// object handles messages from ConnectIQ apps on compatible devices.
|
||||
@protocol IQAppMessageDelegate <NSObject>
|
||||
@optional
|
||||
/// @brief Called by the ConnectIQ SDK when a message is received from a device.
|
||||
///
|
||||
/// @param message The message that was received.
|
||||
/// @param app The device app that sent the message.
|
||||
- (void)receivedMessage:(id)message fromApp:(IQApp *)app;
|
||||
@end
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - CLASS DEFINITION
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief The root of the ConnectIQ SDK API.
|
||||
@interface ConnectIQ : NSObject
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - SINGLETON ACCESS
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief Exposes the single static instance of the ConnectIQ class.
|
||||
///
|
||||
/// @return The single status instance of the ConnectIQ class.
|
||||
+ (ConnectIQ *)sharedInstance;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - INITIALIZATION
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief Initializes the ConnectIQ SDK with startup parameters necessary for
|
||||
/// its operation.
|
||||
///
|
||||
/// @param urlScheme The URL scheme for this companion app. When Garmin Connect
|
||||
/// Mobile is launched, it will return to the companion app by
|
||||
/// launching a URL with this scheme.
|
||||
/// @param delegate The delegate that the SDK will use for notifying the
|
||||
/// companion app about events that require user input. If this
|
||||
/// is nil, the SDK's default UI will be used.
|
||||
- (void)initializeWithUrlScheme:(NSString *)urlScheme uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - EXTERNAL LAUNCHING
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief Launches the Apple App Store page for the Garmin Connect Mobile app.
|
||||
/// The companion app should only call this in response to a
|
||||
/// needsToInstallConnectMobile event that gets triggered on the
|
||||
/// IQUIOverrideDelegate.
|
||||
- (void)showAppStoreForConnectMobile;
|
||||
|
||||
/// @brief Launches Garmin Connect Mobile for the purpose of retrieving a list of
|
||||
/// ConnectIQ-compatible devices.
|
||||
///
|
||||
/// Once the user has chosen which ConnectIQ devices to share with the
|
||||
/// companion app, GCM will return those devices to the companion app by
|
||||
/// opening a URL with the scheme registered in
|
||||
/// initializeWithUrlScheme:uiOverrideDelegate:.
|
||||
///
|
||||
/// The companion app should handle this URL by passing it in to the
|
||||
/// parseDeviceSelectionResponseFromURL: method to get the list of devices
|
||||
/// that the user permitted the companion app to communicate with.
|
||||
- (void)showConnectIQDeviceSelection;
|
||||
|
||||
/// @brief Parses a URL opened from Garmin Connect Mobile into a list of devices.
|
||||
///
|
||||
/// @param url The URL to parse.
|
||||
///
|
||||
/// @return An array of IQDevice objects representing the ConnectIQ-compatible
|
||||
/// devices that the user allowed GCM to share with the companion app.
|
||||
///
|
||||
/// @seealso showConnectIQDeviceSelection
|
||||
- (NSArray *)parseDeviceSelectionResponseFromURL:(NSURL *)url;
|
||||
|
||||
/// @brief Launches Garmin Connect Mobile and shows the ConnectIQ app store page
|
||||
/// for the given app.
|
||||
///
|
||||
/// The companion app should call this if the user would like to manage
|
||||
/// the app on the device, such as to install, upgrade, uninstall, or
|
||||
/// modify settings.
|
||||
///
|
||||
/// @param app The app to show the ConnectIQ app store page for.
|
||||
- (void)showConnectIQStoreForApp:(IQApp *)app;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - DEVICE MANAGEMENT
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief Registers an object as a listener for ConnectIQ device status events.
|
||||
///
|
||||
/// A device may have multiple device event listeners if this method is
|
||||
/// called more than once.
|
||||
///
|
||||
/// @param device A device to listen for status events from.
|
||||
/// @param delegate The listener which will receive status events for this device.
|
||||
- (void)registerForDeviceEvents:(IQDevice *)device delegate:(id<IQDeviceEventDelegate>)delegate;
|
||||
|
||||
/// @brief Unregisters a listener for a specific device.
|
||||
///
|
||||
/// @param device The device to unregister the listener for.
|
||||
/// @param delegate The listener to remove from the device.
|
||||
- (void)unregisterForDeviceEvents:(IQDevice *)device delegate:(id<IQDeviceEventDelegate>)delegate;
|
||||
|
||||
/// @brief Unregisters the specified listener from all devices for which it had
|
||||
/// previously been registered.
|
||||
///
|
||||
/// @param delegate The listener to unregister.
|
||||
- (void)unregisterForAllDeviceEvents:(id<IQDeviceEventDelegate>)delegate;
|
||||
|
||||
/// @brief Gets the current connection status of a device.
|
||||
///
|
||||
/// The device must have been registered for event notifications by
|
||||
/// calling registerForDeviceEvents:delegate: or this method will return
|
||||
/// IQDeviceStatus_InvalidDevice.
|
||||
///
|
||||
/// @param device The device to get the status for.
|
||||
///
|
||||
/// @return The device's current connection status.
|
||||
- (IQDeviceStatus)getDeviceStatus:(IQDevice *)device;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
#pragma mark - APP MANAGEMENT
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
/// @brief Begins getting the status of an app on a device. This method returns
|
||||
/// immediately.
|
||||
///
|
||||
/// @param app The IQApp to get the status for.
|
||||
/// @param completion The completion block that will be triggered when the device
|
||||
/// status operation is complete.
|
||||
- (void)getAppStatus:(IQApp *)app completion:(void(^)(IQAppStatus *appStatus))completion;
|
||||
|
||||
/// @brief Registers an object as a listener for ConnectIQ messages from an app
|
||||
/// on a device.
|
||||
///
|
||||
/// An app may have multiple message listeners if this method is called
|
||||
/// more than once.
|
||||
///
|
||||
/// @param app The app to listen for messages from.
|
||||
/// @param delegate The listener which will receive messages for this app.
|
||||
- (void)registerForAppMessages:(IQApp *)app delegate:(id<IQAppMessageDelegate>)delegate;
|
||||
|
||||
/// @brief Unregisters a listener for a specific app.
|
||||
///
|
||||
/// @param app The app to unregister a listener for.
|
||||
/// @param delegate The listener to remove from the app.
|
||||
- (void)unregisterForAppMessages:(IQApp *)app delegate:(id<IQAppMessageDelegate>)delegate;
|
||||
|
||||
/// @brief Unregisters all previously registered apps for a specific listener.
|
||||
///
|
||||
/// @param delegate The listener to unregister.
|
||||
- (void)unregisterForAllAppMessages:(id<IQAppMessageDelegate>)delegate;
|
||||
|
||||
/// @brief Begins sending a message to an app. This method returns immediately.
|
||||
///
|
||||
/// @param message The message to send to the app. This message must be one of
|
||||
/// the following types: NSString, NSNumber, NSNull, NSArray,
|
||||
/// or NSDictionary. Arrays and dictionaries may be nested.
|
||||
/// @param app The app to send the message to.
|
||||
/// @param progress A progress block that will be triggered periodically
|
||||
/// throughout the transfer. This is guaranteed to be triggered
|
||||
/// at least once.
|
||||
/// @param completion A completion block that will be triggered when the send
|
||||
/// message operation is complete.
|
||||
- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion;
|
||||
|
||||
/// @brief Sends an open app request message request to the device. This method returns immediately.
|
||||
///
|
||||
/// @param app The app to open.
|
||||
/// @param completion A completion block that will be triggered when the send
|
||||
/// message operation is complete.
|
||||
- (void)openAppRequest:(IQApp *)app completion:(IQSendMessageCompletion)completion;
|
||||
|
||||
// TODO *** Holding off on documenting this until this method actually works.
|
||||
- (void)sendImage:(NSData *)bitmap toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion;
|
||||
|
||||
@end
|
||||
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// IQApp.h
|
||||
// ConnectIQ
|
||||
//
|
||||
// Copyright (c) 2014 Garmin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ConnectIQ/IQDevice.h>
|
||||
#import <ConnectIQ/IQAppStatus.h>
|
||||
|
||||
/// @brief Represents an instance of a ConnectIQ app that is installed on a
|
||||
/// Garmin device.
|
||||
@interface IQApp : NSObject <NSSecureCoding>
|
||||
|
||||
/// @brief The unique identifier for this app.
|
||||
@property (nonatomic, readonly) NSUUID *uuid;
|
||||
|
||||
/// @brief The unique identifier for this app in the store.
|
||||
@property (nonatomic, readonly) NSUUID *storeUuid;
|
||||
|
||||
/// @brief The device that this app is installed on.
|
||||
@property (nonatomic, readonly) IQDevice *device;
|
||||
|
||||
/// @brief Creates a new app instance.
|
||||
///
|
||||
/// @param uuid The UUID of the app to create.
|
||||
/// @param storeUuid The store UUID of the app to create.
|
||||
/// @param device The device the app to create is installed on.
|
||||
///
|
||||
/// @return A new IQApp instance with the appropriate values set.
|
||||
+ (IQApp *)appWithUUID:(NSUUID *)uuid storeUuid:(NSUUID *)storeUuid device:(IQDevice *)device;
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// IQAppStatus.h
|
||||
// ConnectIQ
|
||||
//
|
||||
// Copyright (c) 2014 Garmin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// @brief Represents the current status of an app on a Garmin device.
|
||||
@interface IQAppStatus : NSObject
|
||||
|
||||
/// @brief YES if the app is installed on the device, NO if it isn't.
|
||||
@property (nonatomic, readonly) BOOL isInstalled;
|
||||
|
||||
/// @brief The version of the app that is currently installed on the device. If
|
||||
/// the app is not installed, this value is unused.
|
||||
@property (nonatomic, readonly) uint16_t version;
|
||||
|
||||
@end
|
||||
@@ -1,66 +0,0 @@
|
||||
//
|
||||
// IQConstants.h
|
||||
// ConnectIQ
|
||||
//
|
||||
// Copyright (c) 2014 Garmin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// @brief The current version of the ConnectIQ SDK.
|
||||
extern int const IQSDKVersion;
|
||||
|
||||
/// @brief The bundle identifier for the Garmin Connect Mobile app.
|
||||
extern NSString * const IQGCMBundle;
|
||||
|
||||
/// @brief The bundle identifier for the Garmin Connect Mobile Beta app.
|
||||
extern NSString * const IQGCMInternalBetaBundle;
|
||||
|
||||
/// @brief The result of a SendMessage operation
|
||||
typedef NS_ENUM(NSInteger, IQSendMessageResult){
|
||||
///! @brief The message was sent successfully.
|
||||
IQSendMessageResult_Success,
|
||||
|
||||
/// @brief The message failed to send due to an unknown error.
|
||||
IQSendMessageResult_Failure_Unknown,
|
||||
|
||||
/// @brief The message failed to send. There was an error within the SDK or
|
||||
/// on the device.
|
||||
IQSendMessageResult_Failure_InternalError,
|
||||
|
||||
/// @brief The message failed to send. The device is not available right now.
|
||||
IQSendMessageResult_Failure_DeviceNotAvailable,
|
||||
|
||||
/// @brief The message failed to send. The app is not installed on the
|
||||
/// device.
|
||||
IQSendMessageResult_Failure_AppNotFound,
|
||||
|
||||
/// @brief The message failed to send. The device is busy and cannot receive
|
||||
/// the message right now.
|
||||
IQSendMessageResult_Failure_DeviceIsBusy,
|
||||
|
||||
/// @brief The message failed to send. The message contained an unsupported
|
||||
/// type.
|
||||
IQSendMessageResult_Failure_UnsupportedType,
|
||||
|
||||
/// @brief The message failed to send. The device does not have enough memory
|
||||
/// to receive the message.
|
||||
IQSendMessageResult_Failure_InsufficientMemory,
|
||||
|
||||
/// @brief The message failed to send. The connection timed out while sending
|
||||
/// the message.
|
||||
IQSendMessageResult_Failure_Timeout,
|
||||
|
||||
/// @brief The message failed to send and was retried, but could not complete
|
||||
/// after a number of tries.
|
||||
IQSendMessageResult_Failure_MaxRetries,
|
||||
|
||||
/// @brief The message was received by the device but it chose not to display
|
||||
/// a message prompt, ignoring the message.
|
||||
IQSendMessageResult_Failure_PromptNotDisplayed,
|
||||
|
||||
/// @brief The message was received by the device but the app to open
|
||||
/// was already running on the device.
|
||||
IQSendMessageResult_Failure_AppAlreadyRunning,
|
||||
};
|
||||
NSString *NSStringFromSendMessageResult(IQSendMessageResult value);
|
||||
@@ -1,61 +0,0 @@
|
||||
//
|
||||
// IQDevice.h
|
||||
// ConnectIQ
|
||||
//
|
||||
// Copyright (c) 2014 Garmin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
/// @brief The current status of an IQDevice.
|
||||
typedef NS_ENUM(NSInteger, IQDeviceStatus){
|
||||
/// @brief No device with this UUID has been registered for status events
|
||||
/// the SDK.
|
||||
IQDeviceStatus_InvalidDevice,
|
||||
|
||||
/// @brief Bluetooth is either powered off or resetting.
|
||||
IQDeviceStatus_BluetoothNotReady,
|
||||
|
||||
/// @brief This device could not be found by iOS. Perhaps the user removed
|
||||
/// the device?
|
||||
IQDeviceStatus_NotFound,
|
||||
|
||||
/// @brief The device is recognized by iOS, but it is not currently
|
||||
/// connected.
|
||||
IQDeviceStatus_NotConnected,
|
||||
|
||||
/// @brief The device is connected and ready to communicate.
|
||||
IQDeviceStatus_Connected,
|
||||
};
|
||||
|
||||
/// @brief Represents a ConnectIQ-compatible Garmin device.
|
||||
@interface IQDevice : NSObject <NSSecureCoding>
|
||||
|
||||
/// @brief The unique identifier for this device.
|
||||
@property (nonatomic, readonly) NSUUID *uuid;
|
||||
|
||||
/// @brief The model name of the device provided by Garmin Connect Mobile.
|
||||
@property (nonatomic, readonly) NSString *modelName;
|
||||
|
||||
/// @brief The friendly name of the device, set by the user and provided by
|
||||
/// Garmin Connect Mobile.
|
||||
@property (nonatomic, readonly) NSString *friendlyName;
|
||||
|
||||
/// @brief Creates a new device instance.
|
||||
///
|
||||
/// @param uuid The UUID of the device to create.
|
||||
/// @param modelName The model name of the device to create.
|
||||
/// @param friendlyName The friendly name of the device to create.
|
||||
///
|
||||
/// @return A new IQDevice instance with the appropriate values set.
|
||||
+ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName;
|
||||
|
||||
/// @brief Creates a new device instance by copying another device's values.
|
||||
///
|
||||
/// @param device The device to copy values from.
|
||||
///
|
||||
/// @return A new IQDevice instance with all values copied.
|
||||
- (instancetype)initWithDevice:(IQDevice *)device;
|
||||
|
||||
@end
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
framework module ConnectIQ {
|
||||
umbrella header "ConnectIQ.h"
|
||||
export *
|
||||
|
||||
module * { export * }
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user