Skip to content

Commit

Permalink
Simple Telnet server (#106)
Browse files Browse the repository at this point in the history
keira: add telnet server with basic commands
sdk: add "compact" arg to FileUtils::getHumanFriendlySize
  • Loading branch information
and3rson committed Apr 17, 2024
1 parent 9a8b127 commit c034f66
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 18 deletions.
1 change: 1 addition & 0 deletions firmware/keira/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ lib_deps =
https://github.com/moononournation/arduino-nofrendo.git
bitbank2/PNGenc @ ^1.1.1
bblanchon/ArduinoJson @ ^7.0.4
lennarthennigs/ESP Telnet @ ^2.2.1
https://github.com/earlephilhower/ESP8266Audio.git
lib_extra_dirs = ./lib
extra_scripts = targets.py
2 changes: 1 addition & 1 deletion firmware/keira/src/apps/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ void LauncherApp::sdBrowserMenu(FS* fSysDriver, const String& path) {
uint16_t color = entries[i].type == lilka::EntryType::ENT_DIRECTORY ? lilka::colors::Arylide_yellow
: get_file_color(filename);
if (entries[i].type != lilka::EntryType::ENT_DIRECTORY)
menu.addItem(filename, icon, color, lilka::fileutils.getHumanFriendlySize(entries[i].size));
menu.addItem(filename, icon, color, lilka::fileutils.getHumanFriendlySize(entries[i].size, true));
else menu.addItem(filename, icon, color);
}
menu.addItem("<< Назад", 0, 0);
Expand Down
2 changes: 2 additions & 0 deletions firmware/keira/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "services/clock.h"
#include "services/network.h"
#include "services/screenshot.h"
#include "services/telnet.h"
#include "apps/statusbar.h"
#include "apps/launcher.h"

Expand All @@ -20,6 +21,7 @@ void setup() {
serviceManager->addService(new NetworkService());
serviceManager->addService(new ClockService());
serviceManager->addService(new ScreenshotService());
serviceManager->addService(new TelnetService());
appManager->setPanel(new StatusBarApp());
appManager->runApp(new LauncherApp());
}
Expand Down
4 changes: 2 additions & 2 deletions firmware/keira/src/service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ Service::~Service() {
}

void Service::start() {
Serial.println("Starting service " + String(name));
lilka::serial_log("Starting service %s", name);
xTaskCreate(_run, name, stackSize, this, 1, &taskHandle);
}

void Service::_run(void* arg) {
Service* service = static_cast<Service*>(arg);
service->run();
Serial.println("Service " + String(service->name) + " died");
lilka::serial_err("Service %s died", service->name);
}

void Service::setStackSize(uint32_t stackSize) {
Expand Down
5 changes: 5 additions & 0 deletions firmware/keira/src/services/network.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

#include "network.h"

// Macro magic used to convert decimal constant to char[] constant
#define STRX(x) #x
#define STR(x) STRX(x)
#define LILKA_HOSTNAME_PREFIX "LilkaV"

// EEPROM preferences used:
// - network.last_ssid - last connected SSID
// - network.[SSID_hash]_pw - password of known network with a given SSID
Expand Down
5 changes: 1 addition & 4 deletions firmware/keira/src/services/network.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

#include <WiFi.h>
#include "service.h"
// Macro magic used to convert decimal constant to char[] constant
#define STRX(x) #x
#define STR(x) STRX(x)
#define LILKA_HOSTNAME_PREFIX "LilkaV"

enum NetworkState {
NETWORK_STATE_OFFLINE,
NETWORK_STATE_CONNECTING,
Expand Down
181 changes: 181 additions & 0 deletions firmware/keira/src/services/telnet.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#include <EscapeCodes.h>

#include "telnet.h"
#include "servicemanager.h"
#include "network.h"

#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)

ESPTelnet telnet;
EscapeCodes ansi;

TelnetService::TelnetService() : Service("telnet") {
}

TelnetService::~TelnetService() {
}

typedef struct {
const char* command;
void (*function)(String);
} Command;

Command commands[] = {
{
"help",
[](String args) {
telnet.println("Доступні команди:");
telnet.println(" help - показати цей список команд");
telnet.println(" reboot - перезавантажити пристрій");
telnet.println(" uptime - показати час роботи пристрою");
telnet.println(" free - показати стан пам'яті");
telnet.println(" ls [DIR] - показати список файлів на SD-картці");
telnet.println(" find [TEXT] - знайти файли на SD-картці, які містять TEXT в назві");
telnet.println(" exit - розірвати з'єднання");
},
},
{
"reboot",
[](String args) {
telnet.println("Система перезавантажується...");
telnet.disconnectClient();
esp_restart();
},
},
{
"uptime",
[](String args) { telnet.println("Uptime: " + String(millis() / 1000) + " seconds"); },
},
{
"free",
[](String args) {
telnet.println("Heap: " + String(ESP.getFreeHeap()) + " / " + String(ESP.getHeapSize()) + " bytes free");
telnet.println("PSRAM: " + String(ESP.getFreePsram()) + " / " + String(ESP.getPsramSize()) + " bytes free");
},
},
{
"ls",
[](String args) {
File dir = SD.open(args.isEmpty() ? "/" : ("/" + args));
int count = 0;
if (!dir) {
telnet.println("Не вдалося відкрити директорію: " + args);
return;
}
while (File file = dir.openNextFile()) {
count++;
file.isDirectory() ? telnet.print("DIR ") : telnet.print("FILE");
telnet.print(" ");
telnet.printf(
"%8s ", !file.isDirectory() ? lilka::fileutils.getHumanFriendlySize(file.size()).c_str() : "-"
);
telnet.println(file.name());
file.close();
}
dir.close();
telnet.println("Знайдено " + String(count) + " файлів");
},
},
{
"find",
[](String args) {
std::vector<String> dirs;
dirs.push_back("/");
int count = 0;
while (!dirs.empty()) {
String dir = dirs.back();
dirs.pop_back();
File d = SD.open(dir);
if (!d) {
telnet.println("Не вдалося відкрити директорію: " + dir);
continue;
}
while (File file = d.openNextFile()) {
String fullPath = (!dir.equals("/") ? dir : "") + "/" + file.name();
if (file.isDirectory()) {
dirs.push_back(fullPath);
} else if (args.isEmpty() || String(file.name()).indexOf(args) != -1) {
count++;
telnet.print("FILE ");
telnet.printf("%8s ", lilka::fileutils.getHumanFriendlySize(file.size()).c_str());
telnet.println(fullPath);
}
file.close();
}
d.close();
}
telnet.println("Знайдено " + String(count) + " файлів");
},
},
{
"exit",
[](String args) { telnet.disconnectClient(); },
},
};

void TelnetService::run() {
NetworkService* network = ServiceManager::getInstance()->getService<NetworkService>("network");

telnet.onConnectionAttempt([](String ip) {
lilka::serial_log("TelnetService: %s attempting to connect", ip.c_str());
});
telnet.onConnect([](String ip) {
lilka::serial_log("TelnetService: %s connected", ip.c_str());
telnet.print(ansi.cls());
telnet.print(ansi.home());
telnet.println(ansi.setBG(ANSI_BLUE) + " " + ansi.reset() + " Keira OS @ Lilka v" STR(LILKA_VERSION));
telnet.println(ansi.setBG(ANSI_YELLOW) + " " + ansi.reset() + " Слава Україні! ");
telnet.println();
telnet.print("> ");
});
telnet.onReconnect([](String ip) { lilka::serial_log("TelnetService: %s reconnected", ip.c_str()); });
telnet.onDisconnect([](String ip) { lilka::serial_log("TelnetService: %s disconnected", ip.c_str()); });
telnet.onInputReceived([](String input) {
lilka::serial_log("TelnetService: received text: %s", input.c_str());
input.trim();
if (!input.isEmpty()) {
int spaceIndex = input.indexOf(' ');
if (spaceIndex == -1) {
spaceIndex = input.length();
}
String command = input.substring(0, spaceIndex);
command.toLowerCase();
command.trim();
String args = input.substring(spaceIndex + 1);
args.trim();
Command* cmd = &commands[0];
for (int i = 0; i < sizeof(commands) / sizeof(Command); i++) {
if (command.equals(commands[i].command)) {
cmd = &commands[i];
break;
}
}
cmd->function(args);
}
telnet.print("> ");
});

bool wasOnline = false;
while (1) {
if (!network) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
bool isOnline = network->getNetworkState() == NetworkState::NETWORK_STATE_ONLINE;
if (!wasOnline && isOnline) {
wasOnline = true;
// Start telnet server
telnet.begin(23);
} else if (wasOnline && !isOnline) {
wasOnline = false;
// Stop telnet server
telnet.stop();
}
if (isOnline) {
telnet.loop();
} else {
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
}
12 changes: 12 additions & 0 deletions firmware/keira/src/services/telnet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <ESPTelnet.h>

#include "service.h"

class TelnetService : public Service {
public:
TelnetService();
~TelnetService();

private:
void run() override;
};
16 changes: 9 additions & 7 deletions sdk/lib/lilka/src/lilka/fileutils.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "fileutils.h"
#include "serial.h"
#include "spi.h"
#include "config.h"

namespace lilka {
FileUtils::FileUtils() : sdMutex(xSemaphoreCreateMutex()) {
sdfs = &SD;
Expand Down Expand Up @@ -205,12 +207,12 @@ const String FileUtils::getSDRoot() {
const String FileUtils::getSPIFFSRoot() {
return LILKA_SPIFFS_ROOT;
}
const String FileUtils::getHumanFriendlySize(const size_t size) {
const String FileUtils::getHumanFriendlySize(const size_t size, bool compact) {
// Max length of file size

const char* suffixes[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
const int numSuffixes = sizeof(suffixes) / sizeof(suffixes[0]);
if (size == 0) return "0B";
if (size == 0) return String("0") + (compact ? "" : " ") + suffixes[0];

int exp = 0;
double dsize = (double)(size);
Expand All @@ -221,13 +223,13 @@ const String FileUtils::getHumanFriendlySize(const size_t size) {
}

char buffer[50];
snprintf(buffer, sizeof(buffer), "%.0f%s", dsize, suffixes[exp]);
String hFileSize(buffer);
while (hFileSize.length() != H_FILE_SIZE) {
hFileSize = hFileSize + " ";
if (compact) {
snprintf(buffer, sizeof(buffer), "%.0f%s", dsize, suffixes[exp]);
} else {
snprintf(buffer, sizeof(buffer), "%.0f %2s", dsize, suffixes[exp]);
}

return hFileSize;
return String(buffer);
}

bool FileUtils::createSDPartTable() {
Expand Down
6 changes: 2 additions & 4 deletions sdk/lib/lilka/src/lilka/fileutils.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
#pragma once
#include <ff.h>
#include "config.h"
#include <SPIFFS.h>
#include <SD.h>
#include "config.h"
#include <dirent.h>
#include <sd_diskio.h>

#define LILKA_SD_ROOT "/sd"
#define LILKA_SPIFFS_ROOT "/spiffs"
#define LILKA_SLASH "/"
#define H_FILE_SIZE 6
#define LILKA_SD_FREQUENCY 20000000

namespace lilka {
Expand Down Expand Up @@ -137,12 +134,13 @@ class FileUtils {
/// Повернути розмір в читабельному форматі (наприклад, 101 MB).
///
/// @param size Розмір (в байтах)
/// @param compact Чи виводити розмір компактно (наприклад, 1.23MB замість 1.23 MB)
/// @return Рядок, що містить читабельний розмір з суфіксами одиниць виміру
///
/// @code
/// fileutils.getHumanFriendlySize(1234567); // Поверне "1.23 MB"
/// @endcode
const String getHumanFriendlySize(const size_t size);
const String getHumanFriendlySize(const size_t size, bool compact = false);

private:
SemaphoreHandle_t sdMutex = NULL;
Expand Down

0 comments on commit c034f66

Please sign in to comment.