diff --git a/.github/workflows/build.py b/.github/workflows/build.py index 0f15cef..debc99f 100644 --- a/.github/workflows/build.py +++ b/.github/workflows/build.py @@ -7,7 +7,7 @@ Import("env") # determine the latest releasev semver -latest_release_tag = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE, text=True) +latest_release_tag = subprocess.run(["git", "tag", "-l", "--sort=-committerdate"], stdout=subprocess.PIPE, text=True) latest_release_tag = latest_release_tag.stdout.strip() print ("\033[93;1;4mLatest Release Tag : " + latest_release_tag + "\033[0m") @@ -19,6 +19,12 @@ latest_release_main = latest_release_digits[0] latest_release_minor = latest_release_digits[1] latest_release_patch = latest_release_digits[2] + +# increase patch for local development build +latest_release_patch = str(int(latest_release_patch) + 1) +latest_release_semver = latest_release_main + "." + latest_release_minor + "." + latest_release_patch +latest_release_semver_incl_v = "v" + latest_release_semver + print ("\033[93;1;4mLatest Release Main : " + latest_release_main + "\033[0m") print ("\033[93;1;4mLatest Release Minor : " + latest_release_minor + "\033[0m") print ("\033[93;1;4mLatest Release Patch : " + latest_release_patch + "\033[0m") diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c85e30..104dd11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: echo "fulltag = [$fulltag]" declare versiontag=$(echo $fulltag | cut -d'-' -f1) echo "extract SemVer numbers from version tag [$versiontag]" - declare -i lastmajordigit=$(echo $versiontag | cut -c 1- | cut -d'.' -f1) + declare -i lastmajordigit=$(echo $versiontag | cut -c 2- | cut -d'.' -f1) echo "lastmajordigit = $lastmajordigit" declare -i lastminordigit=$(echo $versiontag | cut -c 1- | cut -d'.' -f2) echo "lastminordigit = $lastminordigit" @@ -169,10 +169,6 @@ jobs: - name: Install PlatformIO Core run: pip install --upgrade platformio - - name: Setup OTA settings file - run: | - cp ota.ini.example ota.ini - - name: Build run: | pio run -e wt32-eth01 diff --git a/README.md b/README.md index 79a5e1c..43ab0ae 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ As the Zwift Cog has 14 teeth and a standard chainring 34 teeth the default rati Based on this ratio and the selected ratio from Zwift (e.g. 1.23 ~ "Gear 5") the ratio that will later be applied to the trainer's force will be calculated: + $R_{relative} = R_{selected} / R_{standard}$ Example: @@ -115,11 +116,12 @@ This new grade value is then sent to the trainer in the track resistance mode an The "Target Power" mode uses a different approach. Here we want to set an expected watts value depending on the current track and speed. To calculate the speed we assume a default wheel diameter $d$ of 0.7m and take the current cadence $c$ from the trainer that is being multiplied with the wanted gear ratio. The product is then being multiplied with the calculated perimeter of the wheel: -$V_{gs} = (c · R_{selected}) · (d * \pi)$ + +$V_{gs} = (c · R_{selected}) · (d · \pi)$ This speed is then taken to calculate the geared total force [as described above](#calculate-total-geared-force) which is then being used to calculate the needed power $P$: -$P = F_{totalGeared} * V_{gs}$ +$P = F_{totalGeared} · V_{gs}$ This value is then being sent to the trainer in the target power mode as in ERG mode but of course is updated on every parameter change. @@ -150,13 +152,13 @@ This value is then being sent to the trainer in the target power mode as in ERG To start the WT32-ETH01 in boot mode it is necessary to connect "IO0" with GND and then to reset the board, shortly connect "EN" to GND for a quarter of a second. ## Software installation -- Make a copy of the provided ``ota.ini.example`` file and name it ``ota.ini`` (you can adjust the values in the file to your needs but also leave it as it is). This is because the credentials shouldn't be committed to GitHub and so they are stored in a separate file that is on the .gitignore list. - Open the project in [PlatformIO](https://platformio.org) and let it install the dependencies - Connect the programmer and upload via the task wt32-eth01 -> Upload and Monitor - Connect an ethernet cable and make sure a DHCP server exists in your network - After a few seconds you should be able to see the IP address and the hostname (like e.g. "SHIFTR-123456.local") in the monitor log -- If you don't use the Ethernet interface you can also use the WiFi functionality. For that the device opens an AP with the device name (e.g. "SHIFTR 123456") and the password is the same but with "-" instead of space. Please note that WiFi is provided by an external library called [IoTWebConf](https://github.com/prampec/IotWebConf) and more or less untested +- If you don't use the Ethernet interface you can also use the WiFi functionality. For that the device opens an AP with the device name (e.g. "SHIFTR-123456") and the password is the same but of course can be changed later. Please note that WiFi is provided by an external library called [IoTWebConf](https://github.com/prampec/IotWebConf) and is more or less untested - After that you can configure the device and its network settings in the web interface on e.g. http://SHIFTR-123456.local +- The status page is accessible without credentials, the settings and firmware update pages need the username "admin" and the password is the configured AP password or just the device name (e.g. "SHIFTR-123456") as default. Note that both are case sensitive! - Further updates can be made over ethernet or WiFi so you can disconnect the programming adapter now ## Finalization diff --git a/include/Config.h b/include/Config.h index 2f7c143..60a2654 100644 --- a/include/Config.h +++ b/include/Config.h @@ -4,6 +4,8 @@ #define ST(A) #A #define STR(A) ST(A) +#define DEFAULT_USERNAME "admin" + #define DEVICE_NAME_PREFIX "SHIFTR" #define WEB_SERVER_PORT 80 diff --git a/include/SettingsManager.h b/include/SettingsManager.h index 7c8e8f0..93d778f 100644 --- a/include/SettingsManager.h +++ b/include/SettingsManager.h @@ -22,7 +22,7 @@ typedef enum VirtualShiftingMode { class SettingsManager { public: - static void initialize(); + static void initialize(IotWebConf* iotWebConf); static uint16_t getChainringTeeth(); static uint16_t getSprocketTeeth(); static void setChainringTeeth(uint16_t chainringTeeth); @@ -35,6 +35,8 @@ class SettingsManager { static std::string getTrainerDeviceName(); static void setTrainerDeviceName(std::string trainerDevice); static IotWebConfParameterGroup* getIoTWebConfSettingsParameterGroup(); + static std::string getUsername(); + static std::string getAPPassword(); private: static char iotWebConfChainringTeethParameterValue[]; @@ -50,6 +52,7 @@ class SettingsManager { static IotWebConfSelectParameter iotWebConfVirtualShiftingModeParameter; static IotWebConfCheckboxParameter iotWebConfVirtualShiftingParameter; static IotWebConfTextParameter iotWebConfTrainerDeviceParameter; + static IotWebConf* iotWebConf; }; #endif diff --git a/include/Version.h b/include/Version.h index 344c63f..d3b91b3 100644 --- a/include/Version.h +++ b/include/Version.h @@ -7,10 +7,10 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "v0.0.0" -#define VERSION_MAJOR 0 +#define VERSION "v1.0.1" +#define VERSION_MAJOR 1 #define VERSION_MINOR 0 -#define VERSION_PATCH 0 -#define VERSION_BUILD "0000000" -#define VERSION_TIMESTAMP "2024-Nov-18 19:00:00" +#define VERSION_PATCH 1 +#define VERSION_BUILD "cdc7c8a" +#define VERSION_TIMESTAMP "2024-Nov-24 23:07:28" #endif diff --git a/ota.ini.example b/ota.ini.example deleted file mode 100644 index 587a17d..0000000 --- a/ota.ini.example +++ /dev/null @@ -1,6 +0,0 @@ -[ota] -upload_port = wt32-eth01.local -username = "admin" -password = "password" - - diff --git a/platformio.ini b/platformio.ini index d2db173..5b0a96d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,6 +1,3 @@ -[platformio] -extra_configs = ota.ini - [env:wt32-eth01] platform = espressif32 board = wt32-eth01 @@ -14,8 +11,6 @@ lib_deps = prampec/IotWebConf @ ^3.2.1 build_flags = -DSERIAL_DEBUG_BAUDRATE=${env:wt32-eth01.monitor_speed} - -DOTA_USERNAME=${ota.username} - -DOTA_PASSWORD=${ota.password} -DCORE_DEBUG_LEVEL=3 board_build.embed_files = src-web/index.html @@ -26,10 +21,3 @@ board_build.embed_files = [env:wt32-eth01_local] extends = env:wt32-eth01 extra_scripts = pre:.github/workflows/build.py - -[env:wt32-eth01_ota] -extends = env:wt32-eth01 -upload_protocol = espota -upload_port = ${ota.upload_port} -upload_flags = --auth=${ota.password} - diff --git a/src/SettingsManager.cpp b/src/SettingsManager.cpp index 13e88b6..7b99b2f 100644 --- a/src/SettingsManager.cpp +++ b/src/SettingsManager.cpp @@ -1,4 +1,6 @@ #include +#include +#include char SettingsManager::iotWebConfChainringTeethParameterValue[16]; char SettingsManager::iotWebConfSprocketTeethParameterValue[16]; @@ -16,7 +18,10 @@ IotWebConfSelectParameter SettingsManager::iotWebConfVirtualShiftingModeParamete IotWebConfCheckboxParameter SettingsManager::iotWebConfVirtualShiftingParameter = IotWebConfCheckboxParameter("Virtual shifting", "virtual_shifting", SettingsManager::iotWebConfVirtualShiftingParameterValue, sizeof(iotWebConfVirtualShiftingParameterValue), true); IotWebConfTextParameter SettingsManager::iotWebConfTrainerDeviceParameter = IotWebConfTextParameter("Trainer device", "trainer_device", iotWebConfTrainerDeviceParameterValue, sizeof(iotWebConfTrainerDeviceParameterValue), ""); -void SettingsManager::initialize() { +IotWebConf* SettingsManager::iotWebConf; + +void SettingsManager::initialize(IotWebConf* iotWebConf) { + SettingsManager::iotWebConf = iotWebConf; iotWebConfSettingsGroup.addItem(&iotWebConfTrainerDeviceParameter); iotWebConfSettingsGroup.addItem(&iotWebConfVirtualShiftingParameter); iotWebConfSettingsGroup.addItem(&iotWebConfChainringTeethParameter); @@ -97,4 +102,17 @@ void SettingsManager::setTrainerDeviceName(std::string trainerDevice) { IotWebConfParameterGroup* SettingsManager::getIoTWebConfSettingsParameterGroup() { return &iotWebConfSettingsGroup; -} \ No newline at end of file +} + +std::string SettingsManager::getUsername() { + return std::string(DEFAULT_USERNAME); +} + +std::string SettingsManager::getAPPassword() { + iotwebconf::Parameter* aPPasswordParameter = iotWebConf->getApPasswordParameter(); + std::string aPPassword = std::string(aPPasswordParameter->valueBuffer); + if (aPPassword.length() == 0) { + aPPassword = Utils::getHostName().c_str(); + } + return aPPassword; +} diff --git a/src/main.cpp b/src/main.cpp index a4f9567..e937d38 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -27,12 +26,11 @@ bool isMDNSStarted = false; bool isBLEConnected = false; bool isEthernetConnected = false; bool isWiFiConnected = false; -bool isOTAInProgress = false; DNSServer dnsServer; WebServer webServer(WEB_SERVER_PORT); HTTPUpdateServer updateServer; -IotWebConf iotWebConf(Utils::getDeviceName().c_str(), &dnsServer, &webServer, Utils::getHostName().c_str(), WIFI_CONFIG_VERSION); +IotWebConf iotWebConf(Utils::getHostName().c_str(), &dnsServer, &webServer, Utils::getHostName().c_str(), WIFI_CONFIG_VERSION); ServiceManager serviceManager; @@ -49,29 +47,6 @@ void setup() { } log_i("Bluetooth device manager initialized"); - // initialize OTA - ArduinoOTA.onStart([]() { - log_i("OTA started"); - isOTAInProgress = true; - }); - ArduinoOTA.onEnd([]() { - log_i("OTA finished, rebooting..."); - delay(1000); - ESP.restart(); - }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - log_i("OTA progress: %u%%", (progress / (total / 100))); - }); - ArduinoOTA.onError([](ota_error_t error) { - log_e("OTA error: %u, rebooting...", error); - delay(1000); - ESP.restart(); - }); - ArduinoOTA.setHostname(Utils::getHostName().c_str()); - ArduinoOTA.setMdnsEnabled(false); - ArduinoOTA.setPassword(STR(OTA_PASSWORD)); - log_i("OTA initialized"); - // initialize network events WiFi.onEvent(networkEvent); log_i("Network events initialized"); @@ -81,25 +56,46 @@ void setup() { log_i("Ethernet interface initialized"); // initialize settings manager - SettingsManager::initialize(); + SettingsManager::initialize(&iotWebConf); // initialize wifi manager and web server iotWebConf.setStatusPin(WIFI_STATUS_PIN); iotWebConf.setConfigPin(WIFI_CONFIG_PIN); iotWebConf.addParameterGroup(SettingsManager::getIoTWebConfSettingsParameterGroup()); iotWebConf.setupUpdateServer( - [](const char* updatePath) { updateServer.setup(&webServer, updatePath); }, - [](const char* userName, char* password) { updateServer.updateCredentials(STR(OTA_USERNAME), STR(OTA_PASSWORD)); }); + [](const char* updatePath) { updateServer.setup(&webServer, updatePath, SettingsManager::getUsername().c_str(), SettingsManager::getAPPassword().c_str()); }, + [](const char* userName, char* password) { updateServer.updateCredentials(userName, password); }); iotWebConf.init(); + + // workaround for missing thing name + strncpy(iotWebConf.getThingNameParameter()->valueBuffer, Utils::getHostName().c_str(), iotWebConf.getThingNameParameter()->getLength()); + webServer.on("/debug", handleWebServerDebug); webServer.on("/status", handleWebServerStatus); webServer.on("/favicon.ico", [] { handleWebServerFile("favicon.ico"); }); webServer.on("/style.css", [] { handleWebServerFile("style.css"); }); webServer.on("/", [] { handleWebServerFile("index.html"); }); - webServer.on("/settings", HTTP_GET, [] { handleWebServerFile("settings.html"); }); - webServer.on("/settings", HTTP_POST, [] { handleWebServerSettingsPost(); }); - webServer.on("/devicesettings", [] { handleWebServerSettings(); }); - webServer.on("/config", [] { iotWebConf.handleConfig(); }); + webServer.on("/settings", HTTP_GET, [] { + if (!webServer.authenticate(SettingsManager::getUsername().c_str(), SettingsManager::getAPPassword().c_str())) { + return webServer.requestAuthentication(); + } + handleWebServerFile("settings.html"); }); + webServer.on("/settings", HTTP_POST, [] { + if (!webServer.authenticate(SettingsManager::getUsername().c_str(), SettingsManager::getAPPassword().c_str())) { + return webServer.requestAuthentication(); + } + handleWebServerSettingsPost(); }); + webServer.on("/devicesettings", [] { + if (!webServer.authenticate(SettingsManager::getUsername().c_str(), SettingsManager::getAPPassword().c_str())) { + return webServer.requestAuthentication(); + } + handleWebServerSettings(); }); + webServer.on("/config", [] { + if (!webServer.authenticate(SettingsManager::getUsername().c_str(), SettingsManager::getAPPassword().c_str())) { + return webServer.requestAuthentication(); + } + iotWebConf.handleConfig(); }); + webServer.onNotFound([]() { iotWebConf.handleNotFound(); }); log_i("WiFi manager and web server initialized"); @@ -137,13 +133,9 @@ void setup() { } void loop() { - if (!isOTAInProgress) { - BTDeviceManager::update(); - DirConManager::update(); - iotWebConf.doLoop(); - } else { - ArduinoOTA.handle(); - } + BTDeviceManager::update(); + DirConManager::update(); + iotWebConf.doLoop(); } void networkEvent(WiFiEvent_t event) { @@ -158,8 +150,6 @@ void networkEvent(WiFiEvent_t event) { case ARDUINO_EVENT_ETH_GOT_IP: log_i("Ethernet DHCP successful with IP %u.%u.%u.%u", ETH.localIP()[0], ETH.localIP()[1], ETH.localIP()[2], ETH.localIP()[3]); isEthernetConnected = true; - ArduinoOTA.end(); - ArduinoOTA.begin(); break; case ARDUINO_EVENT_ETH_DISCONNECTED: log_i("Ethernet disconnected"); @@ -172,8 +162,6 @@ void networkEvent(WiFiEvent_t event) { case ARDUINO_EVENT_WIFI_STA_GOT_IP: log_i("WiFi DHCP successful with IP %u.%u.%u.%u", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]); isWiFiConnected = true; - ArduinoOTA.end(); - ArduinoOTA.begin(); break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: log_i("WiFi disconnected");