/*
* ТЕСТ АЛГОРИТМА ПЕРЕСЧЕТА УГЛА ПАРУСА
* Простая программа для проверки расчетов по таблице Excel
* P = F - (k × Fn + b)
*
* Выводит результаты в Serial Monitor
* Можно подставить свои значения для проверки
*/
#include <Arduino.h>
// Структура для сектора пересчета
struct Sector {
int minAngle; // Минимальный угол сектора
int maxAngle; // Максимальный угол сектора
float k; // Коэффициент k
float b; // Коэффициент b
int side; // 1 - правый борт, -1 - левый борт
};
// Таблица коэффициентов из Excel
const Sector sectors[] = {
// Правый борт (0-180)
{20, 105, 0.0588272, 11.8235, 1},
{105, 120, 1.80013, -171.00246, 1},
{120, 135, 0.333357, 4.99948, 1},
{135, 165, 0.666715, -40.00116, 1},
{165, 180, 1.33343, -150.00280, 1},
// Левый борт (180-360) - с нормализацией
{180, 195, 1.33343, -150.00305, -1},
{195, 225, 0.666718, -20.00165, -1},
{225, 240, 0.33336, 54.99905, -1},
{240, 255, 1.80014, -297.00543, -1},
{255, 340, 0.0588283, 146.99979, -1}
};
const int numSectors = sizeof(sectors) / sizeof(sectors[0]);
// Нормализация угла флюгера для левого борта
int normalizeWindAngle(int windAngle, int side) {
if (side == 1) {
// Правый борт - оставляем как есть
return windAngle;
} else {
// Левый борт - нормализуем в диапазон 0-180
return 360 - windAngle;
}
}
// Поиск сектора для заданного угла ветра
const Sector* findSector(int windAngle) {
for (int i = 0; i < numSectors; i++) {
if (windAngle >= sectors[i].minAngle && windAngle <= sectors[i].maxAngle) {
return §ors[i];
}
}
return NULL;
}
// Расчет угла паруса по формуле P = F - (k × Fn + b)
int calculateSailAngle(int windAngle, const Sector* sector) {
if (sector == NULL) {
return 90; // Безопасное положение
}
int normalizedAngle = normalizeWindAngle(windAngle, sector->side);
// Расчет поправки
float correction = (sector->k * normalizedAngle) + sector->b;
// Расчет угла паруса
float sailAngle = windAngle - correction;
// Нормализация результата
while (sailAngle < 0) sailAngle += 360;
while (sailAngle >= 360) sailAngle -= 360;
// Преобразование к диапазону 0-180 для паруса
if (sailAngle > 180) {
sailAngle = 360 - sailAngle;
}
return (int)round(sailAngle);
}
// Тест одного конкретного угла ветра
void testSingleAngle(int windAngle) {
const Sector* sector = findSector(windAngle);
if (sector == NULL) {
Serial.print("Ветер: ");
Serial.print(windAngle);
Serial.println("° - вне диапазона секторов!");
return;
}
int normalized = normalizeWindAngle(windAngle, sector->side);
float correction = (sector->k * normalized) + sector->b;
int sailAngle = calculateSailAngle(windAngle, sector);
Serial.print("Ветер: ");
Serial.print(windAngle);
Serial.print("° | Нормализованный: ");
Serial.print(normalized);
Serial.print("° | k=");
Serial.print(sector->k, 6);
Serial.print(" b=");
Serial.print(sector->b, 6);
Serial.print(" | Поправка: ");
Serial.print(correction, 2);
Serial.print(" | Парус: ");
Serial.print(sailAngle);
Serial.println("°");
}
// Тест всех примеров из Excel таблицы
void testExcelExamples() {
Serial.println("\n=== ТЕСТ ПРИМЕРОВ ИЗ EXCEL ===");
Serial.println("Ветер -> Парус (должно совпадать с Excel)");
// Примеры из правого борта
int testAnglesRight[] = {50, 120, 120, 165, 180};
int expectedRight[] = {35, 75, 75, 95, 90};
Serial.println("\nПравый борт:");
for (int i = 0; i < 5; i++) {
int sail = calculateSailAngle(testAnglesRight[i], findSector(testAnglesRight[i]));
Serial.print(testAnglesRight[i]);
Serial.print("° -> ");
Serial.print(sail);
Serial.print("° (ожидается ");
Serial.print(expectedRight[i]);
Serial.print("°) ");
Serial.println(sail == expectedRight[i] ? "✓" : "✗");
}
// Примеры из левого борта (с нормализацией)
int testAnglesLeft[] = {179, 135, 130, 115, 20};
int expectedLeft[] = {90, 95, 98, 101, 173};
Serial.println("\nЛевый борт (ввод 0-360):");
for (int i = 0; i < 5; i++) {
int sail = calculateSailAngle(testAnglesLeft[i], findSector(testAnglesLeft[i]));
Serial.print(testAnglesLeft[i]);
Serial.print("° -> ");
Serial.print(sail);
Serial.print("° (ожидается ");
Serial.print(expectedLeft[i]);
Serial.print("°) ");
Serial.println(sail == expectedLeft[i] ? "✓" : "✗");
}
}
// Тест всех граничных значений секторов
void testSectorBoundaries() {
Serial.println("\n=== ТЕСТ ГРАНИЦ СЕКТОРОВ ===");
for (int i = 0; i < numSectors; i++) {
Serial.print("\nСектор ");
Serial.print(sectors[i].minAngle);
Serial.print("-");
Serial.print(sectors[i].maxAngle);
Serial.println("°:");
// Тестируем начало, середину и конец сектора
int testPoints[3];
testPoints[0] = sectors[i].minAngle;
testPoints[1] = (sectors[i].minAngle + sectors[i].maxAngle) / 2;
testPoints[2] = sectors[i].maxAngle;
for (int j = 0; j < 3; j++) {
int wind = testPoints[j];
if (wind >= 0 && wind <= 360) {
testSingleAngle(wind);
}
}
}
}
// Тест непрерывного изменения угла ветра
void testContinuousSweep() {
Serial.println("\n=== ТЕСТ НЕПРЕРЫВНОГО ИЗМЕНЕНИЯ ВЕТРА ===");
Serial.println("От 0° до 360° с шагом 15°");
for (int wind = 0; wind <= 360; wind += 15) {
const Sector* sector = findSector(wind);
if (sector != NULL) {
int sail = calculateSailAngle(wind, sector);
Serial.print("Ветер: ");
Serial.print(wind);
Serial.print("° -> Парус: ");
Serial.print(sail);
Serial.println("°");
}
}
}
// Простой интерактивный тест
void interactiveTest() {
Serial.println("\n=== ИНТЕРАКТИВНЫЙ ТЕСТ ===");
Serial.println("Введите угол ветра (0-360) или команду:");
Serial.println(" test - тест примеров из Excel");
Serial.println(" sweep - непрерывный тест 0-360°");
Serial.println(" bounds - тест границ секторов");
Serial.println(" all - все тесты");
Serial.println(" help - эта справка");
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n==========================================");
Serial.println("ТЕСТ АЛГОРИТМА РАСЧЕТА УГЛА ПАРУСА");
Serial.println("Формула: P = F - (k × Fn + b)");
Serial.println("==========================================");
interactiveTest();
}
void loop() {
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.length() == 0) return;
// Проверяем, это число или команда
if (isDigit(input.charAt(0)) ||
(input.charAt(0) == '-' && isDigit(input.charAt(1)))) {
int windAngle = input.toInt();
if (windAngle >= 0 && windAngle <= 360) {
testSingleAngle(windAngle);
} else {
Serial.println("Угол должен быть от 0 до 360 градусов!");
}
} else {
// Обработка команд
if (input == "test") {
testExcelExamples();
} else if (input == "sweep") {
testContinuousSweep();
} else if (input == "bounds") {
testSectorBoundaries();
} else if (input == "all") {
testExcelExamples();
testSectorBoundaries();
testContinuousSweep();
} else if (input == "help") {
interactiveTest();
} else {
Serial.println("Неизвестная команда. Введите 'help' для справки.");
}
}
Serial.println("\nВведите следующий угол или команду:");
}
}