diff --git a/README.md b/README.md index 4fb66c9..ac490a7 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,5 @@ MQTT server: This produces eventgrid.azure.pem certificate file. +openssl s_client -showcerts -connect www.bigfoot-inc.com:443 + diff --git a/certs/bigfoot-inc.pem b/certs/bigfoot-inc.pem new file mode 100755 index 0000000..d79f018 --- /dev/null +++ b/certs/bigfoot-inc.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- + diff --git a/main/App.cpp b/main/App.cpp index 7a7a383..6a92780 100644 --- a/main/App.cpp +++ b/main/App.cpp @@ -72,16 +72,13 @@ void App::init() void App::otaCheck() { - ota_update::OTA ota(*this); + OTA ota(*this); ota.start(); } void App::start() { - // while(true) - // { - // vTaskDelay(pdMS_TO_TICKS(10020)); - // } + } void App::readSensors() diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 56ac3d5..1d3ad1c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,8 +1,8 @@ -idf_component_register(SRCS main.cpp App.cpp Settings.cpp Led.cpp TaskFactory.cpp Wifi.cpp utilities.cpp +idf_component_register(SRCS main.cpp App.cpp Settings.cpp Led.cpp TaskMgr.cpp Wifi.cpp utilities.cpp ProvisionSoftAP.cpp ReaderWriter.cpp OTA.cpp Mqtt.cpp INCLUDE_DIRS "." EMBED_TXTFILES ../html/logo.png ../html/provision.html - EMBED_TXTFILES ../certs/eventgrid.azure.pem ../certs/client1-authn-ID.key ../certs/client1-authn-ID.pem + EMBED_TXTFILES ../certs/eventgrid.azure.pem ../certs/client1-authn-ID.key ../certs/client1-authn-ID.pem ../certs/bigfoot-inc.pem ) #message(FATAL_ERROR "error ${ROLE}") diff --git a/main/Led.cpp b/main/Led.cpp index abdd236..a3fdc14 100644 --- a/main/Led.cpp +++ b/main/Led.cpp @@ -1,6 +1,7 @@ /// © MiroZ 2024 #include "Settings.h" +#include "app_config.h" #include "Led.h" @@ -106,7 +107,7 @@ Led::Led(uint32_t pin) assert(m_queue = xQueueCreate(5, sizeof(struct LED_QUEUE))); // create the led task - m_task = TASK_FACTORY.createTask(std::bind(&Led::run, this), "led task", 2096, 5, 0); + m_task = TaskMgr::getInstance().createTask(std::bind(&Led::run, this), LED_TASK_NAME, LED_TASK_STACK_SIZE, LED_TASK_PRIORITY, LED_TASK_CORE); } Led::~Led() diff --git a/main/Led.h b/main/Led.h index f43518f..21bd3fe 100644 --- a/main/Led.h +++ b/main/Led.h @@ -5,7 +5,7 @@ #include #include -#include "TaskFactory.h" +#include "TaskMgr.h" class Led { diff --git a/main/Mqtt.cpp b/main/Mqtt.cpp index 2d534a4..af5aca4 100644 --- a/main/Mqtt.cpp +++ b/main/Mqtt.cpp @@ -3,7 +3,8 @@ #include "Mqtt.h" #include -#include "TaskFactory.h" +#include "TaskMgr.h" +#include "app_config.h" static const char * mqtt_broker = "mqtt-dev-server.westus2-1.ts.eventgrid.azure.net"; static const char * topic = "wellnuotopics/topic1"; @@ -51,9 +52,7 @@ void Mqtt::task() else ESP_LOGE(TAG, "%d publish failed", n); - ESP_LOGI(TAG, "%d", TaskFactory::getStackHighWaterMark()); - - delay(50000); + delay(20000); n++; } @@ -70,9 +69,11 @@ void Mqtt::start() m_esp_client->setCACert((const char*)server_cert); m_esp_client->setCertificate((const char *)client_cert); // for client verification m_esp_client->setPrivateKey((const char *)client_key); // for client verification - + m_mqtt_client->setServer(mqtt_broker, mqtt_port); m_mqtt_client->setCallback(std::bind(&Mqtt::callback, this, _1, _2, _3)); + m_mqtt_client->setKeepAlive(30); + m_mqtt_client->setSocketTimeout(30); - m_task = TASK_FACTORY.createTask(std::bind(&Mqtt::task, this), "mqtt task", 6144, 5, 0); + m_task = TaskMgr::getInstance().createTask(std::bind(&Mqtt::task, this), MQTT_TASK_NAME, MQTT_TASK_STACK_SIZE, MQTT_TASK_PRIORITY, MQTT_TASK_CORE); } \ No newline at end of file diff --git a/main/OTA.cpp b/main/OTA.cpp index 3d80ef8..2f5e04c 100644 --- a/main/OTA.cpp +++ b/main/OTA.cpp @@ -18,25 +18,21 @@ #include "OTA.h" +#include + +#define OTA_URL "https://bigfoot-inc.com/fw/wellhub.bin" + +extern const uint8_t server_cert[] asm("_binary_bigfoot_inc_pem_start"); -#define OTA_URL "http://192.168.1.131/wellhub.bin" static const char *TAG = "OTA"; -namespace ota_update -{ OTA::OTA(App & app) : m_app(app) { } -void OTA::httpCleanup(esp_http_client_handle_t client) -{ - esp_http_client_close(client); - esp_http_client_cleanup(client); -} - void OTA::taskFatalError() { ESP_LOGE(TAG, "Exiting task due to fatal error..."); @@ -57,6 +53,25 @@ void OTA::start() ESP_LOGW(TAG, "Starting OTA check..."); + HTTPClient client; + + client.begin(OTA_URL, (const char *)server_cert); + int http_code = client.GET(); + + if(http_code != HTTP_CODE_OK) + { + ESP_LOGE(TAG, "Server error %d", http_code); + return; + } + + int remote_len = client.getSize(); + ESP_LOGI(TAG, "remote file len %d", remote_len); + + if(remote_len < 4096) + return; + + WiFiClient* stream = client.getStreamPtr(); + const esp_partition_t *configured = esp_ota_get_boot_partition(); const esp_partition_t *running = esp_ota_get_running_partition(); @@ -70,27 +85,6 @@ void OTA::start() ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", running->type, running->subtype, running->address); - esp_http_client_config_t config = { }; - config.url = OTA_URL; - - esp_http_client_handle_t client = esp_http_client_init(&config); - if (client == NULL) - { - ESP_LOGE(TAG, "Failed to initialize HTTP connection"); - } - err = esp_http_client_open(client, 0); - if (err != ESP_OK) - { - ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err)); - esp_http_client_cleanup(client); - return; - } - esp_http_client_fetch_headers(client); - - int remote_len = esp_http_client_get_content_length(client); - - ESP_LOGI(TAG,"remote len %d", remote_len); - update_partition = esp_ota_get_next_update_partition(NULL); ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", update_partition->subtype, update_partition->address); @@ -101,17 +95,15 @@ void OTA::start() bool image_header_was_checked = false; while (1) { - int data_read = esp_http_client_read(client, otaWriteData, BUFFSIZE); +// int len = stream->available(); + int data_read = stream->readBytes(otaWriteData, BUFFSIZE); if (data_read < 0) { ESP_LOGE(TAG, "Error: SSL data read error"); - esp_http_client_close(client); - esp_http_client_cleanup(client); - taskFatalError(); + return; } else if (data_read > 0) { - //printf("."); if (image_header_was_checked == false) { // this is the first 1k of received image @@ -159,15 +151,15 @@ void OTA::start() ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version); ESP_LOGW(TAG, "The firmware has been rolled back to the previous version."); - httpCleanup(client); + // httpCleanup(client); return; } } + // TODO: add date/time check if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version) + sizeof(new_app_info.project_name) + sizeof(new_app_info.time) + sizeof(new_app_info.date)) == 0) { ESP_LOGI(TAG, "Current running version is the same as a remote. No need for update."); - httpCleanup(client); return; } @@ -180,22 +172,22 @@ void OTA::start() if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); - httpCleanup(client); + // httpCleanup(client); taskFatalError(); } } else { ESP_LOGE(TAG, "received package does not fit len"); - httpCleanup(client); - taskFatalError(); + return; } } err = esp_ota_write(update_handle, (const void *) otaWriteData, data_read); + if (err != ESP_OK) { - httpCleanup(client); + ESP_LOGE(TAG, "Error: esp_ota_write error"); taskFatalError(); } binary_file_length += data_read; @@ -208,11 +200,10 @@ void OTA::start() last_reported = progress; ESP_LOGI(TAG, "%d%%", last_reported); } - } else if (data_read == 0) { - ESP_LOGI(TAG, "Connection closed,all data received"); + ESP_LOGI(TAG, "All data received"); break; } } @@ -222,7 +213,7 @@ void OTA::start() if (esp_ota_end(update_handle) != ESP_OK) { ESP_LOGE(TAG, "esp_ota_end failed!"); - httpCleanup(client); + // httpCleanup(client); taskFatalError(); } @@ -232,7 +223,7 @@ void OTA::start() if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); - httpCleanup(client); + // httpCleanup(client); taskFatalError(); } ESP_LOGI(TAG, "Prepare to restart system!"); @@ -242,5 +233,4 @@ void OTA::start() OTA::~OTA() { -} } \ No newline at end of file diff --git a/main/OTA.h b/main/OTA.h index 9a90c73..4b77de8 100644 --- a/main/OTA.h +++ b/main/OTA.h @@ -12,10 +12,7 @@ #define BUFFSIZE 1024 #define HASH_LEN 32 /* SHA-256 digest length */ -namespace ota_update -{ #include "App.h" -#include "esp_http_client.h" class OTA @@ -25,7 +22,6 @@ public: private: char otaWriteData[BUFFSIZE + 1] = { 0 }; - void httpCleanup(esp_http_client_handle_t client); App & m_app; private: @@ -35,6 +31,5 @@ public: OTA(App & app); virtual ~OTA(); }; -} #endif /* __OTA_H__ */ \ No newline at end of file diff --git a/main/Settings.h b/main/Settings.h index 1cefb65..1eb102e 100644 --- a/main/Settings.h +++ b/main/Settings.h @@ -121,6 +121,7 @@ protected: private: Settings(); + public: static Settings & getInstance(); diff --git a/main/TaskFactory.cpp b/main/TaskFactory.cpp deleted file mode 100644 index 429bedd..0000000 --- a/main/TaskFactory.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/// © MiroZ 2024 - -#include "TaskFactory.h" - -TaskFactory::TaskFactory() -{ -#ifdef USE_MUTEX - m_xMutex = xSemaphoreCreateMutex(); - assert(m_xMutex); -#endif -} - -TaskFactory::~TaskFactory() -{ -#ifdef USE_MUTEX - if(m_xMutex != NULL) - vSemaphoreDelete(m_xMutex); -#endif -} - -TaskHandle_t TaskFactory::createTask(TASK_FUNCTION task_function, const char *name, uint32_t stack_size, UBaseType_t priority, BaseType_t core) -{ - TaskHandle_t task_handle = nullptr; - -#ifdef USE_MUTEX - if (xSemaphoreTake(m_xMutex, portMAX_DELAY)) - { -#endif - m_task_function = task_function; - xTaskCreatePinnedToCore(taskStarter, name, stack_size, this, priority, &task_handle, core); -#ifdef USE_MUTEX - xSemaphoreGive(m_xMutex); - } -#endif - return task_handle; -} - -TaskFactory TASK_FACTORY; \ No newline at end of file diff --git a/main/TaskMgr.cpp b/main/TaskMgr.cpp new file mode 100644 index 0000000..eac25c0 --- /dev/null +++ b/main/TaskMgr.cpp @@ -0,0 +1,88 @@ +/// © MiroZ 2024 + +#include "TaskMgr.h" + +#include +#include + +static const char * TAG = "taskmgr"; + +TaskMgr::TaskMgr() +{ + m_xMutex = xSemaphoreCreateMutex(); + assert(m_xMutex); +#ifdef TASKMGR_TASK_NAME + createTask(std::bind(&TaskMgr::task, this), TASKMGR_TASK_NAME, TASKMGR_TASK_STACK_SIZE, TASKMGR_TASK_PRIORITY, TASKMGR_TASK_CORE); +#endif +} + +TaskMgr::~TaskMgr() +{ + if(m_xMutex != NULL) + vSemaphoreDelete(m_xMutex); +} + +#ifdef TASKMGR_TASK_NAME +void TaskMgr::task() +{ + while(true) + { + delay(TASKMGR_TASK_DELAY); + getInfo(); + } +} +#endif + +TaskMgr & TaskMgr::getInstance() +{ + static TaskMgr taskmgr; + return taskmgr; +} + +TaskHandle_t TaskMgr::createTask(TASK_FUNCTION task_function, const char *name, uint32_t stack_size, UBaseType_t priority, BaseType_t core) +{ + TaskHandle_t task_handle = nullptr; + + if (xSemaphoreTake(m_xMutex, portMAX_DELAY)) + { + m_task_function = task_function; + xTaskCreatePinnedToCore(taskStarter, name, stack_size, this, priority, &task_handle, core); + m_tasks.push_back(task_handle); + + xSemaphoreGive(m_xMutex); + } + + return task_handle; +} + +void TaskMgr::deleteTask(TaskHandle_t task) +{ + if(task == nullptr) + return; + + if (xSemaphoreTake(m_xMutex, portMAX_DELAY)) + { + vTaskDelete(task); + m_tasks.remove(task); + + xSemaphoreGive(m_xMutex); + } +} + +void TaskMgr::getInfo() +{ + if (xSemaphoreTake(m_xMutex, portMAX_DELAY)) + { + ESP_LOGW(TAG, "Current running tasks:"); + for(auto i : m_tasks) + { + const char * name = pcTaskGetName(i); + uint32_t hwm = (uint32_t)uxTaskGetStackHighWaterMark(i); + if(hwm < 256) + ESP_LOGW(TAG, "%10s (%d)", name, hwm); + else + ESP_LOGI(TAG, "%10s (%d)", name, hwm); + } + xSemaphoreGive(m_xMutex); + } +} \ No newline at end of file diff --git a/main/TaskFactory.h b/main/TaskMgr.h similarity index 58% rename from main/TaskFactory.h rename to main/TaskMgr.h index 1439ef3..1d3d32b 100644 --- a/main/TaskFactory.h +++ b/main/TaskMgr.h @@ -3,39 +3,43 @@ #ifndef __TASK_FACTORY_H__ #define __TASK_FACTORY_H__ +#include "app_config.h" #include #include #include #include #include #include +#include typedef std::function TASK_FUNCTION; -// no need for mutex here -#define _USE_MUTEX - -class TaskFactory +class TaskMgr { protected: -#ifdef USE_MUTEX SemaphoreHandle_t m_xMutex = NULL; -#endif TASK_FUNCTION m_task_function = nullptr; + std::list m_tasks; protected: - static void taskStarter(void * ptr) { (static_cast(ptr)->run()); } + static void taskStarter(void * ptr) { (static_cast(ptr)->run()); } void run(){ m_task_function(); } -public: - TaskHandle_t createTask(TASK_FUNCTION task_function, const char *name, uint32_t stack_size, UBaseType_t priority, BaseType_t core); - static uint32_t getStackHighWaterMark(TaskHandle_t task = NULL){ return (uint32_t)uxTaskGetStackHighWaterMark(task); } - static void deleteTask(TaskHandle_t task = NULL){ vTaskDelete(task); } +#ifdef TASKMGR_TASK_NAME + void task(); +#endif - TaskFactory(); - ~TaskFactory(); +private: + TaskMgr(); + +public: + static TaskMgr & getInstance(); + + TaskHandle_t createTask(TASK_FUNCTION task_function, const char *name, uint32_t stack_size, UBaseType_t priority, BaseType_t core); + void deleteTask(TaskHandle_t task = NULL); + void getInfo(); + + ~TaskMgr(); }; -extern TaskFactory TASK_FACTORY; - #endif /* __TASK_FACTORY_H__ */ \ No newline at end of file diff --git a/main/Wifi.cpp b/main/Wifi.cpp index 1f37877..432d52c 100644 --- a/main/Wifi.cpp +++ b/main/Wifi.cpp @@ -6,6 +6,7 @@ #include #include #include "utilities.h" +#include "app_config.h" static const char *TAG = "Wifi"; #define IGNORE_SSID_MINS 20 @@ -101,7 +102,7 @@ int Wifi::start() if(m_task == nullptr) { ESP_LOGW(TAG, "Starting wifi"); - m_task = TASK_FACTORY.createTask(std::bind(&Wifi::task, this), "wifi task", 4096, 5, 0); + m_task = TaskMgr::getInstance().createTask(std::bind(&Wifi::task, this), WIFI_TASK_NAME, WIFI_TASK_STACK_SIZE, WIFI_TASK_PRIORITY, WIFI_TASK_CORE); } return 0; } diff --git a/main/Wifi.h b/main/Wifi.h index cb1f3e5..b9f04ec 100644 --- a/main/Wifi.h +++ b/main/Wifi.h @@ -6,7 +6,7 @@ #include #include #include "Settings.h" -#include "TaskFactory.h" +#include "TaskMgr.h" #define _USE_TIMER diff --git a/main/app_config.h b/main/app_config.h new file mode 100644 index 0000000..31ec673 --- /dev/null +++ b/main/app_config.h @@ -0,0 +1,31 @@ +/// © MiroZ 2024 + +#ifndef __APP_CONFIG_H__ +#define __APP_CONFIG_H__ + +// LED task +#define LED_TASK_NAME "led" +#define LED_TASK_STACK_SIZE 2096 +#define LED_TASK_PRIORITY 5 +#define LED_TASK_CORE 0 + +// WIFI task +#define WIFI_TASK_NAME "wifi" +#define WIFI_TASK_STACK_SIZE 4096 +#define WIFI_TASK_PRIORITY 5 +#define WIFI_TASK_CORE 0 + +// MQTT task +#define MQTT_TASK_NAME "mqtt" +#define MQTT_TASK_STACK_SIZE 6144 +#define MQTT_TASK_PRIORITY 5 +#define MQTT_TASK_CORE 0 + +// task mgr task +#define TASKMGR_TASK_NAME "task mgr" +#define TASKMGR_TASK_STACK_SIZE 2048 +#define TASKMGR_TASK_PRIORITY 5 +#define TASKMGR_TASK_CORE 0 +#define TASKMGR_TASK_DELAY 60000 + +#endif \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp index 51e0fe6..e6f2a46 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "TaskFactory.h" +#include "TaskMgr.h" #include "esp_task_wdt.h" #include "App.h" @@ -25,8 +25,7 @@ extern "C" void app_main(void) App * app = new App(); app->init(); app->start(); - ESP_LOGI(TAG, "stack high water mark %d", TaskFactory::getStackHighWaterMark()); // we're no longer needed - TaskFactory::deleteTask(); + vTaskDelete(NULL); } \ No newline at end of file diff --git a/ota.sh b/ota.sh index f35955a..6c3a1ac 100755 --- a/ota.sh +++ b/ota.sh @@ -12,6 +12,5 @@ if (($ret_val != 0)); then fi cp build/wellhub.bin /var/www/esp_ota - -idf.py -p /dev/ttyUSB1 monitor -B 450000 +lftp -c "open -u mirozmrzli@bigfoot-inc.com,3445trGGDSa9 ftp.bigfoot-inc.com; put build/wellhub.bin" diff --git a/upload.sh b/upload.sh new file mode 100755 index 0000000..8415242 --- /dev/null +++ b/upload.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +#!/bin/bash +lftp -u mirozmrzli@bigfoot-inc.com,3445trGGDSa9 mirozmrzli@ftp.bigfoot-inc.com +put build/wellhub.bin +bye +