diff --git a/3d_models/.gitignore b/3d_models/.gitignore new file mode 100644 index 0000000..e91a70c --- /dev/null +++ b/3d_models/.gitignore @@ -0,0 +1,2 @@ +*.FCBak +/export \ No newline at end of file diff --git a/3d_models/conveer.FCStd b/3d_models/conveer.FCStd new file mode 100644 index 0000000..4007dc1 Binary files /dev/null and b/3d_models/conveer.FCStd differ diff --git a/arduino_code/Test/.gitignore b/arduino_code/Test/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/arduino_code/Test/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/arduino_code/Test/.vscode/extensions.json b/arduino_code/Test/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/arduino_code/Test/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/arduino_code/Test/README.md b/arduino_code/Test/README.md new file mode 100644 index 0000000..513c652 --- /dev/null +++ b/arduino_code/Test/README.md @@ -0,0 +1,138 @@ +Вот обновленная и расширенная документация по MQTT-интерфейсу, включающая новую функциональность работы с датчиками расстояния **VL53L0X**, а также оптимизации, появившиеся в коде. + +--- + +# 📡 Документация по MQTT интерфейсу (ESP32 + TMC2209 + Servo + VL53L0X) + +## 📌 Общая информация +- **Архитектура**: ESP32 (FreeRTOS задача `mqttTask`). +- **Период опроса телеметрии**: 500 мс. +- **Оптимизация трафика**: + 1. Публикация данных происходит **только при изменении значения** (строгое кэширование). + 2. Используется статический буфер (`intToString`/`uintToString`) вместо динамического класса `String` для экономии памяти и предотвращения фрагментации кучи. +- **Префиксы**: + - `.../control/...` — топики для **отправки команд** устройству (подписка). + - `.../feedback/...` — топики для **получения статуса/телеметрии** от устройства (публикация). + +--- + +## ⚙️ 1. Управление шаговым двигателем (Motor Control) + +| Топик | Тип данных | Описание | Пример Payload | +| :--- | :---: | :--- | :--- | +| `motor/control/rpm` | Integer | Целевая скорость в об/мин (RPM). Отрицательные значения включают реверс. | `-150`, `0`, `300` | +| `motor/control/driver` | String | Аппаратное вкл/выкл драйвера (пин `EN_PIN`). `on` = LOW (вкл), иначе = HIGH (выкл). | `on`, `off` | +| `motor/control/totalsteps/reset`| Any | Сброс счетчика шагов в ноль. Устройство сразу опубликует `"0"` в feedback. | `1`, `reset` | + +### Телеметрия двигателя (Motor Feedback) +*(Публикуется только при изменении)* +- `motor/feedback/rpm` (Integer): Текущая скорость. +- `motor/feedback/totalsteps` (Integer): Общее количество шагов. +- `motor/feedback/is_run` (String: `true`/`false`): Двигатель движется. +- `motor/feedback/driver/status` (String: `on`/`off`): Общий статус драйвера. +- `motor/feedback/tmc/status` (String: `on`/`off`): Статус программного включения TMC. + +#### Детальная телеметрия TMC2209 (`motor/feedback/tmc/...`) +- `current_percent` (Integer): Текущий % рабочего тока. +- `microsteps` (Integer): Текущий режим микрошага. +- `sg_result` (Integer): Текущее значение StallGuard (нагрузка). +- `interstep_duration` (Integer): Длительность между шагами. +- `status/over_temp` (String: `true`/`false`): Перегрев. +- `status/short_to_ground` (String: `true`/`false`): КЗ на землю. +- `status/open_load` (String: `true`/`false`): Обрыв нагрузки. +- `status/stealth_chop_active` (String: `true`/`false`): Активен ли StealthChop. +- `status/standstill` (String: `true`/`false`): Двигатель в покое. +- `status/current_scaling` (Integer): Внутренний масштабный коэффициент тока. + +--- + +## 🦾 2. Управление сервоприводами (Servo Control) + +Поддержка нескольких каналов (`{channel}` от `0` до `MAX_SERVOS - 1`). + +### Команды +| Топик (пример для канала 0) | Тип данных | Описание | Пример Payload | +| :--- | :---: | :--- | :--- | +| `servo/control/0/angle` | Integer (0-180) | Установить угол поворота. | `90` | +| `servo/control/0/enable` | String | Включить (`on`, `1`, `true`) или выключить. | `on` | + +### Обратная связь +- `servo/0/feedback/status` (String: `on`/`off`): Статус питания сервопривода. +- `servo/0/feedback/angle` (Integer): Текущий установленный угол. + +--- + +## 📏 3. Управление датчиками VL53L0X (Sensor Control) **(НОВОЕ)** + +Поддержка до 8 каналов (`VL53L0X_MAX_CHANNELS = 8`). Топики используют параметр `{channel}` (0–7). + +| Топик | Тип данных | Описание | Пример Payload | +| :--- | :---: | :--- | :--- | +| `sensor/control/mode` | Integer | Установка глобального режима измерения (0 до `MODE_COUNT - 1`). | `0`, `1` | +| `sensor/control/mode_name` | String | *Заглушка/Логирование.* Принимает имя режима для отладки. | `LongRange` | +| `sensor/control/calibrate/start/{ch}`| Integer | Начало калибровки: указать близкое расстояние в мм. | `50` | +| `sensor/control/calibrate/finish/{ch}`| Integer | Завершение калибровки: указать дальнее расстояние в мм. | `500` | +| `sensor/control/clear_cal/{ch}` | Any | Сбросить калибровку для указанного канала. | `1` | +| `sensor/control/enable/{ch}` | String | Включить (`on`, `1`, `true`) или выключить конкретный канал. | `on` | +| `sensor/control/publish_all` | Any | **Принудительный сброс кэша.** Заставляет устройство немедленно опубликовать текущие значения всех датчиков, даже если они не изменились. | `1` | + +--- + +## 📊 4. Телеметрия датчиков VL53L0X (Sensor Feedback) **(НОВОЕ)** + +### Глобальная информация о режиме +Публикуется только при смене режима измерения: +- `sensor/feedback/mode` (String): Человекочитаемое имя текущего режима (например, "Default", "LongRange"). +- `sensor/feedback/mode_id` (Integer): Числовой ID текущего режима. +- `sensor/feedback/max_range` (Integer): Максимальная дальность для текущего режима (в мм). + +### Постатусная информация по каналам (`{channel}` = 0..7) +*(Публикуется только при изменении состояния или значения)* + +| Топик (пример для канала 0) | Тип данных | Описание | +| :--- | :---: | :--- | +| `sensor/feedback/0/status` | String (`on`/`off`) | Включен ли логически данный канал. | +| `sensor/feedback/0/calibrated` | String (`true`/`false`)| Была ли проведена калибровка для этого канала. | +| `sensor/feedback/0/distance` | Integer или String | **Калиброванное** расстояние в мм. Если значение `65535` (ошибка/вне диапазона), публикуется строка `"out_of_range"`. | +| `sensor/feedback/0/raw` | Integer | **Сырое** (некалиброванное) значение расстояния в мм. | + +*Примечание: Топики `distance` и `raw` публикуются только если канал активен (`vl53l0xIsChannelActive`).* + +--- + +## 💡 Важные особенности реализации (Обновлено) + +1. **Безопасная работа со строками**: В новом коде добавлены функции `intToString` и `uintToString`, использующие статические буферы. Это полностью устраняет риск фрагментации памяти (heap fragmentation) при частой публикации телеметрии, который был присущ использованию класса `String`. +2. **Принудительная публикация**: Топик `sensor/control/publish_all` сбрасывает кэш значений `distance` и `raw` на `65535`. При следующем цикле (через 500 мс) система "увидит" изменение и гарантированно отправит актуальные данные. Это полезно при подключении нового клиента, которому нужно получить текущее состояние без перезагрузки устройства. +3. **Обработка ошибок дальности**: Если датчик возвращает `65535` (стандартный код ошибки "вне диапазона" или сбоя измерения для VL53L0X), в топик `distance` публикуется понятная строка `"out_of_range"`, а не число, что упрощает обработку на стороне клиента. +4. **Изоляция каналов**: Цикл телеметрии VL53L0X предварительно проверяет `vl53l0xIsChannelPresent(ch)`, поэтому несуществующие или отключенные на аппаратном уровне каналы не создают лишнего трафика. + +--- + +### 🛠️ Примеры использования (CLI / mosquitto) + +```bash +# --- Двигатель --- +mosquitto_pub -t "motor/control/rpm" -m "100" +mosquitto_pub -t "motor/control/tmc/stealthchop" -m "on" + +# --- Сервопривод (канал 0) --- +mosquitto_pub -t "servo/control/0/enable" -m "on" +mosquitto_pub -t "servo/control/0/angle" -m "90" + +# --- Датчики VL53L0X --- +# Включить канал 1 +mosquitto_pub -t "sensor/control/enable/1" -m "on" + +# Начать калибровку канала 1 (близкая точка 50 мм) +mosquitto_pub -t "sensor/control/calibrate/start/1" -m "50" +# ... передвинуть объект ... +# Завершить калибровку канала 1 (дальняя точка 400 мм) +mosquitto_pub -t "sensor/control/calibrate/finish/1" -m "400" + +# Принудительно запросить публикацию всех текущих показаний датчиков +mosquitto_pub -t "sensor/control/publish_all" -m "1" + +# Подписаться на все события датчиков для отладки +mosquitto_sub -t "sensor/feedback/#" -v +``` diff --git a/arduino_code/Test/include/README b/arduino_code/Test/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/arduino_code/Test/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/arduino_code/Test/lib/README b/arduino_code/Test/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/arduino_code/Test/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/arduino_code/Test/platformio.ini b/arduino_code/Test/platformio.ini new file mode 100644 index 0000000..d23aaa0 --- /dev/null +++ b/arduino_code/Test/platformio.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:upesy_wroom] +platform = espressif32 +board = upesy_wroom +framework = arduino +monitor_speed = 115200 +upload_speed = 921600 +upload_port = /dev/ttyUSB0 +lib_deps = + knolleary/PubSubClient@^2.8 + janelia-arduino/TMC2209@^9.4.0 + madhephaestus/ESP32Servo@^3.2.1 + adafruit/Adafruit PWM Servo Driver Library@^3.0.3 + pololu/VL53L0X@^1.3.1 diff --git a/arduino_code/Test/src/config.cpp b/arduino_code/Test/src/config.cpp new file mode 100644 index 0000000..877eb3f --- /dev/null +++ b/arduino_code/Test/src/config.cpp @@ -0,0 +1,38 @@ +#include "config.h" + +const char* WIFI_SSID = "TP-Link_3E5C"; +const char* WIFI_PASS = "12697571"; +const char* MQTT_SERVER = "192.168.0.200"; +const int MQTT_PORT = 1883; +const char* MQTT_USER = "test"; +const char* MQTT_PASS = "1234"; +const char* MQTT_CLIENT_ID = "ESP32_Stepper"; + +// UART2: RX=16, TX=17 +HardwareSerial& TMC_SERIAL = Serial2; +const uint32_t TMC_BAUD_RATE = 115200; +const uint8_t TMC_SERIAL_ADDRESS = 0; // Если MS1 и MS2 на GND + +const int16_t TMC_RX_PIN = 16; +const int16_t TMC_TX_PIN = 17; +const int EN_PIN = 18; + +const int STEPS_PER_REVOLUTION = 200; +const unsigned long RAMP_DURATION_MS = 2000; + +// Ток задается в процентах от максимума (зависит от R_sense). +// Для R_sense=0.11 Ом, 100% ~ 1.77А RMS. Для R_sense=0.15 Ом, 100% ~ 1.2А RMS. +const uint8_t TMC_RUN_CURRENT_PERCENT = 50; // 50% тока при движении +const uint8_t TMC_HOLD_CURRENT_PERCENT = 20; // 20% тока в простое +const uint8_t TMC_STALL_GUARD_THRESH = 10; +const uint16_t TMC_MICROSTEPS = 1; + +// I2C Настройка +const int16_t I2C_SDA_PIN = 21; +const int16_t I2C_SCL_PIN = 22; + + +// ========== SERVO CONFIGURATION ========== +#define SERVO_DEFAULT_CHANNEL 0 +#define SERVO_MIN_ANGLE 0 +#define SERVO_MAX_ANGLE 180 \ No newline at end of file diff --git a/arduino_code/Test/src/config.h b/arduino_code/Test/src/config.h new file mode 100644 index 0000000..c4e03a0 --- /dev/null +++ b/arduino_code/Test/src/config.h @@ -0,0 +1,38 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include + +// WiFi & MQTT +extern const char* WIFI_SSID; +extern const char* WIFI_PASS; +extern const char* MQTT_SERVER; +extern const int MQTT_PORT; +extern const char* MQTT_USER; +extern const char* MQTT_PASS; +extern const char* MQTT_CLIENT_ID; + +// UART for TMC2209 +extern HardwareSerial& TMC_SERIAL; +extern const uint32_t TMC_BAUD_RATE; +extern const uint8_t TMC_SERIAL_ADDRESS; // Адрес драйвера (0-3) +extern const int16_t TMC_RX_PIN; +extern const int16_t TMC_TX_PIN; +extern const int EN_PIN; + +// Motor Params +extern const int STEPS_PER_REVOLUTION; // Базовые шаги мотора (обычно 200) +extern const unsigned long RAMP_DURATION_MS; + +// TMC2209 Defaults (Токи в процентах 0-100%) +extern const uint8_t TMC_RUN_CURRENT_PERCENT; +extern const uint8_t TMC_HOLD_CURRENT_PERCENT; +extern const uint8_t TMC_STALL_GUARD_THRESH; +extern const uint16_t TMC_MICROSTEPS; + +// I2C Настройка +extern const int16_t I2C_SDA_PIN; +extern const int16_t I2C_SCL_PIN; + +#endif \ No newline at end of file diff --git a/arduino_code/Test/src/main.cpp b/arduino_code/Test/src/main.cpp new file mode 100644 index 0000000..5206e9f --- /dev/null +++ b/arduino_code/Test/src/main.cpp @@ -0,0 +1,42 @@ +#include +#include +#include "config.h" +#include "motor.h" +#include "vl53l0x_sensor.h" +#include "mqtt_handler.h" + +void setup() { + Serial.begin(115200); + + // Инициализация мотора (Core 1 context initially) + motorInit(); + + xTaskCreatePinnedToCore( + sensorTask, + "SensorTask", + 4096, + NULL, + 1, + NULL, + 0 + ); + + // Запуск задачи MQTT на Core 0 + xTaskCreatePinnedToCore( + mqttTask, + "MQTT_Task", + 20480, + NULL, + 3, + NULL, + 0 // CORE 0 + ); + + Serial.println("System Initialized. Multi-core ready."); +} + +void loop() { + // Loop выполняется на Core 1 + motorLoop(); + vTaskDelay(pdMS_TO_TICKS(5)); +} \ No newline at end of file diff --git a/arduino_code/Test/src/motor.cpp b/arduino_code/Test/src/motor.cpp new file mode 100644 index 0000000..b9d530d --- /dev/null +++ b/arduino_code/Test/src/motor.cpp @@ -0,0 +1,295 @@ +#include "motor.h" +#include "config.h" + +static TMC2209 stepper_driver; +static bool tmc_initialized = false; +static SemaphoreHandle_t tmc_uart_mutex = NULL; + +volatile unsigned long total_steps = 0; +static int target_rpm = 0; +static int current_rpm_display = 0; + +static bool is_ramping = false; +static unsigned long ramp_start_ms = 0; +static float start_speed_sps = 0; +static float end_speed_sps = 0; +static float current_speed_sps = 0; +static int32_t last_vactual = 0; +static bool velocity_sent = false; // Флаг для отправки хотя бы раз + +static uint16_t current_microsteps = TMC_MICROSTEPS; +static uint8_t current_run_percent = TMC_RUN_CURRENT_PERCENT; + +#define TMC_LOCK() xSemaphoreTake(tmc_uart_mutex, portMAX_DELAY) +#define TMC_UNLOCK() xSemaphoreGive(tmc_uart_mutex) + +const float TMC_FCLK = 12800000.0; +const float VACTUAL_FACTOR = 8388608.0 / TMC_FCLK; + +int32_t calculateVActual(float microsteps_per_second) { + return (int32_t)(microsteps_per_second * VACTUAL_FACTOR); +} + +void motorInit() { + pinMode(EN_PIN, OUTPUT); + digitalWrite(EN_PIN, LOW); + tmc_uart_mutex = xSemaphoreCreateMutex(); + + TMC_SERIAL.begin(TMC_BAUD_RATE, SERIAL_8N1, TMC_RX_PIN, TMC_TX_PIN); + delay(500); + + TMC_LOCK(); + + stepper_driver.setup(TMC_SERIAL, TMC_BAUD_RATE, + (TMC2209::SerialAddress)TMC_SERIAL_ADDRESS, + TMC_RX_PIN, TMC_TX_PIN); + + delay(200); + + // Проверка связи + if (!stepper_driver.isCommunicating()) { + Serial.println("ERROR: TMC2209 not communicating!"); + TMC_UNLOCK(); + tmc_initialized = false; + return; + } + + Serial.println("TMC2209 communicating OK"); + + // Базовая настройка + stepper_driver.setMicrostepsPerStep(TMC_MICROSTEPS); + stepper_driver.setRunCurrent(TMC_RUN_CURRENT_PERCENT); + stepper_driver.setHoldCurrent(TMC_HOLD_CURRENT_PERCENT); + stepper_driver.setHoldDelay(7); + stepper_driver.setStallGuardThreshold(TMC_STALL_GUARD_THRESH); + + stepper_driver.enableAutomaticCurrentScaling(); + stepper_driver.enableAutomaticGradientAdaptation(); + + // КРИТИЧНО: Отключаем StealthChop для работы moveAtVelocity()! + stepper_driver.disableStealthChop(); + delay(10); + + // Включаем CoolStep для энергосбережения + stepper_driver.enableCoolStep(); + + // Программное включение драйвера + stepper_driver.enable(); + delay(100); + + tmc_initialized = stepper_driver.isSetupAndCommunicating(); + + if (tmc_initialized) { + Serial.println("TMC2209 initialized successfully"); + TMC2209::Settings settings = stepper_driver.getSettings(); + Serial.printf("Run: %d%%, Hold: %d%%, Microsteps: %d, StealthChop: %s\n", + settings.irun_percent, settings.ihold_percent, + settings.microsteps_per_step, settings.stealth_chop_enabled ? "ON" : "OFF"); + } else { + Serial.println("ERROR: TMC2209 setup failed!"); + } + + TMC_UNLOCK(); + + current_microsteps = TMC_MICROSTEPS; + current_run_percent = TMC_RUN_CURRENT_PERCENT; +} + +void motorLoop() { + if (is_ramping) { + unsigned long now = millis(); + unsigned long elapsed = now - ramp_start_ms; + + if (elapsed >= RAMP_DURATION_MS) { + is_ramping = false; + current_speed_sps = end_speed_sps; + } else { + float progress = (float)elapsed / RAMP_DURATION_MS; + current_speed_sps = start_speed_sps + (end_speed_sps - start_speed_sps) * progress; + } + + int32_t vactual = calculateVActual(current_speed_sps); + + // Отправляем если: скорость изменилась ИЛИ это первая отправка в рампе + if (abs(vactual - last_vactual) >= 0 || !velocity_sent) { + TMC_LOCK(); + stepper_driver.moveAtVelocity(vactual); + TMC_UNLOCK(); + last_vactual = vactual; + velocity_sent = true; + + Serial.printf("VACTUAL: %d (SPS: %.1f, RPM: %d)\n", + vactual, current_speed_sps, current_rpm_display); + } + + unsigned long steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * current_microsteps; + current_rpm_display = ((unsigned long)abs(current_speed_sps) * 60) / steps_per_rev; + } else if (!velocity_sent && current_speed_sps == 0) { + // Если мотор стоит и скорость не отправлялась - отправляем 0 + TMC_LOCK(); + stepper_driver.moveAtVelocity(0); + TMC_UNLOCK(); + velocity_sent = true; + } + if (current_speed_sps != 0) { + total_steps += (unsigned long)(abs(current_speed_sps) * 0.005); + } + + vTaskDelay(pdMS_TO_TICKS(5)); +} + +void setTargetRPM(int rpm) { + target_rpm = rpm; + if (rpm != 0 && current_rpm_display < 5) resetSteps(); + + unsigned long steps_per_rev = (unsigned long)STEPS_PER_REVOLUTION * current_microsteps; + + start_speed_sps = current_speed_sps; + end_speed_sps = (rpm != 0) ? ((float)rpm * steps_per_rev) / 60.0f : 0; + + ramp_start_ms = millis(); + is_ramping = true; + velocity_sent = false; // Сбрасываем флаг для новой отправки + + Serial.printf("Target RPM: %d -> SPS: %.1f\n", rpm, end_speed_sps); +} + +void resetSteps() { + total_steps = 0; +} + +unsigned long getMotorSteps() { return total_steps; } +int getCurrentRPM() { return current_rpm_display; } +bool isMotorRunning() { return (abs(current_speed_sps) > 1); } + +void tmcSetCurrentPercent(uint8_t run_percent, uint8_t hold_percent) { + if (!tmc_initialized) return; + TMC_LOCK(); + stepper_driver.setAllCurrentValues(run_percent, hold_percent, 7); + TMC_UNLOCK(); + current_run_percent = run_percent; +} + +void tmcSetMicrosteps(uint16_t ms) { + if (!tmc_initialized) return; + TMC_LOCK(); + stepper_driver.setMicrostepsPerStep(ms); + TMC_UNLOCK(); + current_microsteps = ms; + if (target_rpm != 0) setTargetRPM(target_rpm); +} + +void tmcSetStallGuard(uint8_t threshold) { + if (!tmc_initialized) return; + TMC_LOCK(); + stepper_driver.setStallGuardThreshold(threshold); + TMC_UNLOCK(); +} + +void tmcSoftwareEnable(bool enable) { + if (!tmc_initialized) return; + TMC_LOCK(); + if (enable) { + stepper_driver.enable(); + // После enable нужно заново отправить скорость + velocity_sent = false; + } else { + stepper_driver.moveAtVelocity(0); + stepper_driver.disable(); + last_vactual = 0; + current_speed_sps = 0; + is_ramping = false; + } + TMC_UNLOCK(); +} + +void tmcSetStealthChop(bool enable) { + if (!tmc_initialized) return; + TMC_LOCK(); + if (enable) { + stepper_driver.enableStealthChop(); + // В StealthChop VACTUAL не работает, останавливаем мотор + stepper_driver.moveAtVelocity(0); + last_vactual = 0; + current_speed_sps = 0; + } else { + stepper_driver.disableStealthChop(); + velocity_sent = false; + } + TMC_UNLOCK(); +} + +void tmcSetCoolStep(bool enable) { + if (!tmc_initialized) return; + TMC_LOCK(); + enable ? stepper_driver.enableCoolStep() : stepper_driver.disableCoolStep(); + TMC_UNLOCK(); +} + +bool tmcIsInitialized() { return tmc_initialized; } + +bool tmcIsCommunicating() { + if (!tmc_initialized) return false; + TMC_LOCK(); + bool res = stepper_driver.isCommunicating(); + TMC_UNLOCK(); + return res; +} + +// Функция проверки состояния EN_PIN +bool checkDriverStatus() { + bool en_state = (digitalRead(EN_PIN) == LOW); + return en_state; +} + +// Функция проверки программного состояния TMC +bool checkTmcSoftwareEnable() { + TMC2209::Settings s = tmcGetSettings(); + bool tmc_state = s.software_enabled; + return tmc_state; +} + +uint16_t tmcGetMicrostepsSetting() { return current_microsteps; } +uint8_t tmcGetRunCurrentPercent() { return current_run_percent; } + +TMC2209::Status tmcGetStatus() { + TMC2209::Status s = {}; + if (!tmc_initialized) return s; + TMC_LOCK(); + s = stepper_driver.getStatus(); + TMC_UNLOCK(); + return s; +} + +TMC2209::Settings tmcGetSettings() { + TMC2209::Settings s = {}; + if (!tmc_initialized) return s; + TMC_LOCK(); + s = stepper_driver.getSettings(); + TMC_UNLOCK(); + return s; +} + +uint16_t tmcGetStallGuardResult() { + if (!tmc_initialized) return 0; + TMC_LOCK(); + uint16_t res = stepper_driver.getStallGuardResult(); + TMC_UNLOCK(); + return res; +} + +uint32_t tmcGetInterstepDuration() { + if (!tmc_initialized) return 0; + TMC_LOCK(); + uint32_t res = stepper_driver.getInterstepDuration(); + TMC_UNLOCK(); + return res; +} + +uint16_t tmcGetMicrostepCounter() { + if (!tmc_initialized) return 0; + TMC_LOCK(); + uint16_t res = stepper_driver.getMicrostepCounter(); + TMC_UNLOCK(); + return res; +} \ No newline at end of file diff --git a/arduino_code/Test/src/motor.h b/arduino_code/Test/src/motor.h new file mode 100644 index 0000000..19896d0 --- /dev/null +++ b/arduino_code/Test/src/motor.h @@ -0,0 +1,42 @@ +#ifndef MOTOR_H +#define MOTOR_H + +#include +#include + +void motorInit(); +void motorLoop(); + +// Управление движением (через UART VACTUAL) +void setTargetRPM(int rpm); // Поддерживает отрицательные значения для реверса! +void resetSteps(); + +// Геттеры состояния движения +unsigned long getMotorSteps(); // Считается программно +int getCurrentRPM(); +bool isMotorRunning(); + +// Управление TMC2209 через UART +void tmcSetCurrentPercent(uint8_t run_percent, uint8_t hold_percent); +void tmcSetMicrosteps(uint16_t ms); +void tmcSetStallGuard(uint8_t threshold); +void tmcSoftwareEnable(bool enable); // Вкл/Выкл драйвер программно +void tmcSetStealthChop(bool enable); +void tmcSetCoolStep(bool enable); + +// Расширенная телеметрия +bool tmcIsInitialized(); +bool tmcIsCommunicating(); +uint16_t tmcGetMicrostepsSetting(); +uint8_t tmcGetRunCurrentPercent(); + +// Структуры статусов для MQTT +TMC2209::Status tmcGetStatus(); +TMC2209::Settings tmcGetSettings(); +uint16_t tmcGetStallGuardResult(); +uint32_t tmcGetInterstepDuration(); +uint16_t tmcGetMicrostepCounter(); +bool checkDriverStatus(); +bool checkTmcSoftwareEnable(); + +#endif \ No newline at end of file diff --git a/arduino_code/Test/src/mqtt_handler.cpp b/arduino_code/Test/src/mqtt_handler.cpp new file mode 100644 index 0000000..e896ef9 --- /dev/null +++ b/arduino_code/Test/src/mqtt_handler.cpp @@ -0,0 +1,506 @@ +#include "mqtt_handler.h" +#include "config.h" +#include "motor.h" +#include "servo_control.h" +#include "vl53l0x_sensor.h" + +static WiFiClient espClient; +static PubSubClient client(espClient); + +// Кэш телеметрии +static unsigned long last_feedback_time = 0; +static int last_pub_rpm = -1; +static unsigned long last_pub_steps = -1; +static int last_pub_is_run = -1; + +// TMC Кэш +static uint16_t last_pub_sg = 65535; +static uint32_t last_pub_interstep = 0; +static uint8_t last_pub_current_pct = 255; +static uint16_t last_pub_microsteps = 0; + +// Статусы (битовые флаги) +static int last_pub_over_temp = -1; +static int last_pub_short_gnd = -1; +static int last_pub_open_load = -1; +static int last_pub_stealth_active = -1; +static int last_pub_standstill = -1; +static int last_pub_driver_status = -1; +static int last_pub_tmc_software_enable = -1; +static uint8_t last_pub_current_scaling = 255; + +// ============================================ +// КЭШИРОВАНИЕ ДЛЯ VL53L0X +// ============================================ + +#define VL53L0X_MAX_CHANNELS 8 +static uint16_t last_pub_vl53_distance[VL53L0X_MAX_CHANNELS] = {65535}; +static uint16_t last_pub_vl53_raw[VL53L0X_MAX_CHANNELS] = {65535}; +static int last_pub_vl53_status[VL53L0X_MAX_CHANNELS] = {-1}; +static int last_pub_vl53_calibrated[VL53L0X_MAX_CHANNELS] = {-1}; +static MeasurementMode last_pub_vl53_mode = MODE_COUNT; + +// ============================================ +// БУФЕРЫ ДЛЯ ПРЕОБРАЗОВАНИЯ +// ============================================ + +static char int_buffer[16]; +static char uint_buffer[16]; + +static const char* intToString(int value) { + snprintf(int_buffer, sizeof(int_buffer), "%d", value); + return int_buffer; +} + +static const char* uintToString(unsigned long value) { + snprintf(uint_buffer, sizeof(uint_buffer), "%lu", value); + return uint_buffer; +} + +// ============================================ +// WIFI И MQTT ПОДКЛЮЧЕНИЕ +// ============================================ + +static void setup_wifi() { + Serial.print("Connecting to WiFi"); + WiFi.begin(WIFI_SSID, WIFI_PASS); + while (WiFi.status() != WL_CONNECTED) { + vTaskDelay(pdMS_TO_TICKS(500)); + Serial.print("."); + } + Serial.println("\nWiFi Connected"); +} + +static void resetAllCaches() { + // Motor + last_pub_rpm = -1; + last_pub_steps = (unsigned long)-1; + last_pub_is_run = -1; + last_pub_sg = 65535; + last_pub_interstep = 0; + last_pub_current_pct = 255; + last_pub_microsteps = 0; + last_pub_over_temp = -1; + last_pub_short_gnd = -1; + last_pub_open_load = -1; + last_pub_stealth_active = -1; + last_pub_standstill = -1; + last_pub_current_scaling = 255; + last_pub_driver_status = -1; + last_pub_tmc_software_enable = -1; + + // VL53L0X + for (int i = 0; i < VL53L0X_MAX_CHANNELS; i++) { + last_pub_vl53_distance[i] = 65535; + last_pub_vl53_raw[i] = 65535; + last_pub_vl53_status[i] = -1; + last_pub_vl53_calibrated[i] = -1; + } + last_pub_vl53_mode = MODE_COUNT; +} + +static void subscribeToAllTopics() { + // Motor + client.subscribe("motor/control/rpm"); + client.subscribe("motor/control/driver"); + client.subscribe("motor/control/totalsteps/reset"); + client.subscribe("motor/control/tmc/current_percent"); + client.subscribe("motor/control/tmc/microsteps"); + client.subscribe("motor/control/tmc/stallguard"); + client.subscribe("motor/control/tmc/enable"); + client.subscribe("motor/control/tmc/stealthchop"); + client.subscribe("motor/control/tmc/coolstep"); + + // Servo + client.subscribe("servo/control/+/#"); + + // VL53L0X + client.subscribe("sensor/control/mode"); + client.subscribe("sensor/control/mode_name"); + client.subscribe("sensor/control/calibrate/start/+"); + client.subscribe("sensor/control/calibrate/finish/+"); + client.subscribe("sensor/control/clear_cal/+"); + client.subscribe("sensor/control/enable/+"); + client.subscribe("sensor/control/publish_all"); +} + +static void reconnect() { + while (!client.connected()) { + if (client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) { + resetAllCaches(); + subscribeToAllTopics(); + Serial.println("MQTT Connected and subscribed"); + } else { + Serial.printf("MQTT connection failed, rc=%d, retrying...\n", client.state()); + vTaskDelay(pdMS_TO_TICKS(5000)); + } + } +} + +// ============================================ +// ОБРАБОТКА ВХОДЯЩИХ MQTT КОМАНД +// ============================================ +static void callback(char* topic, byte* payload, unsigned int length) { + char msg[length + 1]; + memcpy(msg, payload, length); + msg[length] = '\0'; + + if (strcmp(topic, "motor/control/rpm") == 0) { + setTargetRPM(atoi(msg)); // Поддерживает отрицательные для реверса! + } + else if (strcmp(topic, "motor/control/driver") == 0) { + // TMC2209: LOW = Enabled, HIGH = Disabled + bool enable = (strcmp(msg, "on") == 0); + digitalWrite(EN_PIN, enable ? LOW : HIGH); + } + else if (strcmp(topic, "motor/control/totalsteps/reset") == 0) { + resetSteps(); + if (client.connected()) client.publish("motor/feedback/totalsteps", "0"); + last_pub_steps = 0; + } + // --- TMC Control --- + else if (strcmp(topic, "motor/control/tmc/current_percent") == 0) { + uint8_t pct = atoi(msg); + if (pct <= 100) tmcSetCurrentPercent(pct, pct / 2); // Hold = 50% от Run + } + else if (strcmp(topic, "motor/control/tmc/microsteps") == 0) { + uint16_t ms = atoi(msg); + tmcSetMicrosteps(ms); + } + else if (strcmp(topic, "motor/control/tmc/stallguard") == 0) { + tmcSetStallGuard(atoi(msg)); + } + else if (strcmp(topic, "motor/control/tmc/enable") == 0) { + tmcSoftwareEnable(strcmp(msg, "on") == 0); + } + else if (strcmp(topic, "motor/control/tmc/stealthchop") == 0) { + tmcSetStealthChop(strcmp(msg, "on") == 0); + } + else if (strcmp(topic, "motor/control/tmc/coolstep") == 0) { + tmcSetCoolStep(strcmp(msg, "on") == 0); + } + else if (strncmp(topic, "servo/control", 12) == 0) { + handleServoMQTTCommand(topic, msg); + } + // === VL53L0X === + else if (strcmp(topic, "sensor/control/mode") == 0) { + int mode = atoi(msg); + if (mode >= 0 && mode < MODE_COUNT) { + vl53l0xSetMode((MeasurementMode)mode); + } + } + else if (strcmp(topic, "sensor/control/mode_name") == 0) { + // Маппинг имени режима на ID (если нужно) + // Пока просто логируем + Serial.printf("Mode name request: %s\n", msg); + } + else if (strncmp(topic, "sensor/control/calibrate/start/", 31) == 0) { + int channel = atoi(topic + 31); + int near_mm = atoi(msg); + if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS && near_mm > 0) { + vl53l0xStartCalibration(channel, near_mm); + } + } + else if (strncmp(topic, "sensor/control/calibrate/finish/", 32) == 0) { + int channel = atoi(topic + 32); + int far_mm = atoi(msg); + if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS && far_mm > 0) { + vl53l0xFinishCalibration(channel, far_mm); + } + } + else if (strncmp(topic, "sensor/control/clear_cal/", 25) == 0) { + int channel = atoi(topic + 25); + if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS) { + vl53l0xClearCalibration(channel); + } + } + else if (strncmp(topic, "sensor/control/enable/", 22) == 0) { + int channel = atoi(topic + 22); + bool enable = (strcmp(msg, "on") == 0 || strcmp(msg, "1") == 0 || strcmp(msg, "true") == 0); + if (channel >= 0 && channel < VL53L0X_MAX_CHANNELS) { + vl53l0xEnableChannel(channel, enable); + } + } + else if (strcmp(topic, "sensor/control/publish_all") == 0) { + // Сбрасываем кэш для принудительной публикации + for (int i = 0; i < VL53L0X_MAX_CHANNELS; i++) { + last_pub_vl53_distance[i] = 65535; + last_pub_vl53_raw[i] = 65535; + } + } +} + +// ============================================ +// ПУБЛИКАЦИЯ MOTOR TELEMETRY +// ============================================ + +static void publishMotorTelemetry() { + // RPM + int rpm = getCurrentRPM(); + if (rpm != last_pub_rpm) { + client.publish("motor/feedback/rpm", intToString(rpm)); + last_pub_rpm = rpm; + } + + // Steps + unsigned long steps = getMotorSteps(); + if (steps != last_pub_steps) { + client.publish("motor/feedback/totalsteps", uintToString(steps)); + last_pub_steps = steps; + } + + // Is running + int run = isMotorRunning() ? 1 : 0; + if (run != last_pub_is_run) { + client.publish("motor/feedback/is_run", run ? "true" : "false"); + last_pub_is_run = run; + } + + // TMC + if (tmcIsInitialized()) { + uint8_t pct = tmcGetRunCurrentPercent(); + if (pct != last_pub_current_pct) { + client.publish("motor/feedback/tmc/current_percent", intToString(pct)); + last_pub_current_pct = pct; + } + + uint16_t ms = tmcGetMicrostepsSetting(); + if (ms != last_pub_microsteps) { + client.publish("motor/feedback/tmc/microsteps", intToString(ms)); + last_pub_microsteps = ms; + } + + uint16_t sg = tmcGetStallGuardResult(); + if (sg != last_pub_sg) { + client.publish("motor/feedback/tmc/sg_result", intToString(sg)); + last_pub_sg = sg; + } + + uint32_t interstep = tmcGetInterstepDuration(); + if (interstep != last_pub_interstep) { + client.publish("motor/feedback/tmc/interstep_duration", uintToString(interstep)); + last_pub_interstep = interstep; + } + + TMC2209::Status status = tmcGetStatus(); + + int ot = (status.over_temperature_warning || status.over_temperature_shutdown) ? 1 : 0; + if (ot != last_pub_over_temp) { + client.publish("motor/feedback/tmc/status/over_temp", ot ? "true" : "false"); + last_pub_over_temp = ot; + } + + int sgnd = (status.short_to_ground_a || status.short_to_ground_b) ? 1 : 0; + if (sgnd != last_pub_short_gnd) { + client.publish("motor/feedback/tmc/status/short_to_ground", sgnd ? "true" : "false"); + last_pub_short_gnd = sgnd; + } + + int ol = (status.open_load_a || status.open_load_b) ? 1 : 0; + if (ol != last_pub_open_load) { + client.publish("motor/feedback/tmc/status/open_load", ol ? "true" : "false"); + last_pub_open_load = ol; + } + + int sa = status.stealth_chop_mode ? 1 : 0; + if (sa != last_pub_stealth_active) { + client.publish("motor/feedback/tmc/status/stealth_chop_active", sa ? "true" : "false"); + last_pub_stealth_active = sa; + } + + int ss = status.standstill ? 1 : 0; + if (ss != last_pub_standstill) { + client.publish("motor/feedback/tmc/status/standstill", ss ? "true" : "false"); + last_pub_standstill = ss; + } + + if (status.current_scaling != last_pub_current_scaling) { + client.publish("motor/feedback/tmc/status/current_scaling", intToString(status.current_scaling)); + last_pub_current_scaling = status.current_scaling; + } + + int cds = checkDriverStatus() ? 1 : 0; + if (cds != last_pub_driver_status) { + client.publish("motor/feedback/driver/status", cds ? "on" : "off"); + last_pub_driver_status = cds; + } + + int tse = checkTmcSoftwareEnable() ? 1 : 0; + if (tse != last_pub_tmc_software_enable) { + client.publish("motor/feedback/tmc/status", tse ? "on" : "off"); + last_pub_tmc_software_enable = tse; + } + } +} + +// ============================================ +// ПУБЛИКАЦИЯ VL53L0X TELEMETRY +// ============================================ + +static void publishVL53L0XTelemetry() { + // Публикация режима + MeasurementMode current_mode = vl53l0xGetMode(); + if (current_mode != last_pub_vl53_mode) { + const ModeProfile* profile = vl53l0xGetModeProfile(); + + client.publish("sensor/feedback/mode", profile->name); + client.publish("sensor/feedback/mode_id", intToString(current_mode)); + client.publish("sensor/feedback/max_range", intToString(profile->max_range_mm)); + + last_pub_vl53_mode = current_mode; + } + + // Публикация данных с каждого канала + for (uint8_t ch = 0; ch < VL53L0X_MAX_CHANNELS; ch++) { + if (!vl53l0xIsChannelPresent(ch)) continue; + + char topic[64]; + + // Статус канала + int status = vl53l0xIsChannelEnabled(ch) ? 1 : 0; + if (status != last_pub_vl53_status[ch]) { + snprintf(topic, sizeof(topic), "sensor/feedback/%d/status", ch); + client.publish(topic, status ? "on" : "off"); + last_pub_vl53_status[ch] = status; + } + + // Статус калибровки + int calibrated = vl53l0xIsCalibrated(ch) ? 1 : 0; + if (calibrated != last_pub_vl53_calibrated[ch]) { + snprintf(topic, sizeof(topic), "sensor/feedback/%d/calibrated", ch); + client.publish(topic, calibrated ? "true" : "false"); + last_pub_vl53_calibrated[ch] = calibrated; + } + + // Только если канал активен, публикуем расстояния + if (vl53l0xIsChannelActive(ch)) { + // Калиброванное расстояние + uint16_t distance = vl53l0xReadDistance(ch); + if (distance != last_pub_vl53_distance[ch]) { + snprintf(topic, sizeof(topic), "sensor/feedback/%d/distance", ch); + if (distance != 65535) { + client.publish(topic, intToString(distance)); + } else { + client.publish(topic, "out_of_range"); + } + last_pub_vl53_distance[ch] = distance; + } + + // Сырое значение + uint16_t raw = vl53l0xReadRawDistance(ch); + if (raw != last_pub_vl53_raw[ch]) { + snprintf(topic, sizeof(topic), "sensor/feedback/%d/raw", ch); + if (raw != 65535) { + client.publish(topic, intToString(raw)); + } + last_pub_vl53_raw[ch] = raw; + } + } + } +} + +///// + +void checkAndPublishServoStatus(uint8_t channel) { + if (channel >= MAX_SERVOS) return; + + bool current_state = servo_enabled[channel]; + if (current_state != last_published_status[channel]) { + last_published_status[channel] = current_state; + + char topic[64]; + snprintf(topic, sizeof(topic), "servo/%d/feedback/status", channel); + + String status = current_state ? "on" : "off"; + client.publish(topic, status.c_str()); + + Serial.printf("Published servo %d status: %s\n", channel, status.c_str()); + } +} + +void checkAndPublishServoAngle(uint8_t channel) { + if (channel >= MAX_SERVOS) return; + + uint8_t current_angle = current_angles[channel]; + if (current_angle != last_published_angles[channel]) { + last_published_angles[channel] = current_angle; + + char topic[64]; + snprintf(topic, sizeof(topic), "servo/%d/feedback/angle", channel); + + char payload[8]; + snprintf(payload, sizeof(payload), "%d", current_angle); + client.publish(topic, payload); + + Serial.printf("Published servo %d angle: %d\n", channel, current_angle); + } +} + +void handleServoMQTTCommand(const char* topic, const char* payload) { + // Парсим топик: servo/control/{channel}/{command} + int channel = -1; + char command[32] = {0}; + + if (sscanf(topic, "servo/control/%2d/%8s", &channel, command) != 2) { + Serial.printf("Invalid servo topic: %s\n", topic); + return; + } + + if (channel < 0 || channel >= MAX_SERVOS) { + Serial.printf("Invalid servo channel: %d\n", channel); + return; + } + + Serial.printf("Servo %d command: %s = %s\n", channel, command, payload); + + if (strcmp(command, "angle") == 0) { + int angle = atoi(payload); + if (angle >= 0 && angle <= 180) { + setServoAngle(channel, (uint8_t)angle); + checkAndPublishServoAngle(channel); + checkAndPublishServoStatus(channel); + } + } + else if (strcmp(command, "enable") == 0) { + bool enable = (strcmp(payload, "on") == 0 || strcmp(payload, "1") == 0 || strcmp(payload, "true") == 0); + if (enable) { + enableServo(channel); + } else { + disableServo(channel); + } + checkAndPublishServoStatus(channel); + } +} + +///// + +// ============================================ +// ГЛАВНЫЙ ЦИКЛ ПУБЛИКАЦИИ +// ============================================ + +static void publishTelemetry() { + unsigned long now = millis(); + if (now - last_feedback_time >= 500) { + publishMotorTelemetry(); + publishVL53L0XTelemetry(); + last_feedback_time = now; + } +} + +void mqttTask(void *parameter) { + static unsigned long last_stack_check = 0; + + setup_wifi(); + client.setServer(MQTT_SERVER, MQTT_PORT); + client.setCallback(callback); + + servoInit(); + + for (;;) { + if (!client.connected()) reconnect(); + client.loop(); + publishTelemetry(); + + vTaskDelay(pdMS_TO_TICKS(10)); + } +} \ No newline at end of file diff --git a/arduino_code/Test/src/mqtt_handler.h b/arduino_code/Test/src/mqtt_handler.h new file mode 100644 index 0000000..9e2da27 --- /dev/null +++ b/arduino_code/Test/src/mqtt_handler.h @@ -0,0 +1,11 @@ +#ifndef MQTT_HANDLER_H +#define MQTT_HANDLER_H + +#include +#include +#include + +void mqttTask(void *parameter); +void handleServoMQTTCommand(const char* topic, const char* payload); + +#endif \ No newline at end of file diff --git a/arduino_code/Test/src/servo_control.cpp b/arduino_code/Test/src/servo_control.cpp new file mode 100644 index 0000000..8e26f1c --- /dev/null +++ b/arduino_code/Test/src/servo_control.cpp @@ -0,0 +1,98 @@ +#include "servo_control.h" +#include "mqtt_handler.h" +#include "config.h" + +static Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); +static bool servo_initialized = false; + + + +void servoInit() { + Serial.println("Initializing PCA9685 servo driver..."); + + // Инициализация I2C на пинах 21 (SDA) и 22 (SCL) + Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); + + pwm.begin(); + pwm.setOscillatorFrequency(27000000); + pwm.setPWMFreq(50); // 50 Hz для сервоприводов + + delay(10); + + servo_initialized = true; + Serial.println("PCA9685 initialized successfully"); + + // Инициализируем все каналы как выключенные + for (int i = 0; i < MAX_SERVOS; i++) { + current_angles[i] = 90; // Начальное положение - середина + servo_enabled[i] = false; + disableServo(i); + } +} + +// Преобразование угла (0-180) в длину импульса +uint16_t angleToPulse(uint8_t angle) { + if (angle > 180) angle = 180; + return map(angle, 0, 180, SERVO_MIN_PULSE, SERVO_MAX_PULSE); +} + +// Преобразование длины импульса в угол +uint8_t pulseToAngle(uint16_t pulse) { + if (pulse < SERVO_MIN_PULSE) return 0; + if (pulse > SERVO_MAX_PULSE) return 180; + return map(pulse, SERVO_MIN_PULSE, SERVO_MAX_PULSE, 0, 180); +} + +void setServoAngle(uint8_t channel, uint8_t angle) { + if (!servo_initialized || channel >= MAX_SERVOS) return; + + if (angle > 180) angle = 180; + + current_angles[channel] = angle; + servo_enabled[channel] = true; + + uint16_t pulse = angleToPulse(angle); + setServoPulse(channel, pulse); + + Serial.printf("Servo %d: angle=%d, pulse=%d\n", channel, angle, pulse); +} + +void setServoPulse(uint8_t channel, uint16_t pulse) { + if (!servo_initialized || channel >= MAX_SERVOS) return; + + // Преобразование микросекунд в тики PCA9685 + // PCA9685 имеет 4096 тиков на период при 50Hz = 20000 мкс + // 1 мкс = 4096 / 20000 = 0.2048 тика + double pulselength = 4096.0 / 20000.0; // тиков на микросекунду + uint16_t ticks = pulse * pulselength; + + pwm.setPWM(channel, 0, ticks); +} + +void enableServo(uint8_t channel) { + if (!servo_initialized || channel >= MAX_SERVOS) return; + + servo_enabled[channel] = true; + setServoAngle(channel, current_angles[channel]); + + Serial.printf("Servo %d: ENABLED\n", channel); +} + +void disableServo(uint8_t channel) { + if (!servo_initialized || channel >= MAX_SERVOS) return; + + servo_enabled[channel] = false; + pwm.setPWM(channel, 0, 0); // Отключаем сигнал + + Serial.printf("Servo %d: DISABLED\n", channel); +} + +uint8_t getServoAngle(uint8_t channel) { + if (channel >= MAX_SERVOS) return 0; + return current_angles[channel]; +} + +bool isServoEnabled(uint8_t channel) { + if (channel >= MAX_SERVOS) return false; + return servo_enabled[channel]; +} \ No newline at end of file diff --git a/arduino_code/Test/src/servo_control.h b/arduino_code/Test/src/servo_control.h new file mode 100644 index 0000000..0f6b203 --- /dev/null +++ b/arduino_code/Test/src/servo_control.h @@ -0,0 +1,32 @@ +#ifndef SERVO_H +#define SERVO_H + +#include +#include + + +// Хранение текущего состояния сервоприводов +#define MAX_SERVOS 16 +static uint8_t current_angles[MAX_SERVOS] = {0}; +static bool servo_enabled[MAX_SERVOS] = {false}; +static uint8_t last_published_angles[MAX_SERVOS] = {255}; +static bool last_published_status[MAX_SERVOS] = {false}; + +// Минимальная и максимальная длина импульса для сервопривода (в микросекундах) +static const uint16_t SERVO_MIN_PULSE = 600; +static const uint16_t SERVO_MAX_PULSE = 2400; + +// Инициализация сервопривода +void servoInit(); + +// Управление сервоприводом +void setServoAngle(uint8_t channel, uint8_t angle); +void setServoPulse(uint8_t channel, uint16_t pulse); +void enableServo(uint8_t channel); +void disableServo(uint8_t channel); + +// Получение состояния +uint8_t getServoAngle(uint8_t channel); +bool isServoEnabled(uint8_t channel); + +#endif // SERVO_H \ No newline at end of file diff --git a/arduino_code/Test/src/vl53l0x_sensor.cpp b/arduino_code/Test/src/vl53l0x_sensor.cpp new file mode 100644 index 0000000..79b3f92 --- /dev/null +++ b/arduino_code/Test/src/vl53l0x_sensor.cpp @@ -0,0 +1,477 @@ +#include "vl53l0x_sensor.h" +#include "config.h" + +// ============================================ +// КОНСТАНТЫ +// ============================================ + +#define TCA9548A_ADDRESS 0x70 +#define TCA9548A_CHANNELS 8 + +#define MIN_DISTANCE_MM 30 +#define OUT_OF_RANGE_VALUE 65535 + +#define FILTER_SIZE 5 + +// ============================================ +// ПРОФИЛИ РЕЖИМОВ +// ============================================ + +static const ModeProfile MODES[MODE_COUNT] = { + { + "HIGH_ACCURACY", + 200000, 14, 10, 0.5f, 500, 2 + }, + { + "PRECISION", + 66000, 14, 10, 0.3f, 1000, 5 + }, + { + "DEFAULT", + 33000, 14, 10, 0.25f, 1200, 15 + }, + { + "LONG_RANGE", + 33000, 18, 14, 0.1f, 2000, 40 + }, + { + "ULTRA_LONG", + 100000, 18, 14, 0.05f, 2500, 80 + } +}; + +// ============================================ +// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ +// ============================================ + +static VL53L0X sensor; +static Preferences preferences; + +static MeasurementMode current_mode = MODE_DEFAULT; +static bool sensor_present[TCA9548A_CHANNELS] = {false}; +static bool channel_enabled[TCA9548A_CHANNELS] = {false}; +static CalibrationData calibration[TCA9548A_CHANNELS]; + +static uint16_t filter_buffer[TCA9548A_CHANNELS][FILTER_SIZE]; +static uint8_t filter_index[TCA9548A_CHANNELS] = {0}; + +static bool calibration_in_progress = false; +static uint8_t calibration_channel = 0; +static uint16_t calibration_near_raw = 0; +static uint16_t calibration_near_known = 0; + +// ============================================ +// TCA9548A МУЛЬТИПЛЕКСОР +// ============================================ + +static void TCA9548A_Select(uint8_t channel) { + Wire.beginTransmission(TCA9548A_ADDRESS); + Wire.write((channel < TCA9548A_CHANNELS) ? (1 << channel) : 0x00); + Wire.endTransmission(); + delay(3); +} + +static void TCA9548A_DisableAll() { + Wire.beginTransmission(TCA9548A_ADDRESS); + Wire.write(0x00); + Wire.endTransmission(); +} + +static bool checkTCA9548A() { + Wire.beginTransmission(TCA9548A_ADDRESS); + return (Wire.endTransmission() == 0); +} + +// ============================================ +// NVS (СОХРАНЕНИЕ В FLASH) +// ============================================ + +static void loadCalibration() { + preferences.begin("vl53_cal", true); + + for (uint8_t ch = 0; ch < TCA9548A_CHANNELS; ch++) { + char key[16]; + snprintf(key, sizeof(key), "ch%d", ch); + + size_t len = preferences.getBytesLength(key); + if (len == sizeof(CalibrationData)) { + preferences.getBytes(key, &calibration[ch], sizeof(CalibrationData)); + } else { + calibration[ch].valid = false; + calibration[ch].scale = 1.0f; + calibration[ch].offset = 0.0f; + } + } + + int saved_mode = preferences.getInt("mode", MODE_DEFAULT); + if (saved_mode >= 0 && saved_mode < MODE_COUNT) { + current_mode = (MeasurementMode)saved_mode; + } + + preferences.end(); +} + +static void saveCalibration(uint8_t channel) { + preferences.begin("vl53_cal", false); + char key[16]; + snprintf(key, sizeof(key), "ch%d", channel); + preferences.putBytes(key, &calibration[channel], sizeof(CalibrationData)); + preferences.end(); +} + +static void saveMode() { + preferences.begin("vl53_cal", false); + preferences.putInt("mode", (int)current_mode); + preferences.end(); +} + +// ============================================ +// ПРИМЕНЕНИЕ РЕЖИМА +// ============================================ + +static bool applyModeProfile() { + const ModeProfile& profile = MODES[current_mode]; + + sensor.setTimeout(500); + + if (!sensor.init()) { + return false; + } + + sensor.setAddress(0x29); + sensor.setSignalRateLimit(profile.signal_rate_limit); + + sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, profile.vcsel_prerange); + sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, profile.vcsel_final); + sensor.setMeasurementTimingBudget(profile.timing_budget_us); + + return true; +} + +// ============================================ +// КАЛИБРОВКА И ФИЛЬТРАЦИЯ +// ============================================ + +static uint16_t applyCalibration(uint8_t channel, uint16_t raw_mm) { + if (!calibration[channel].valid || raw_mm == OUT_OF_RANGE_VALUE) { + return raw_mm; + } + + float calibrated = (float)raw_mm * calibration[channel].scale + calibration[channel].offset; + + const ModeProfile& profile = MODES[current_mode]; + if (calibrated < MIN_DISTANCE_MM) calibrated = MIN_DISTANCE_MM; + if (calibrated > profile.max_range_mm) return OUT_OF_RANGE_VALUE; + + return (uint16_t)calibrated; +} + +static uint16_t applyFilter(uint8_t channel, uint16_t new_value) { + if (new_value == OUT_OF_RANGE_VALUE) return OUT_OF_RANGE_VALUE; + + filter_buffer[channel][filter_index[channel]] = new_value; + filter_index[channel] = (filter_index[channel] + 1) % FILTER_SIZE; + + uint32_t sum = 0; + uint8_t count = 0; + for (uint8_t i = 0; i < FILTER_SIZE; i++) { + if (filter_buffer[channel][i] != 0) { + sum += filter_buffer[channel][i]; + count++; + } + } + + return (count > 0) ? (uint16_t)(sum / count) : new_value; +} + +// ============================================ +// ПУБЛИЧНЫЕ ФУНКЦИИ +// ============================================ + +void vl53l0xInit() { + Serial.println("Initializing VL53L0X sensors..."); + + Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); + Wire.setClock(400000); + delay(100); + + if (!checkTCA9548A()) { + Serial.println("ERROR: TCA9548A not found!"); + return; + } + Serial.println("✓ TCA9548A found"); + TCA9548A_DisableAll(); + + loadCalibration(); + Serial.printf("✓ Loaded mode: %s\n", MODES[current_mode].name); + + Serial.println("\nScanning VL53L0X channels:"); + for (uint8_t channel = 0; channel < TCA9548A_CHANNELS; channel++) { + TCA9548A_Select(channel); + delay(20); + + if (applyModeProfile()) { + sensor_present[channel] = true; + channel_enabled[channel] = true; + Serial.printf(" ✓ Channel %d: VL53L0X found", channel); + if (calibration[channel].valid) Serial.print(" [CALIBRATED]"); + Serial.println(); + + memset(filter_buffer[channel], 0, sizeof(filter_buffer[channel])); + filter_index[channel] = 0; + } else { + sensor_present[channel] = false; + channel_enabled[channel] = false; + Serial.printf(" ✗ Channel %d: No device\n", channel); + } + + TCA9548A_DisableAll(); + delay(5); + } + + Serial.printf("\n✓ VL53L0X initialized: %d sensors found\n\n", + vl53l0xGetChannelCount()); +} + +void vl53l0xLoop() { + // Внутренняя логика датчиков (если нужна) + // Сейчас вся публикация в mqtt_handle.cpp +} + +bool vl53l0xSetMode(MeasurementMode mode) { + if (mode < 0 || mode >= MODE_COUNT) { + Serial.printf("ERROR: Invalid mode %d\n", mode); + return false; + } + + current_mode = mode; + saveMode(); + + const ModeProfile& profile = MODES[current_mode]; + Serial.printf("✓ Mode changed to: %s (max %d mm)\n", + profile.name, profile.max_range_mm); + + return true; +} + +MeasurementMode vl53l0xGetMode() { + return current_mode; +} + +const ModeProfile* vl53l0xGetModeProfile() { + return &MODES[current_mode]; +} + +uint16_t vl53l0xReadDistance(uint8_t channel) { + if (channel >= TCA9548A_CHANNELS || !sensor_present[channel] || !channel_enabled[channel]) { + return OUT_OF_RANGE_VALUE; + } + + TCA9548A_Select(channel); + + if (!applyModeProfile()) { + TCA9548A_DisableAll(); + return OUT_OF_RANGE_VALUE; + } + + uint16_t distance = sensor.readRangeSingleMillimeters(); + bool timeout = sensor.timeoutOccurred(); + + TCA9548A_DisableAll(); + + if (distance == 65535 || timeout) return OUT_OF_RANGE_VALUE; + + const ModeProfile& profile = MODES[current_mode]; + if (distance < MIN_DISTANCE_MM || distance > profile.max_range_mm) { + return OUT_OF_RANGE_VALUE; + } + + uint16_t filtered = applyFilter(channel, distance); + uint16_t calibrated = applyCalibration(channel, filtered); + + return calibrated; +} + +uint16_t vl53l0xReadRawDistance(uint8_t channel) { + if (channel >= TCA9548A_CHANNELS || !sensor_present[channel] || !channel_enabled[channel]) { + return OUT_OF_RANGE_VALUE; + } + + TCA9548A_Select(channel); + + if (!applyModeProfile()) { + TCA9548A_DisableAll(); + return OUT_OF_RANGE_VALUE; + } + + uint16_t distance = sensor.readRangeSingleMillimeters(); + bool timeout = sensor.timeoutOccurred(); + + TCA9548A_DisableAll(); + + if (distance == 65535 || timeout) return OUT_OF_RANGE_VALUE; + + return distance; +} + +bool vl53l0xIsChannelActive(uint8_t channel) { + return (channel < TCA9548A_CHANNELS && sensor_present[channel] && channel_enabled[channel]); +} + +int vl53l0xGetChannelCount() { + int count = 0; + for (uint8_t i = 0; i < TCA9548A_CHANNELS; i++) { + if (sensor_present[i]) count++; + } + return count; +} + +bool vl53l0xStartCalibration(uint8_t channel, uint16_t near_known_mm) { + if (channel >= TCA9548A_CHANNELS || !sensor_present[channel]) { + Serial.printf("ERROR: Channel %d not available\n", channel); + return false; + } + + Serial.printf("Starting calibration for channel %d (near point: %d mm)\n", + channel, near_known_mm); + + uint32_t sum = 0; + uint8_t valid = 0; + + for (uint8_t i = 0; i < 20; i++) { + uint16_t d = vl53l0xReadRawDistance(channel); + if (d != OUT_OF_RANGE_VALUE) { + sum += d; + valid++; + } + delay(50); + } + + if (valid == 0) { + Serial.println("ERROR: Failed to read near point"); + return false; + } + + calibration_near_raw = (uint16_t)(sum / valid); + calibration_near_known = near_known_mm; + calibration_channel = channel; + calibration_in_progress = true; + + Serial.printf("✓ Near point captured: raw=%d mm, actual=%d mm\n", + calibration_near_raw, calibration_near_known); + Serial.println("Now place object at FAR point and call vl53l0xFinishCalibration()"); + + return true; +} + +bool vl53l0xFinishCalibration(uint8_t channel, uint16_t far_known_mm) { + if (!calibration_in_progress || channel != calibration_channel) { + Serial.println("ERROR: Calibration not in progress or wrong channel"); + return false; + } + + Serial.printf("Finishing calibration for channel %d (far point: %d mm)\n", + channel, far_known_mm); + + uint32_t sum = 0; + uint8_t valid = 0; + + for (uint8_t i = 0; i < 20; i++) { + uint16_t d = vl53l0xReadRawDistance(channel); + if (d != OUT_OF_RANGE_VALUE) { + sum += d; + valid++; + } + delay(50); + } + + if (valid == 0) { + Serial.println("ERROR: Failed to read far point"); + calibration_in_progress = false; + return false; + } + + uint16_t far_raw = (uint16_t)(sum / valid); + + Serial.printf("✓ Far point captured: raw=%d mm, actual=%d mm\n", + far_raw, far_known_mm); + + if (far_raw == calibration_near_raw) { + Serial.println("ERROR: Raw values are identical"); + calibration_in_progress = false; + return false; + } + + float scale = (float)(far_known_mm - calibration_near_known) / + (float)(far_raw - calibration_near_raw); + float offset = (float)calibration_near_known - + (float)calibration_near_raw * scale; + + calibration[channel].valid = true; + calibration[channel].near_raw = calibration_near_raw; + calibration[channel].near_known = calibration_near_known; + calibration[channel].far_raw = far_raw; + calibration[channel].far_known = far_known_mm; + calibration[channel].scale = scale; + calibration[channel].offset = offset; + + saveCalibration(channel); + + Serial.printf("✓ Calibration complete: scale=%.5f, offset=%.2f\n", scale, offset); + + calibration_in_progress = false; + + return true; +} + +void vl53l0xClearCalibration(uint8_t channel) { + if (channel >= TCA9548A_CHANNELS) return; + + calibration[channel].valid = false; + calibration[channel].scale = 1.0f; + calibration[channel].offset = 0.0f; + saveCalibration(channel); + + Serial.printf("✓ Channel %d calibration cleared\n", channel); +} + +bool vl53l0xIsCalibrated(uint8_t channel) { + return (channel < TCA9548A_CHANNELS && calibration[channel].valid); +} + +CalibrationData vl53l0xGetCalibration(uint8_t channel) { + if (channel >= TCA9548A_CHANNELS) { + CalibrationData empty = {false, 0, 0, 0, 0, 1.0f, 0.0f}; + return empty; + } + return calibration[channel]; +} + +void vl53l0xEnableChannel(uint8_t channel, bool enable) { + if (channel >= TCA9548A_CHANNELS) return; + + if (!sensor_present[channel]) { + Serial.printf("ERROR: Channel %d not present\n", channel); + return; + } + + channel_enabled[channel] = enable; + Serial.printf("✓ Channel %d %s\n", channel, enable ? "enabled" : "disabled"); +} + +bool vl53l0xIsChannelEnabled(uint8_t channel) { + return (channel < TCA9548A_CHANNELS && channel_enabled[channel]); +} + +bool vl53l0xIsChannelPresent(uint8_t channel) { + return (channel < TCA9548A_CHANNELS && sensor_present[channel]); +} + +void sensorTask(void *parameter) { + vl53l0xInit(); + + for (;;) { + vl53l0xLoop(); + vTaskDelay(pdMS_TO_TICKS(50)); + } +} \ No newline at end of file diff --git a/arduino_code/Test/src/vl53l0x_sensor.h b/arduino_code/Test/src/vl53l0x_sensor.h new file mode 100644 index 0000000..c3a7e91 --- /dev/null +++ b/arduino_code/Test/src/vl53l0x_sensor.h @@ -0,0 +1,68 @@ +#ifndef VL53L0X_SENSOR_H +#define VL53L0X_SENSOR_H + +#include +#include +#include +#include + +// Режимы измерения +enum MeasurementMode { + MODE_HIGH_ACCURACY = 0, + MODE_PRECISION = 1, + MODE_DEFAULT = 2, + MODE_LONG_RANGE = 3, + MODE_ULTRA_LONG = 4, + MODE_COUNT = 5 +}; + +struct ModeProfile { + const char* name; + uint32_t timing_budget_us; + uint8_t vcsel_prerange; + uint8_t vcsel_final; + float signal_rate_limit; + uint16_t max_range_mm; + uint8_t accuracy_mm; +}; + +struct CalibrationData { + bool valid; + uint16_t near_raw; + uint16_t near_known; + uint16_t far_raw; + uint16_t far_known; + float scale; + float offset; +}; + +// Инициализация и цикл +void vl53l0xInit(); +void vl53l0xLoop(); + +// Управление режимами +bool vl53l0xSetMode(MeasurementMode mode); +MeasurementMode vl53l0xGetMode(); +const ModeProfile* vl53l0xGetModeProfile(); + +// Чтение данных +uint16_t vl53l0xReadDistance(uint8_t channel); +uint16_t vl53l0xReadRawDistance(uint8_t channel); +bool vl53l0xIsChannelActive(uint8_t channel); +int vl53l0xGetChannelCount(); + +// Калибровка +bool vl53l0xStartCalibration(uint8_t channel, uint16_t near_known_mm); +bool vl53l0xFinishCalibration(uint8_t channel, uint16_t far_known_mm); +void vl53l0xClearCalibration(uint8_t channel); +bool vl53l0xIsCalibrated(uint8_t channel); +CalibrationData vl53l0xGetCalibration(uint8_t channel); + +// Управление каналами +void vl53l0xEnableChannel(uint8_t channel, bool enable); +bool vl53l0xIsChannelEnabled(uint8_t channel); +bool vl53l0xIsChannelPresent(uint8_t channel); + +void sensorTask(void *parameter); + +#endif // VL53L0X_SENSOR_H \ No newline at end of file diff --git a/arduino_code/Test/test/README b/arduino_code/Test/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/arduino_code/Test/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/backend_control/.gitignore b/backend_control/.gitignore new file mode 100644 index 0000000..64d5cfb --- /dev/null +++ b/backend_control/.gitignore @@ -0,0 +1 @@ +/testing \ No newline at end of file diff --git a/backend_control/README.md b/backend_control/README.md new file mode 100644 index 0000000..4ab4ef8 --- /dev/null +++ b/backend_control/README.md @@ -0,0 +1,61 @@ +# Интерфейс управления + +![](img/gui_interface.png) + +## Область Управления + +`Целевой RPM` - Задается уставка скорости в диапозоне от -1000 до 1000 оборотов в минуту + +`Ток` - Уставка тока в диапозоне 0 - 100% + +`StallGuard` - + +`Микрошаг` - Уставка микрошага + +`Сброс счетчика шагов` - Сброс счетчика шагов + +## Область Режимы + +`Аппаратное вкл. (Driver EN)` - Аппаратное Включение/Выключение драйвера tmc2209 + +`Программное вкл. (TMC Chip)` - Программное Включение/Выключение драйвера tmc2209 + +`StealthChop (Тихий)` - Включение/Выключение тихого режима работы шагового двигателя. + +`CoolStep (Энергосбер.)` - Включение/Выключение энергосберегающего режима работы шагового двигателя. + +## Область Телеметрия и Статусы + +`Текущий RPM (Факт)` + +`Всего шагов` + +`Вращение` + +`SG Result` - Для отслеживания нагрузки на вал или момента срыва шагов. Резкое падение значения sg_result при движении обычно означает столкновение или заклинивание механизма. + +`Interstep (ns)` + +`Current Scaling` + +## Область Ошибки и Флаги + +`Перегрев` + +`КЗ на землю` + +`Обрыв нагрузки` + +`StealthChop активен` + +`Остановка (Standstill)` + +![](img/servo_interface.png) + +## Область Управление Серво + +Тут можно задать угол сервопривода и отключить сервопривод + +## Облать Статус и телеметрия + +Вывод информации о сервоприводе включен или выключен. Так же выведен текущий угол. \ No newline at end of file diff --git a/backend_control/create_venv.sh b/backend_control/create_venv.sh new file mode 100644 index 0000000..4fdf688 --- /dev/null +++ b/backend_control/create_venv.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +python -m venv testing + +source testing/bin/activate +pip install -r requirements.txt \ No newline at end of file diff --git a/backend_control/gui.py b/backend_control/gui.py new file mode 100644 index 0000000..20e209a --- /dev/null +++ b/backend_control/gui.py @@ -0,0 +1,460 @@ +import customtkinter as ctk +import paho.mqtt.client as mqtt + +# ================= НАСТРОЙКИ ================= +MQTT_BROKER = "192.168.0.200" +MQTT_PORT = 1883 +MQTT_USER = "test" +MQTT_PASSWORD = "1234" + +MAX_SERVOS = 4 +MAX_SENSORS = 8 # Количество каналов VL53L0X + +# Цвета +COLOR_OK = "#28a745" +COLOR_ERR = "#dc3545" +COLOR_OFF = "#555555" +COLOR_ACTIVE = "#00d2ff" +COLOR_PENDING = "#ffaa00" +COLOR_WARN = "#ff9900" # Для out_of_range + +class MotorSCADA: + def __init__(self): + ctk.set_appearance_mode("Dark") + ctk.set_default_color_theme("blue") + + self.root = ctk.CTk() + self.root.title("🚀 Motor, Servo & Sensor SCADA") + self.root.geometry("1200x900") + self.root.minsize(1100, 800) + + # Флаги ожидания + self.driver_pending = False + self.tmc_pending = False + self.servo_pending = {i: False for i in range(MAX_SERVOS)} + self.sensor_pending = {i: False for i in range(MAX_SENSORS)} + + self.setup_gui() + self.setup_mqtt() + + def setup_gui(self): + # --- Шапка --- + header = ctk.CTkFrame(self.root, height=60) + header.pack(fill="x", padx=20, pady=(20, 10)) + header.pack_propagate(False) + ctk.CTkLabel(header, text="Motor, Servo & Sensor SCADA", font=ctk.CTkFont(size=24, weight="bold")).pack(side="left", padx=20) + self.lbl_status = ctk.CTkLabel(header, text="● Отключено", text_color=COLOR_ERR, font=ctk.CTkFont(size=16, weight="bold")) + self.lbl_status.pack(side="right", padx=20) + + # --- Вкладки --- + self.tabview = ctk.CTkTabview(self.root) + self.tabview.pack(fill="both", expand=True, padx=20, pady=10) + + self.tab_motor = self.tabview.add("Шаговый двигатель (TMC2209)") + self.tab_servo = self.tabview.add(f"Сервоприводы (0-{MAX_SERVOS-1})") + self.tab_sensor = self.tabview.add(f"Датчики VL53L0X (0-{MAX_SENSORS-1})") + + self.create_motor_tab(self.tab_motor) + self.create_servo_tab(self.tab_servo) + self.create_sensor_tab(self.tab_sensor) + + # ================= ВКЛАДКА ШАГОВОГО ДВИГАТЕЛЯ ================= + # (Код для мотора остался без изменений, чтобы не раздувать ответ, + # но в реальном файле он должен быть здесь полностью) + def create_motor_tab(self, parent): + grid = ctk.CTkFrame(parent, fg_color="transparent") + grid.pack(fill="both", expand=True) + grid.grid_columnconfigure((0, 1, 2), weight=1, uniform="col") + grid.grid_rowconfigure(0, weight=1) + self.create_control_frame(grid) + self.create_modes_frame(grid) + self.create_telemetry_frame(grid) + + def create_control_frame(self, parent): + frame = ctk.CTkFrame(parent) + frame.grid(row=0, column=0, sticky="nsew", padx=(0, 10)) + ctk.CTkLabel(frame, text="⚙️ Управление", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 20)) + ctk.CTkLabel(frame, text="Целевой RPM:").pack(anchor="w", padx=20) + rpm_frame = ctk.CTkFrame(frame, fg_color="transparent") + rpm_frame.pack(fill="x", padx=20, pady=5) + self.sld_rpm = ctk.CTkSlider(rpm_frame, from_=-1000, to=1000, command=self.on_rpm_slider_change) + self.sld_rpm.pack(side="left", fill="x", expand=True, padx=(0, 10)) + self.ent_rpm = ctk.CTkEntry(rpm_frame, width=80, justify="right") + self.ent_rpm.insert(0, "0"); self.ent_rpm.pack(side="left", padx=(0, 10)) + self.ent_rpm.bind("", self.on_rpm_entry_apply); self.ent_rpm.bind("", self.on_rpm_entry_apply) + self.lbl_rpm_val = ctk.CTkLabel(rpm_frame, text="0", width=50); self.lbl_rpm_val.pack(side="right") + + ctk.CTkLabel(frame, text="Ток (%):").pack(anchor="w", padx=20, pady=(15,0)) + cur_frame = ctk.CTkFrame(frame, fg_color="transparent") + cur_frame.pack(fill="x", padx=20, pady=5) + self.sld_current = ctk.CTkSlider(cur_frame, from_=0, to=100, command=self.on_current_change) + self.sld_current.pack(side="left", fill="x", expand=True) + self.lbl_cur_val = ctk.CTkLabel(cur_frame, text="50", width=50); self.lbl_cur_val.pack(side="right", padx=(10, 0)) + + ctk.CTkLabel(frame, text="StallGuard (0-255):").pack(anchor="w", padx=20, pady=(15,0)) + self.ent_sg = ctk.CTkEntry(frame, width=100); self.ent_sg.insert(0, "0") + self.ent_sg.pack(anchor="w", padx=20, pady=5) + ctk.CTkButton(frame, text="Применить SG", width=150, command=self.on_sg_apply).pack(pady=5) + + ctk.CTkLabel(frame, text="Микрошаги:").pack(anchor="w", padx=20, pady=(15,0)) + self.opt_msteps = ctk.CTkOptionMenu(frame, values=["1", "2", "4", "8", "16", "32", "64", "128", "256"], command=self.on_msteps_change) + self.opt_msteps.set("16"); self.opt_msteps.pack(anchor="w", padx=20, pady=5) + ctk.CTkButton(frame, text="Сбросить счетчик шагов", fg_color="#dc3545", hover_color="#b02a37", command=self.on_reset_steps).pack(pady=20) + + def create_modes_frame(self, parent): + frame = ctk.CTkFrame(parent) + frame.grid(row=0, column=1, sticky="nsew", padx=10) + ctk.CTkLabel(frame, text="🔌 Режимы и Включение", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 20)) + self.sw_driver, self.led_driver_fb = self.create_switch_with_feedback(frame, "Аппаратное вкл. (Driver EN)", self.on_driver_change) + self.sw_tmc_enable, self.led_tmc_fb = self.create_switch_with_feedback(frame, "Программное вкл. (TMC Chip)", self.on_tmc_enable_change) + ctk.CTkFrame(frame, height=2, fg_color="#4a4a6a").pack(fill="x", padx=20, pady=15) + self.sw_stealth = self.create_switch(frame, "StealthChop (Тихий)", self.on_stealth_change) + self.sw_cool = self.create_switch(frame, "CoolStep (Энергосбер.)", self.on_cool_change) + + def create_telemetry_frame(self, parent): + frame = ctk.CTkFrame(parent) + frame.grid(row=0, column=2, sticky="nsew", padx=(10, 0)) + ctk.CTkLabel(frame, text="📊 Телеметрия и Статусы", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 10)) + tel_frame = ctk.CTkFrame(frame); tel_frame.pack(fill="x", padx=10, pady=5) + self.lbl_fb_rpm = self.create_telemetry_row(tel_frame, "Текущий RPM:") + self.lbl_fb_steps = self.create_telemetry_row(tel_frame, "Всего шагов:") + self.lbl_fb_run = self.create_telemetry_row(tel_frame, "Вращение:") + self.lbl_fb_sg = self.create_telemetry_row(tel_frame, "SG Result:") + self.lbl_fb_interstep = self.create_telemetry_row(tel_frame, "Interstep:") + self.lbl_fb_cscale = self.create_telemetry_row(tel_frame, "Current Scaling:") + ctk.CTkLabel(frame, text="🚨 Ошибки и Флаги", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(15, 5)) + stat_frame = ctk.CTkFrame(frame); stat_frame.pack(fill="x", padx=10, pady=5) + self.led_over_temp = self.create_led_row(stat_frame, "Перегрев:") + self.led_short_gnd = self.create_led_row(stat_frame, "КЗ на землю:") + self.led_open_load = self.create_led_row(stat_frame, "Обрыв нагрузки:") + self.led_stealth_act = self.create_led_row(stat_frame, "StealthChop активен:") + self.led_standstill = self.create_led_row(stat_frame, "Остановка:") + + # ================= ВКЛАДКА СЕРВОПРИВОДОВ ================= + def create_servo_tab(self, parent): + grid = ctk.CTkFrame(parent, fg_color="transparent") + grid.pack(fill="both", expand=True, padx=10, pady=10) + grid.grid_columnconfigure((0, 1), weight=1, uniform="col") + grid.grid_rowconfigure((0, 1), weight=1, uniform="row") + self.servo_ui = {} + for i in range(MAX_SERVOS): + row, col = divmod(i, 2) + frame = ctk.CTkFrame(grid) + frame.grid(row=row, column=col, sticky="nsew", padx=10, pady=10) + self.servo_ui[i] = self.create_servo_card(frame, i) + + def create_servo_card(self, parent, channel): + ui = {} + ctk.CTkLabel(parent, text=f"🦾 Сервопривод #{channel}", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(10, 10)) + ctk.CTkLabel(parent, text="Угол (0-180°):").pack(anchor="w", padx=20) + ang_frame = ctk.CTkFrame(parent, fg_color="transparent"); ang_frame.pack(fill="x", padx=20, pady=5) + sld = ctk.CTkSlider(ang_frame, from_=0, to=180, command=lambda val, ch=channel: self.on_servo_ang_slider(ch, val)) + sld.pack(side="left", fill="x", expand=True, padx=(0, 10)); sld.set(90) + ent = ctk.CTkEntry(ang_frame, width=60, justify="right"); ent.insert(0, "90"); ent.pack(side="left", padx=(0, 10)) + ent.bind("", lambda event, ch=channel: self.on_servo_ang_entry(ch, event)) + ent.bind("", lambda event, ch=channel: self.on_servo_ang_entry(ch, event)) + lbl_val = ctk.CTkLabel(ang_frame, text="90", width=40); lbl_val.pack(side="right") + ui['slider_ang'], ui['entry_ang'], ui['lbl_ang_val'] = sld, ent, lbl_val + + sw, led_fb = self.create_switch_with_feedback(parent, "Включить серво", lambda ch=channel: self.on_servo_enable_change(ch)) + ui['switch_en'], ui['led_fb'] = sw, led_fb + + tel_frame = ctk.CTkFrame(parent); tel_frame.pack(fill="x", padx=10, pady=15) + ui['lbl_fb_ang'] = self.create_telemetry_row(tel_frame, "Текущий угол:") + stat_frame = ctk.CTkFrame(parent); stat_frame.pack(fill="x", padx=10, pady=5) + ui['led_status'] = self.create_led_row(stat_frame, "Статус:") + return ui + + # ================= ВКЛАДКА ДАТЧИКОВ VL53L0X ================= + def create_sensor_tab(self, parent): + main_frame = ctk.CTkFrame(parent, fg_color="transparent") + main_frame.pack(fill="both", expand=True, padx=10, pady=10) + + # --- Глобальное управление --- + global_frame = ctk.CTkFrame(main_frame) + global_frame.pack(fill="x", padx=10, pady=(0, 10)) + ctk.CTkLabel(global_frame, text="📏 Глобальные настройки VL53L0X", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(10, 5)) + + info_frame = ctk.CTkFrame(global_frame, fg_color="transparent") + info_frame.pack(fill="x", padx=20, pady=10) + + self.lbl_sensor_mode = self.create_telemetry_row(info_frame, "Режим:") + self.lbl_sensor_mode_id = self.create_telemetry_row(info_frame, "ID режима:") + self.lbl_sensor_max_range = self.create_telemetry_row(info_frame, "Макс. дальность (мм):") + + ctrl_frame = ctk.CTkFrame(global_frame, fg_color="transparent") + ctrl_frame.pack(fill="x", padx=20, pady=(0, 10)) + + ctk.CTkLabel(ctrl_frame, text="Выбрать режим:").pack(side="left", padx=(0, 10)) + # Предполагаем, что режимов от 0 до 4 (Default, HighAccuracy, LongRange, HighSpeed) + self.opt_sensor_mode = ctk.CTkOptionMenu(ctrl_frame, values=["0", "1", "2", "3", "4"], width=100, command=self.on_sensor_mode_change) + self.opt_sensor_mode.set("0"); self.opt_sensor_mode.pack(side="left", padx=(0, 20)) + + ctk.CTkButton(ctrl_frame, text="🔄 Принудительно обновить все", fg_color="#007bff", hover_color="#0056b3", command=self.on_sensor_publish_all).pack(side="right") + + # --- Сетка каналов (Scrollable) --- + scroll_frame = ctk.CTkScrollableFrame(main_frame) + scroll_frame.pack(fill="both", expand=True, padx=10, pady=10) + scroll_frame.grid_columnconfigure((0, 1, 2, 3), weight=1, uniform="col") + + self.sensor_ui = {} + for i in range(MAX_SENSORS): + row, col = divmod(i, 4) + frame = ctk.CTkFrame(scroll_frame) + frame.grid(row=row, column=col, sticky="nsew", padx=5, pady=5) + self.sensor_ui[i] = self.create_sensor_card(frame, i) + + def create_sensor_card(self, parent, channel): + ui = {} + ctk.CTkLabel(parent, text=f"📡 Канал #{channel}", font=ctk.CTkFont(size=14, weight="bold")).pack(pady=(5, 5)) + + # Включение и калибровка + sw, led_fb = self.create_switch_with_feedback(parent, "Включить", lambda ch=channel: self.on_sensor_enable_change(ch)) + ui['switch_en'], ui['led_fb'] = sw, led_fb + + cal_stat_frame = ctk.CTkFrame(parent, fg_color="transparent") + cal_stat_frame.pack(fill="x", padx=10, pady=5) + ctk.CTkLabel(cal_stat_frame, text="Калибровка:").pack(side="left") + ui['led_calibrated'] = ctk.CTkLabel(cal_stat_frame, text="●", font=ctk.CTkFont(size=16), text_color=COLOR_OFF) + ui['led_calibrated'].pack(side="right") + + # Поля калибровки + cal_ctrl_frame = ctk.CTkFrame(parent, fg_color="transparent") + cal_ctrl_frame.pack(fill="x", padx=10, pady=5) + + ctk.CTkLabel(cal_ctrl_frame, text="Ближняя (мм):").pack(anchor="w") + ent_near = ctk.CTkEntry(cal_ctrl_frame, width=60, justify="right"); ent_near.insert(0, "50"); ent_near.pack(side="left", padx=(0, 5)) + btn_start = ctk.CTkButton(cal_ctrl_frame, text="Старт", width=60, height=28, command=lambda ch=channel, e=ent_near: self.on_sensor_cal_start(ch, e)) + btn_start.pack(side="right") + + ctk.CTkLabel(cal_ctrl_frame, text="Дальняя (мм):").pack(anchor="w", pady=(5,0)) + ent_far = ctk.CTkEntry(cal_ctrl_frame, width=60, justify="right"); ent_far.insert(0, "500"); ent_far.pack(side="left", padx=(0, 5), pady=(5,0)) + btn_finish = ctk.CTkButton(cal_ctrl_frame, text="Финиш", width=60, height=28, command=lambda ch=channel, e=ent_far: self.on_sensor_cal_finish(ch, e)) + btn_finish.pack(side="right") + + ctk.CTkButton(parent, text="Сбросить калибровку", fg_color="#6c757d", hover_color="#5a6268", height=28, command=lambda ch=channel: self.on_sensor_clear_cal(ch)).pack(pady=5) + + ui['ent_near'], ui['ent_far'] = ent_near, ent_far + + # Телеметрия + tel_frame = ctk.CTkFrame(parent) + tel_frame.pack(fill="x", padx=5, pady=5) + ui['lbl_dist'] = self.create_telemetry_row(tel_frame, "Дист. (мм):") + ui['lbl_raw'] = self.create_telemetry_row(tel_frame, "Сырое (мм):") + + return ui + + # ================= ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ GUI ================= + def create_switch(self, parent, text, command): + frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", padx=10, pady=5) + ctk.CTkLabel(frame, text=text).pack(side="left") + switch = ctk.CTkSwitch(frame, text="", command=command); switch.pack(side="right") + return switch + + def create_switch_with_feedback(self, parent, text, command): + frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", padx=10, pady=5) + ctk.CTkLabel(frame, text=text).pack(side="left") + feedback_led = ctk.CTkLabel(frame, text="●", font=ctk.CTkFont(size=18), text_color=COLOR_OFF) + feedback_led.pack(side="right", padx=(10, 0)) + switch = ctk.CTkSwitch(frame, text="", command=command); switch.pack(side="right", padx=(10, 0)) + return switch, feedback_led + + def create_telemetry_row(self, parent, text): + frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", pady=2) + ctk.CTkLabel(frame, text=text, anchor="w", font=ctk.CTkFont(size=12)).pack(side="left") + val_lbl = ctk.CTkLabel(frame, text="-", font=ctk.CTkFont(weight="bold", size=12), text_color=COLOR_ACTIVE, anchor="e") + val_lbl.pack(side="right") + return val_lbl + + def create_led_row(self, parent, text): + frame = ctk.CTkFrame(parent, fg_color="transparent"); frame.pack(fill="x", pady=2) + ctk.CTkLabel(frame, text=text, anchor="w", font=ctk.CTkFont(size=12)).pack(side="left") + led_lbl = ctk.CTkLabel(frame, text="●", font=ctk.CTkFont(size=16), text_color=COLOR_OFF) + led_lbl.pack(side="right") + return led_lbl + + # ================= MQTT ================= + def setup_mqtt(self): + self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="python_scada") + self.client.username_pw_set(MQTT_USER, MQTT_PASSWORD) + self.client.on_connect = self.on_mqtt_connect + self.client.on_disconnect = self.on_mqtt_disconnect + self.client.on_message = self.on_mqtt_message + try: + self.client.connect(MQTT_BROKER, MQTT_PORT, 60) + self.client.loop_start() + except Exception as e: + print(f"Ошибка подключения: {e}") + self.update_status(False) + + def on_mqtt_connect(self, client, userdata, flags, reason_code, properties): + if reason_code == 0: + self.root.after(0, self.update_status, True) + client.subscribe("motor/feedback/#") + client.subscribe("servo/+/feedback/#") + client.subscribe("sensor/feedback/#") # Подписка на датчики + else: + self.root.after(0, self.update_status, False) + + def on_mqtt_disconnect(self, client, userdata, flags, reason_code, properties): + self.root.after(0, self.update_status, False) + + def on_mqtt_message(self, client, userdata, msg): + self.root.after(0, self.process_feedback, msg.topic, msg.payload.decode('utf-8')) + + def process_feedback(self, topic, val): + parts = topic.split('/') + + # --- MOTOR --- + if parts[0] == 'motor' and parts[1] == 'feedback': + if topic == "motor/feedback/rpm": self.lbl_fb_rpm.configure(text=val) + elif topic == "motor/feedback/totalsteps": self.lbl_fb_steps.configure(text=val) + elif topic == "motor/feedback/is_run": + is_run = val == "true" + self.lbl_fb_run.configure(text="Да" if is_run else "Нет", text_color=COLOR_OK if is_run else COLOR_ERR) + elif topic == "motor/feedback/tmc/current_percent": self.lbl_cur_val.configure(text=val); self.sld_current.set(int(val)) + elif topic == "motor/feedback/tmc/microsteps": self.opt_msteps.set(val) + elif topic == "motor/feedback/tmc/sg_result": self.lbl_fb_sg.configure(text=val) + elif topic == "motor/feedback/tmc/interstep_duration": self.lbl_fb_interstep.configure(text=val) + elif topic == "motor/feedback/tmc/status/current_scaling": self.lbl_fb_cscale.configure(text=val) + elif topic == "motor/feedback/tmc/status/over_temp": self.update_led(self.led_over_temp, val, True) + elif topic == "motor/feedback/tmc/status/short_to_ground": self.update_led(self.led_short_gnd, val, True) + elif topic == "motor/feedback/tmc/status/open_load": self.update_led(self.led_open_load, val, True) + elif topic == "motor/feedback/tmc/status/stealth_chop_active": self.update_led(self.led_stealth_act, val, False) + elif topic == "motor/feedback/tmc/status/standstill": self.update_led(self.led_standstill, val, False) + elif topic == "motor/feedback/driver/status": + is_on = val == "on"; self.driver_pending = False + if self.sw_driver.get() != is_on: self.sw_driver.select() if is_on else self.sw_driver.deselect() + self.led_driver_fb.configure(text_color=COLOR_OK if is_on else COLOR_OFF) + elif topic == "motor/feedback/tmc/status": + is_on = val == "on"; self.tmc_pending = False + if self.sw_tmc_enable.get() != is_on: self.sw_tmc_enable.select() if is_on else self.sw_tmc_enable.deselect() + self.led_tmc_fb.configure(text_color=COLOR_OK if is_on else COLOR_OFF) + + # --- SERVO --- + elif parts[0] == 'servo' and len(parts) == 4 and parts[2] == 'feedback': + try: + ch = int(parts[1]); param = parts[3] + if ch in self.servo_ui: + ui = self.servo_ui[ch] + if param == 'angle': ui['lbl_fb_ang'].configure(text=val) + elif param == 'status': + is_on = val == "on"; self.servo_pending[ch] = False + if ui['switch_en'].get() != is_on: ui['switch_en'].select() if is_on else ui['switch_en'].deselect() + ui['led_fb'].configure(text_color=COLOR_OK if is_on else COLOR_OFF) + self.update_led(ui['led_status'], val, False) + except ValueError: pass + + # --- SENSOR --- + elif parts[0] == 'sensor' and parts[1] == 'feedback': + if len(parts) == 3: # Глобальные sensor/feedback/mode... + param = parts[2] + if param == 'mode': self.lbl_sensor_mode.configure(text=val) + elif param == 'mode_id': self.lbl_sensor_mode_id.configure(text=val); self.opt_sensor_mode.set(val) + elif param == 'max_range': self.lbl_sensor_max_range.configure(text=val) + elif len(parts) == 4: # Канальные sensor/feedback/{ch}/... + try: + ch = int(parts[2]); param = parts[3] + if ch in self.sensor_ui: + ui = self.sensor_ui[ch] + if param == 'status': + is_on = val == "on"; self.sensor_pending[ch] = False + if ui['switch_en'].get() != is_on: ui['switch_en'].select() if is_on else ui['switch_en'].deselect() + ui['led_fb'].configure(text_color=COLOR_OK if is_on else COLOR_OFF) + elif param == 'calibrated': + is_cal = val == "true" + ui['led_calibrated'].configure(text_color=COLOR_OK if is_cal else COLOR_OFF) + elif param == 'distance': + if val == "out_of_range": + ui['lbl_dist'].configure(text="Вне диапазона", text_color=COLOR_WARN) + else: + ui['lbl_dist'].configure(text=val, text_color=COLOR_ACTIVE) + elif param == 'raw': + ui['lbl_raw'].configure(text=val, text_color=COLOR_ACTIVE) + except ValueError: pass + + # ================= ОБРАБОТЧИКИ СОБЫТИЙ ================= + def publish(self, topic, payload): + if self.client.is_connected(): self.client.publish(topic, str(payload), qos=1) + + def update_status(self, is_online): + self.lbl_status.configure(text="● Подключено" if is_online else "● Отключено", text_color=COLOR_OK if is_online else COLOR_ERR) + + def update_led(self, label, val, is_error): + is_true = val in ["true", "1", "on"] + label.configure(text_color=COLOR_ERR if (is_true and is_error) else (COLOR_OK if is_true else COLOR_OFF)) + + # --- Motor Handlers --- + def on_rpm_slider_change(self, value): + int_val = int(value); self.ent_rpm.delete(0, ctk.END); self.ent_rpm.insert(0, str(int_val)) + self.lbl_rpm_val.configure(text=str(int_val)); self.publish("motor/control/rpm", int_val) + def on_rpm_entry_apply(self, event=None): + try: + int_val = max(-1000, min(1000, int(self.ent_rpm.get()))) + self.sld_rpm.set(int_val); self.lbl_rpm_val.configure(text=str(int_val)); self.publish("motor/control/rpm", int_val) + except ValueError: self.ent_rpm.delete(0, ctk.END); self.ent_rpm.insert(0, str(int(self.sld_rpm.get()))) + def on_current_change(self, value): + int_val = int(value); self.lbl_cur_val.configure(text=str(int_val)); self.publish("motor/control/tmc/current_percent", int_val) + def on_sg_apply(self): + val = self.ent_sg.get() + if val.isdigit() and 0 <= int(val) <= 255: self.publish("motor/control/tmc/stallguard", int(val)) + def on_msteps_change(self, choice): self.publish("motor/control/tmc/microsteps", int(choice)) + def on_reset_steps(self): self.publish("motor/control/totalsteps/reset", "1") + def on_driver_change(self): + is_on = self.sw_driver.get(); self.driver_pending = True; self.led_driver_fb.configure(text_color=COLOR_PENDING) + self.publish("motor/control/driver", "on" if is_on else "off") + def on_tmc_enable_change(self): + is_on = self.sw_tmc_enable.get(); self.tmc_pending = True; self.led_tmc_fb.configure(text_color=COLOR_PENDING) + self.publish("motor/control/tmc/enable", "on" if is_on else "off") + def on_stealth_change(self): self.publish("motor/control/tmc/stealthchop", "on" if self.sw_stealth.get() else "off") + def on_cool_change(self): self.publish("motor/control/tmc/coolstep", "on" if self.sw_cool.get() else "off") + + # --- Servo Handlers --- + def on_servo_ang_slider(self, channel, value): + int_val = int(value); ui = self.servo_ui[channel] + ui['entry_ang'].delete(0, ctk.END); ui['entry_ang'].insert(0, str(int_val)) + ui['lbl_ang_val'].configure(text=str(int_val)); self.publish(f"servo/control/{channel}/angle", int_val) + def on_servo_ang_entry(self, channel, event=None): + ui = self.servo_ui[channel] + try: + int_val = max(0, min(180, int(ui['entry_ang'].get()))) + ui['slider_ang'].set(int_val); ui['lbl_ang_val'].configure(text=str(int_val)); self.publish(f"servo/control/{channel}/angle", int_val) + except ValueError: ui['entry_ang'].delete(0, ctk.END); ui['entry_ang'].insert(0, str(int(ui['slider_ang'].get()))) + def on_servo_enable_change(self, channel): + ui = self.servo_ui[channel]; is_on = ui['switch_en'].get() + self.servo_pending[channel] = True; ui['led_fb'].configure(text_color=COLOR_PENDING) + self.publish(f"servo/control/{channel}/enable", "on" if is_on else "off") + + # --- Sensor Handlers --- + def on_sensor_mode_change(self, choice): + self.publish("sensor/control/mode", int(choice)) + + def on_sensor_publish_all(self): + self.publish("sensor/control/publish_all", "1") + + def on_sensor_enable_change(self, channel): + ui = self.sensor_ui[channel]; is_on = ui['switch_en'].get() + self.sensor_pending[channel] = True; ui['led_fb'].configure(text_color=COLOR_PENDING) + self.publish(f"sensor/control/enable/{channel}", "on" if is_on else "off") + + def on_sensor_cal_start(self, channel, entry_widget): + val = entry_widget.get() + if val.isdigit(): + self.publish(f"sensor/control/calibrate/start/{channel}", int(val)) + + def on_sensor_cal_finish(self, channel, entry_widget): + val = entry_widget.get() + if val.isdigit(): + self.publish(f"sensor/control/calibrate/finish/{channel}", int(val)) + + def on_sensor_clear_cal(self, channel): + self.publish(f"sensor/control/clear_cal/{channel}", "1") + + def run(self): + self.root.mainloop() + self.client.loop_stop() + self.client.disconnect() + +if __name__ == "__main__": + app = MotorSCADA() + app.run() \ No newline at end of file diff --git a/backend_control/img/gui_interface.png b/backend_control/img/gui_interface.png new file mode 100644 index 0000000..edff0dc Binary files /dev/null and b/backend_control/img/gui_interface.png differ diff --git a/backend_control/img/servo_interface.png b/backend_control/img/servo_interface.png new file mode 100644 index 0000000..38a4737 Binary files /dev/null and b/backend_control/img/servo_interface.png differ diff --git a/backend_control/requirements.txt b/backend_control/requirements.txt new file mode 100644 index 0000000..fb9048c --- /dev/null +++ b/backend_control/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt +customtkinter diff --git a/kicad/ozon/.gitignore b/kicad/ozon/.gitignore new file mode 100644 index 0000000..6654d5c --- /dev/null +++ b/kicad/ozon/.gitignore @@ -0,0 +1,2 @@ +*.lck +.history \ No newline at end of file diff --git a/kicad/ozon/.history b/kicad/ozon/.history new file mode 160000 index 0000000..2a8a462 --- /dev/null +++ b/kicad/ozon/.history @@ -0,0 +1 @@ +Subproject commit 2a8a4622270e76b1f98b90dcc1ea27bec04272ef diff --git a/kicad/ozon/ozon.kicad_pcb b/kicad/ozon/ozon.kicad_pcb new file mode 100644 index 0000000..ccd82d6 --- /dev/null +++ b/kicad/ozon/ozon.kicad_pcb @@ -0,0 +1,2 @@ +(kicad_pcb (version 20260206) (generator "pcbnew") (generator_version "10.0") +) \ No newline at end of file diff --git a/kicad/ozon/ozon.kicad_prl b/kicad/ozon/ozon.kicad_prl new file mode 100644 index 0000000..6e386ce --- /dev/null +++ b/kicad/ozon/ozon.kicad_prl @@ -0,0 +1,105 @@ +{ + "board": { + "active_layer": 0, + "active_layer_preset": "", + "auto_track_width": true, + "hidden_netclasses": [], + "hidden_nets": [], + "high_contrast_mode": 0, + "net_color_mode": 1, + "opacity": { + "images": 0.6, + "pads": 1.0, + "shapes": 1.0, + "tracks": 1.0, + "vias": 1.0, + "zones": 0.6 + }, + "prototype_zone_fills": false, + "selection_filter": { + "dimensions": true, + "footprints": true, + "graphics": true, + "keepouts": true, + "lockedItems": false, + "otherItems": true, + "pads": true, + "text": true, + "tracks": true, + "vias": true, + "zones": true + }, + "visible_items": [ + "vias", + "footprint_text", + "footprint_anchors", + "ratsnest", + "grid", + "footprints_front", + "footprints_back", + "footprint_values", + "footprint_references", + "tracks", + "drc_errors", + "drawing_sheet", + "bitmaps", + "pads", + "zones", + "drc_warnings", + "drc_exclusions", + "locked_item_shadows", + "conflict_shadows", + "shapes", + "board_outline_area", + "ly_points" + ], + "visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff", + "zone_display_mode": 0 + }, + "git": { + "integration_disabled": false, + "repo_type": "", + "repo_username": "", + "ssh_key": "" + }, + "meta": { + "filename": "ozon.kicad_prl", + "version": 5 + }, + "net_inspector_panel": { + "col_hidden": [], + "col_order": [], + "col_widths": [], + "custom_group_rules": [], + "expanded_rows": [], + "filter_by_net_name": true, + "filter_by_netclass": true, + "filter_text": "", + "group_by_constraint": false, + "group_by_netclass": false, + "show_time_domain_details": false, + "show_unconnected_nets": false, + "show_zero_pad_nets": false, + "sort_ascending": true, + "sorting_column": -1 + }, + "open_jobsets": [], + "project": { + "files": [] + }, + "schematic": { + "hierarchy_collapsed": [], + "selection_filter": { + "graphics": true, + "images": true, + "labels": true, + "lockedItems": false, + "otherItems": true, + "pins": true, + "ruleAreas": true, + "symbols": true, + "text": true, + "wires": true + } + } +} diff --git a/kicad/ozon/ozon.kicad_pro b/kicad/ozon/ozon.kicad_pro new file mode 100644 index 0000000..494cd6b --- /dev/null +++ b/kicad/ozon/ozon.kicad_pro @@ -0,0 +1,435 @@ +{ + "board": { + "3dviewports": [], + "ipc2581": { + "bom_rev": "", + "dist": "", + "distpn": "", + "internal_id": "", + "mfg": "", + "mpn": "", + "sch_revision": "" + }, + "layer_pairs": [], + "layer_presets": [], + "viewports": [] + }, + "boards": [], + "component_class_settings": { + "assignments": [], + "meta": { + "version": 0 + }, + "sheet_component_classes": { + "enabled": false + } + }, + "cvpcb": { + "equivalence_files": [] + }, + "erc": { + "erc_exclusions": [], + "meta": { + "version": 0 + }, + "pin_map": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 2 + ], + [ + 0, + 2, + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 2, + 2, + 2 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 2 + ], + [ + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 1, + 1, + 2 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 2 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2 + ], + [ + 1, + 1, + 1, + 1, + 1, + 0, + 1, + 1, + 1, + 1, + 1, + 2 + ], + [ + 0, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 2 + ], + [ + 0, + 2, + 1, + 2, + 0, + 0, + 1, + 0, + 2, + 2, + 2, + 2 + ], + [ + 0, + 2, + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 0, + 2 + ], + [ + 0, + 2, + 1, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 0, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ] + ], + "rule_severities": { + "bus_definition_conflict": "error", + "bus_entry_needed": "error", + "bus_to_bus_conflict": "error", + "bus_to_net_conflict": "error", + "different_unit_footprint": "error", + "different_unit_net": "error", + "duplicate_reference": "error", + "duplicate_sheet_names": "error", + "endpoint_off_grid": "warning", + "extra_units": "error", + "field_name_whitespace": "warning", + "footprint_filter": "ignore", + "footprint_link_issues": "warning", + "four_way_junction": "ignore", + "ground_pin_not_ground": "warning", + "hier_label_mismatch": "error", + "isolated_pin_label": "warning", + "label_dangling": "error", + "label_multiple_wires": "warning", + "lib_symbol_issues": "warning", + "lib_symbol_mismatch": "warning", + "missing_bidi_pin": "warning", + "missing_input_pin": "warning", + "missing_power_pin": "error", + "missing_unit": "warning", + "multiple_net_names": "warning", + "net_not_bus_member": "warning", + "no_connect_connected": "warning", + "no_connect_dangling": "warning", + "pin_not_connected": "error", + "pin_not_driven": "error", + "pin_to_pin": "warning", + "power_pin_not_driven": "error", + "same_local_global_label": "warning", + "similar_label_and_power": "warning", + "similar_labels": "warning", + "similar_power": "warning", + "simulation_model_issue": "ignore", + "single_global_label": "ignore", + "stacked_pin_name": "warning", + "unannotated": "error", + "unconnected_wire_endpoint": "warning", + "undefined_netclass": "error", + "unit_value_mismatch": "error", + "unresolved_variable": "error", + "wire_dangling": "error" + } + }, + "libraries": { + "pinned_footprint_libs": [], + "pinned_symbol_libs": [] + }, + "meta": { + "filename": "ozon.kicad_pro", + "version": 3 + }, + "net_settings": { + "classes": [ + { + "bus_width": 12, + "clearance": 0.2, + "diff_pair_gap": 0.25, + "diff_pair_via_gap": 0.25, + "diff_pair_width": 0.2, + "line_style": 0, + "microvia_diameter": 0.3, + "microvia_drill": 0.1, + "name": "Default", + "pcb_color": "rgba(0, 0, 0, 0.000)", + "priority": 2147483647, + "schematic_color": "rgba(0, 0, 0, 0.000)", + "track_width": 0.2, + "tuning_profile": "", + "via_diameter": 0.6, + "via_drill": 0.3, + "wire_width": 6 + } + ], + "meta": { + "version": 5 + }, + "net_colors": null, + "netclass_assignments": null, + "netclass_patterns": [] + }, + "pcbnew": { + "last_paths": { + "idf": "", + "netlist": "", + "plot": "", + "specctra_dsn": "", + "vrml": "" + }, + "page_layout_descr_file": "" + }, + "schematic": { + "annotate_start_num": 0, + "annotation": { + "method": 0, + "sort_order": 0 + }, + "bom_export_filename": "${PROJECTNAME}.csv", + "bom_fmt_presets": [], + "bom_fmt_settings": { + "field_delimiter": ",", + "keep_line_breaks": false, + "keep_tabs": false, + "name": "CSV", + "ref_delimiter": ",", + "ref_range_delimiter": "", + "string_delimiter": "\"" + }, + "bom_presets": [], + "bom_settings": { + "exclude_dnp": false, + "fields_ordered": [ + { + "group_by": false, + "label": "Reference", + "name": "Reference", + "show": true + }, + { + "group_by": false, + "label": "Qty", + "name": "${QUANTITY}", + "show": true + }, + { + "group_by": true, + "label": "Value", + "name": "Value", + "show": true + }, + { + "group_by": true, + "label": "DNP", + "name": "${DNP}", + "show": true + }, + { + "group_by": true, + "label": "Exclude from BOM", + "name": "${EXCLUDE_FROM_BOM}", + "show": true + }, + { + "group_by": true, + "label": "Exclude from Board", + "name": "${EXCLUDE_FROM_BOARD}", + "show": true + }, + { + "group_by": true, + "label": "Footprint", + "name": "Footprint", + "show": true + }, + { + "group_by": false, + "label": "Datasheet", + "name": "Datasheet", + "show": true + } + ], + "filter_string": "", + "group_symbols": true, + "include_excluded_from_bom": true, + "name": "Default Editing", + "sort_asc": true, + "sort_field": "Обозначение" + }, + "bus_aliases": {}, + "connection_grid_size": 50.0, + "drawing": { + "dashed_lines_dash_length_ratio": 12.0, + "dashed_lines_gap_length_ratio": 3.0, + "default_line_thickness": 6.0, + "default_text_size": 50.0, + "field_names": [], + "hop_over_size_choice": 0, + "intersheets_ref_own_page": false, + "intersheets_ref_prefix": "", + "intersheets_ref_short": false, + "intersheets_ref_show": false, + "intersheets_ref_suffix": "", + "junction_size_choice": 3, + "label_size_ratio": 0.375, + "operating_point_overlay_i_precision": 3, + "operating_point_overlay_i_range": "~A", + "operating_point_overlay_v_precision": 3, + "operating_point_overlay_v_range": "~V", + "overbar_offset_ratio": 1.23, + "pin_symbol_size": 25.0, + "text_offset_ratio": 0.15 + }, + "legacy_lib_dir": "", + "legacy_lib_list": [], + "meta": { + "version": 1 + }, + "page_layout_descr_file": "", + "plot_directory": "", + "reuse_designators": true, + "subpart_first_id": 65, + "subpart_id_separator": 0, + "top_level_sheets": [ + { + "filename": "ozon.kicad_sch", + "name": "Корневой лист", + "uuid": "8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + } + ], + "used_designators": "R1,M1-5,#PWR1-10,U1-6", + "variants": [] + }, + "sheets": [ + [ + "8eb30ef0-8ef0-4108-b9d0-4c3ae489af42", + "Корневой лист" + ] + ], + "text_variables": {}, + "tuning_profiles": { + "meta": { + "version": 0 + }, + "tuning_profiles_impedance_geometric": [] + } +} diff --git a/kicad/ozon/ozon.kicad_sch b/kicad/ozon/ozon.kicad_sch new file mode 100644 index 0000000..88c5397 --- /dev/null +++ b/kicad/ozon/ozon.kicad_sch @@ -0,0 +1,6460 @@ +(kicad_sch + (version 20260306) + (generator "eeschema") + (generator_version "10.0") + (uuid "8eb30ef0-8ef0-4108-b9d0-4c3ae489af42") + (paper "A3") + (lib_symbols + (symbol "Device:R" + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "R" + (at 2.032 0 90) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "R" + (at 0 0 90) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at -1.778 0 90) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Resistor" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "R res resistor" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "R_*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "R_0_1" + (rectangle + (start -1.016 -2.54) + (end 1.016 2.54) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "R_1_1" + (pin passive line + (at 0 3.81 270) + (length 1.27) + (name "" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -3.81 90) + (length 1.27) + (name "" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "Driver_LED:PCA9685PW" + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "U" + (at -12.7 22.225 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "PCA9685PW" + (at 1.27 22.225 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Package_SO:TSSOP-28_4.4x9.7mm_P0.65mm" + (at 0.635 -24.765 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Datasheet" "http://www.nxp.com/docs/en/data-sheet/PCA9685.pdf" + (at -10.16 17.78 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "16-channel 12-bit PWM Fm+ I2C-bus LED controller RGBA TSSOP" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "PWM LED driver I2C TSSOP" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "TSSOP*4.4x9.7mm*P0.65mm*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "PCA9685PW_0_1" + (rectangle + (start -12.7 20.32) + (end 12.7 -22.86) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + ) + (symbol "PCA9685PW_1_1" + (pin input line + (at -17.78 -5.08 0) + (length 5.08) + (name "A0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 -7.62 0) + (length 5.08) + (name "A1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 -10.16 0) + (length 5.08) + (name "A2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 -12.7 0) + (length 5.08) + (name "A3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 -15.24 0) + (length 5.08) + (name "A4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 17.78 180) + (length 5.08) + (name "LED0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 15.24 180) + (length 5.08) + (name "LED1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 12.7 180) + (length 5.08) + (name "LED2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 10.16 180) + (length 5.08) + (name "LED3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "9" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 7.62 180) + (length 5.08) + (name "LED4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "10" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 5.08 180) + (length 5.08) + (name "LED5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "11" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 2.54 180) + (length 5.08) + (name "LED6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 0 180) + (length 5.08) + (name "LED7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "13" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 -27.94 90) + (length 5.08) + (name "VSS" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "14" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -2.54 180) + (length 5.08) + (name "LED8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "15" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -5.08 180) + (length 5.08) + (name "LED9" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "16" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -7.62 180) + (length 5.08) + (name "LED10" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "17" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -10.16 180) + (length 5.08) + (name "LED11" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "18" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -12.7 180) + (length 5.08) + (name "LED12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "19" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -15.24 180) + (length 5.08) + (name "LED13" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "20" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -17.78 180) + (length 5.08) + (name "LED14" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "21" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 17.78 -20.32 180) + (length 5.08) + (name "LED15" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "22" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 10.16 0) + (length 5.08) + (name "~{OE}" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "23" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 -17.78 0) + (length 5.08) + (name "A5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "24" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 12.7 0) + (length 5.08) + (name "EXTCLK" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "25" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -17.78 17.78 0) + (length 5.08) + (name "SCL" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "26" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -17.78 15.24 0) + (length 5.08) + (name "SDA" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "27" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 25.4 270) + (length 5.08) + (name "VDD" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "28" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "Driver_Motor:TMC2209-LA" + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "U" + (at -11.684 26.924 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "TMC2209-LA" + (at 13.208 26.924 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Package_DFN_QFN:VQFN-28-1EP_5x5mm_P0.5mm_EP3.7x3.7mm_ThermalVias" + (at 0 -27.94 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.analog.com/media/en/technical-documentation/data-sheets/TMC2209_datasheet_rev1.09.pdf" + (at 0 -29.972 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "2-phase stepper motor driver, 256 µSteps, 2.8A peak, 2.0A RMS, VS = 4.75..29V, STEP/DIR and UART interface, VQFN-28" + (at 0 -32.004 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "ADI-Trinamic Analog-Devices" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "*VQFN*5x5mm*P0.5mm*EP3.7*3.7*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "TMC2209-LA_0_1" + (rectangle + (start -12.7 25.4) + (end 12.7 -22.86) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + ) + (symbol "TMC2209-LA_1_1" + (pin output line + (at 15.24 -10.16 180) + (length 2.54) + (name "OB2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 -7.62 0) + (length 2.54) + (name "~{EN}" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 -25.4 90) + (length 2.54) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 15.24 10.16 180) + (length 2.54) + (name "CPO" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 15.24 5.08 180) + (length 2.54) + (name "CPI" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 5.08 27.94 270) + (length 2.54) + (name "VCP" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 -2.54 0) + (length 2.54) + (name "SPREAD" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_out line + (at 15.24 20.32 180) + (length 2.54) + (name "5VOUT" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 2.54 0) + (length 2.54) + (name "MS1/AD0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "9" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 0 0) + (length 2.54) + (name "MS2/AD1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "10" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at -15.24 -15.24 0) + (length 2.54) + (name "DIAG" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "11" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at -15.24 -17.78 0) + (length 2.54) + (name "INDEX" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 12.7 0) + (length 2.54) + (name "CLK" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "13" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 7.62 0) + (length 2.54) + (name "~{PD}/UART" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "14" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at -2.54 27.94 270) + (length 2.54) + (name "VCC_IO" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "15" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 20.32 0) + (length 2.54) + (name "STEP" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "16" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 15.24 15.24 180) + (length 2.54) + (name "VREF" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "17" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -25.4 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "18" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 17.78 0) + (length 2.54) + (name "DIR" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "19" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 -10.16 0) + (length 2.54) + (name "STDBY" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "20" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 15.24 -2.54 180) + (length 2.54) + (name "OA2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "21" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 27.94 270) + (length 2.54) + (name "VS" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "22" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 15.24 -15.24 180) + (length 2.54) + (name "BRA" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "23" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 15.24 0 180) + (length 2.54) + (name "OA1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "24" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 2.54 -25.4 90) + (length 2.54) + (name "NC" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "25" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 15.24 -7.62 180) + (length 2.54) + (name "OB1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "26" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 15.24 -17.78 180) + (length 2.54) + (name "BRB" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "27" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 27.94 270) + (length 2.54) + (hide yes) + (name "VS" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "28" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -25.4 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "29" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "Interface_Expansion:TCA9548APWR" + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "U" + (at -7.62 21.59 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "TCA9548APWR" + (at 1.016 21.59 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Package_SO:TSSOP-24_4.4x7.8mm_P0.65mm" + (at 0 -25.4 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://www.ti.com/lit/ds/symlink/tca9548a.pdf" + (at 1.27 6.35 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Low voltage 8-channel I2C switch with reset, TSSOP-24" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "Low voltage 8-channel I2C switch with reset" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "TSSOP*4.4x7.8mm*P0.65mm*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "TCA9548APWR_0_1" + (rectangle + (start -7.62 20.32) + (end 7.62 -22.86) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + ) + (symbol "TCA9548APWR_1_1" + (pin input line + (at -10.16 -12.7 0) + (length 2.54) + (name "A0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -10.16 -10.16 0) + (length 2.54) + (name "A1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -10.16 5.08 0) + (length 2.54) + (name "~{RESET}" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 15.24 180) + (length 2.54) + (name "SD0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 17.78 180) + (length 2.54) + (name "SC0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 10.16 180) + (length 2.54) + (name "SD1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 12.7 180) + (length 2.54) + (name "SC1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 5.08 180) + (length 2.54) + (name "SD2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 7.62 180) + (length 2.54) + (name "SC2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "9" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 0 180) + (length 2.54) + (name "SD3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "10" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 2.54 180) + (length 2.54) + (name "SC3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "11" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 -25.4 90) + (length 2.54) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 -5.08 180) + (length 2.54) + (name "SD4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "13" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 -2.54 180) + (length 2.54) + (name "SC4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "14" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 -10.16 180) + (length 2.54) + (name "SD5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "15" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 -7.62 180) + (length 2.54) + (name "SC5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "16" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 -15.24 180) + (length 2.54) + (name "SD6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "17" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 -12.7 180) + (length 2.54) + (name "SC6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "18" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 10.16 -20.32 180) + (length 2.54) + (name "SD7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "19" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin output line + (at 10.16 -17.78 180) + (length 2.54) + (name "SC7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "20" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -10.16 -7.62 0) + (length 2.54) + (name "A2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "21" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -10.16 17.78 0) + (length 2.54) + (name "SCL" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "22" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -10.16 15.24 0) + (length 2.54) + (name "SDA" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "23" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 22.86 270) + (length 2.54) + (name "VCC" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "24" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "Motor:Motor_Servo" + (pin_names + (offset 0.0254) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "M" + (at -5.08 4.445 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Motor_Servo" + (at -5.08 -4.064 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left top) + ) + ) + (property "Footprint" "" + (at 0 -4.826 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://forums.parallax.com/uploads/attachments/46831/74481.png" + (at 0 -4.826 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Servo Motor (Futaba, HiTec, JR connector)" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "Servo Motor" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "PinHeader*P2.54mm*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "Motor_Servo_0_1" + (polyline + (pts + (xy 2.413 1.778) (xy 1.905 1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.413 1.778) (xy 2.286 1.397) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.413 -1.778) (xy 2.032 -1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.413 -1.778) (xy 2.286 -1.397) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 2.413 -1.778) + (mid 1.2406 0) + (end 2.413 1.778) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 3.175 0) + (radius 0.1778) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 3.175 0) + (radius 1.4224) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 5.08 3.556) (xy -5.08 3.556) (xy -5.08 -3.556) (xy 6.35 -3.556) (xy 6.35 1.524) + ) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 5.969 2.794) + (radius 0.127) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 6.35 4.445) (xy 2.54 1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 6.477 3.302) + (radius 0.127) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 6.35 4.445) + (mid 7.4487 4.2737) + (end 7.62 3.175) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 6.985 3.81) + (radius 0.127) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 7.62 3.175) (xy 4.191 -1.016) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "Motor_Servo_1_1" + (pin passive line + (at -7.62 2.54 0) + (length 2.54) + (name "PWM" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -7.62 0 0) + (length 2.54) + (name "+" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -7.62 -2.54 0) + (length 2.54) + (name "-" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "Motor:Stepper_Motor_bipolar" + (pin_names + (offset 0) + (hide yes) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "M" + (at 3.81 2.54 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Stepper_Motor_bipolar" + (at 3.81 1.27 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left top) + ) + ) + (property "Footprint" "" + (at 0.254 -0.254 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://www.infineon.com/dgdl/Application-Note-TLE8110EE_driving_UniPolarStepperMotor_V1.1.pdf?fileId=db3a30431be39b97011be5d0aa0a00b0" + (at 0.254 -0.254 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "4-wire bipolar stepper motor" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "bipolar stepper motor" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "PinHeader*P2.54mm*Vertical* TerminalBlock* Motor*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "Stepper_Motor_bipolar_0_0" + (polyline + (pts + (xy -1.27 -1.778) (xy -1.27 2.032) (xy 0 -0.508) (xy 1.27 2.032) (xy 1.27 -1.778) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "Stepper_Motor_bipolar_0_1" + (polyline + (pts + (xy -5.08 2.54) (xy -4.445 2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -5.08 -2.54) (xy -4.445 -2.54) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -4.445 2.54) + (mid -3.8127 1.905) + (end -4.445 1.27) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -4.445 1.27) + (mid -3.8127 0.635) + (end -4.445 0) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -4.445 0) + (mid -3.8127 -0.635) + (end -4.445 -1.27) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -4.445 -1.27) + (mid -3.8127 -1.905) + (end -4.445 -2.54) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy -2.54 5.08) (xy -2.54 4.445) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start -1.27 4.445) + (mid -1.905 3.8127) + (end -2.54 4.445) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 0 4.445) + (mid -0.635 3.8127) + (end -1.27 4.445) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (circle + (center 0 0) + (radius 3.2512) + (stroke + (width 0.254) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 1.27 4.445) + (mid 0.635 3.8127) + (end 0 4.445) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (arc + (start 2.54 4.445) + (mid 1.905 3.8127) + (end 1.27 4.445) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + (polyline + (pts + (xy 2.54 5.08) (xy 2.54 4.445) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "Stepper_Motor_bipolar_1_1" + (pin passive line + (at -2.54 7.62 270) + (length 2.54) + (name "" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 2.54 7.62 270) + (length 2.54) + (name "-" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -7.62 2.54 0) + (length 2.54) + (name "" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at -7.62 -2.54 0) + (length 2.54) + (name "" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "RF_Module:ESP32-WROOM-32" + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "U" + (at -12.7 34.29 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "ESP32-WROOM-32" + (at 1.27 34.29 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "RF_Module:ESP32-WROOM-32" + (at 0 -38.1 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf" + (at -7.62 1.27 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "RF Module, ESP32-D0WDQ6 SoC, Wi-Fi 802.11b/g/n, Bluetooth, BLE, 32-bit, 2.7-3.6V, onboard antenna, SMD" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "RF Radio BT ESP ESP32 Espressif onboard PCB antenna" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "ESP32?WROOM?32*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "ESP32-WROOM-32_0_1" + (rectangle + (start -12.7 33.02) + (end 12.7 -33.02) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + ) + (symbol "ESP32-WROOM-32_1_1" + (pin power_in line + (at 0 -35.56 90) + (length 2.54) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 35.56 270) + (length 2.54) + (name "VDD" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 30.48 0) + (length 2.54) + (name "EN" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 25.4 0) + (length 2.54) + (name "SENSOR_VP" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -15.24 22.86 0) + (length 2.54) + (name "SENSOR_VN" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 15.24 -25.4 180) + (length 2.54) + (name "IO34" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at 15.24 -27.94 180) + (length 2.54) + (name "IO35" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -20.32 180) + (length 2.54) + (name "IO32" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -22.86 180) + (length 2.54) + (name "IO33" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "9" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -12.7 180) + (length 2.54) + (name "IO25" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "10" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -15.24 180) + (length 2.54) + (name "IO26" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "11" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -17.78 180) + (length 2.54) + (name "IO27" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 10.16 180) + (length 2.54) + (name "IO14" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "13" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 15.24 180) + (length 2.54) + (name "IO12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "14" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -35.56 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "15" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 12.7 180) + (length 2.54) + (name "IO13" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "16" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 -5.08 0) + (length 2.54) + (name "SHD/SD2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "17" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 -7.62 0) + (length 2.54) + (name "SWP/SD3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "18" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 -12.7 0) + (length 2.54) + (name "SCS/CMD" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "19" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 -10.16 0) + (length 2.54) + (name "SCK/CLK" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "20" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 0 0) + (length 2.54) + (name "SDO/SD0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "21" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -15.24 -2.54 0) + (length 2.54) + (name "SDI/SD1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "22" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 7.62 180) + (length 2.54) + (name "IO15" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "23" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 25.4 180) + (length 2.54) + (name "IO2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "24" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 30.48 180) + (length 2.54) + (name "IO0" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "25" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 20.32 180) + (length 2.54) + (name "IO4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "26" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 5.08 180) + (length 2.54) + (name "IO16" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "27" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 2.54 180) + (length 2.54) + (name "IO17" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "28" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 17.78 180) + (length 2.54) + (name "IO5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "29" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 0 180) + (length 2.54) + (name "IO18" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "30" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -2.54 180) + (length 2.54) + (name "IO19" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "31" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin no_connect line + (at -12.7 -27.94 0) + (length 2.54) + (hide yes) + (name "NC" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "32" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -5.08 180) + (length 2.54) + (name "IO21" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "33" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 22.86 180) + (length 2.54) + (name "RXD0/IO3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "34" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 27.94 180) + (length 2.54) + (name "TXD0/IO1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "35" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -7.62 180) + (length 2.54) + (name "IO22" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "36" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at 15.24 -10.16 180) + (length 2.54) + (name "IO23" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "37" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -35.56 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "38" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -35.56 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "39" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "Sensor_Distance:VL53L0CXV0DH1" + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "U" + (at -6.35 13.97 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "VL53L0CXV0DH1" + (at 12.065 13.97 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "Sensor_Distance:ST_VL53L1x" + (at 17.145 -13.97 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.st.com/resource/en/datasheet/vl53l0x.pdf" + (at 2.54 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "2m distance ranging ToF sensor, Optical LGA12" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "VL53L0x ToF" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_fp_filters" "ST*VL53L1x*" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "VL53L0CXV0DH1_0_1" + (rectangle + (start -7.62 12.7) + (end 7.62 -12.7) + (stroke + (width 0.254) + (type default) + ) + (fill + (type background) + ) + ) + ) + (symbol "VL53L0CXV0DH1_1_1" + (pin power_in line + (at 2.54 15.24 270) + (length 2.54) + (name "AVDDVCSEL" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 2.54 -15.24 90) + (length 2.54) + (name "AVSSVCSEL" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "2" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 -15.24 90) + (length 2.54) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "3" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -15.24 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "4" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -10.16 -7.62 0) + (length 2.54) + (name "XSHUT" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "5" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -15.24 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "6" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin open_collector line + (at -10.16 5.08 0) + (length 2.54) + (name "GPIO1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "7" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin no_connect line + (at -7.62 7.62 0) + (length 2.54) + (hide yes) + (name "DNC" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "8" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin bidirectional line + (at -10.16 0 0) + (length 2.54) + (name "SDA" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "9" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin input line + (at -10.16 -2.54 0) + (length 2.54) + (name "SCL" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "10" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin power_in line + (at 0 15.24 270) + (length 2.54) + (name "AVDD" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "11" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + (pin passive line + (at 0 -15.24 90) + (length 2.54) + (hide yes) + (name "GND" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "12" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + (symbol "power:GND" + (power global) + (pin_numbers + (hide yes) + ) + (pin_names + (offset 0) + (hide yes) + ) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (duplicate_pin_numbers_are_jumpers no) + (property "Reference" "#PWR" + (at 0 -6.35 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 0 -3.81 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "ki_keywords" "global power" + (at 0 0 0) + (show_name no) + (do_not_autoplace no) + (hide yes) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (symbol "GND_0_1" + (polyline + (pts + (xy 0 0) (xy 0 -1.27) (xy 1.27 -1.27) (xy 0 -2.54) (xy -1.27 -1.27) (xy 0 -1.27) + ) + (stroke + (width 0) + (type default) + ) + (fill + (type none) + ) + ) + ) + (symbol "GND_1_1" + (pin power_in line + (at 0 0 270) + (length 0) + (name "" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (number "1" + (effects + (font + (size 1.27 1.27) + ) + ) + ) + ) + ) + (embedded_fonts no) + ) + ) + (junction + (at 144.78 152.4) + (diameter 0) + (color 0 0 0 0) + (uuid "4cf55877-1800-4181-a92e-0c01027e394d") + ) + (junction + (at 109.22 137.16) + (diameter 0) + (color 0 0 0 0) + (uuid "5d97d31c-4303-4744-9f1f-df572d06a9df") + ) + (junction + (at 124.46 29.21) + (diameter 0) + (color 0 0 0 0) + (uuid "7f30818d-c1bc-40a5-a352-05aed00ea9a6") + ) + (junction + (at 102.87 73.66) + (diameter 0) + (color 0 0 0 0) + (uuid "adea91eb-3885-4f77-b347-a0947c776882") + ) + (junction + (at 114.3 134.62) + (diameter 0) + (color 0 0 0 0) + (uuid "b7f3812f-a7d8-4964-907f-ec13033f574e") + ) + (junction + (at 124.46 152.4) + (diameter 0) + (color 0 0 0 0) + (uuid "f02bc537-182c-4d44-b91d-5c697a513909") + ) + (wire + (pts + (xy 214.63 68.58) (xy 200.66 68.58) + ) + (stroke + (width 0) + (type default) + ) + (uuid "031d5b95-5b11-4411-b41e-75c7fdaad9df") + ) + (wire + (pts + (xy 227.33 135.89) (xy 222.25 135.89) + ) + (stroke + (width 0) + (type default) + ) + (uuid "07cc06bf-5ab4-4490-82dd-b4a09f9e26b0") + ) + (wire + (pts + (xy 203.2 80.01) (xy 166.37 80.01) + ) + (stroke + (width 0) + (type default) + ) + (uuid "09c544ea-0ac4-479e-8a98-9b87a6361df6") + ) + (wire + (pts + (xy 224.79 59.69) (xy 195.58 59.69) + ) + (stroke + (width 0) + (type default) + ) + (uuid "0b6b7616-98a5-48ae-ac0f-c791daa2b58a") + ) + (wire + (pts + (xy 279.4 165.1) (xy 289.56 165.1) + ) + (stroke + (width 0) + (type default) + ) + (uuid "13cd5d9a-1f99-493d-92c7-8db4399b6c85") + ) + (wire + (pts + (xy 97.79 73.66) (xy 102.87 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "1539d575-701f-4c95-b75e-f33907522feb") + ) + (wire + (pts + (xy 227.33 143.51) (xy 227.33 135.89) + ) + (stroke + (width 0) + (type default) + ) + (uuid "15b50113-8d0b-43b9-bd17-f3e0587c822b") + ) + (wire + (pts + (xy 262.89 151.13) (xy 245.11 151.13) + ) + (stroke + (width 0) + (type default) + ) + (uuid "1d4f8ade-2e7b-47b0-bc20-fd11f20e7bc7") + ) + (wire + (pts + (xy 114.3 134.62) (xy 114.3 171.45) + ) + (stroke + (width 0) + (type default) + ) + (uuid "20f0f9a7-c321-4e24-bde7-fd15113e11cd") + ) + (wire + (pts + (xy 262.89 148.59) (xy 262.89 151.13) + ) + (stroke + (width 0) + (type default) + ) + (uuid "2820da2e-36ff-45db-8e46-3680fb4035b4") + ) + (wire + (pts + (xy 190.5 151.13) (xy 190.5 134.62) + ) + (stroke + (width 0) + (type default) + ) + (uuid "305f60c7-1d5d-449d-9603-c9c5faaa12e6") + ) + (wire + (pts + (xy 198.12 69.85) (xy 166.37 69.85) + ) + (stroke + (width 0) + (type default) + ) + (uuid "3af22bc7-7988-4cc3-b515-078affebddfd") + ) + (wire + (pts + (xy 102.87 73.66) (xy 116.84 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "3e6d2db0-a8e6-4a24-8a8f-e434c66d0031") + ) + (wire + (pts + (xy 116.84 76.2) (xy 73.66 76.2) + ) + (stroke + (width 0) + (type default) + ) + (uuid "3e9c6d13-1bed-47b3-a7bc-59bc31d53c89") + ) + (wire + (pts + (xy 187.96 137.16) (xy 109.22 137.16) + ) + (stroke + (width 0) + (type default) + ) + (uuid "469b3ca0-a9c9-4b4a-b86c-3e39f19e2711") + ) + (wire + (pts + (xy 73.66 73.66) (xy 90.17 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "4a65e6ca-c292-43f2-bf98-9a1fae414ff7") + ) + (wire + (pts + (xy 134.62 207.01) (xy 144.78 207.01) + ) + (stroke + (width 0) + (type default) + ) + (uuid "50769a7a-2639-41d9-98ef-435f018824d0") + ) + (wire + (pts + (xy 279.4 151.13) (xy 289.56 151.13) + ) + (stroke + (width 0) + (type default) + ) + (uuid "55355335-684b-482f-8687-407b914cd78d") + ) + (wire + (pts + (xy 187.96 153.67) (xy 187.96 137.16) + ) + (stroke + (width 0) + (type default) + ) + (uuid "56ec1cc3-ff26-4894-8d70-b9cc5d2989ad") + ) + (wire + (pts + (xy 198.12 63.5) (xy 198.12 69.85) + ) + (stroke + (width 0) + (type default) + ) + (uuid "580e21fb-d35f-41ec-813d-e944462ad023") + ) + (wire + (pts + (xy 116.84 73.66) (xy 116.84 62.23) + ) + (stroke + (width 0) + (type default) + ) + (uuid "5ce2f91d-5ea6-4b6b-a269-6182d11e9b2c") + ) + (wire + (pts + (xy 195.58 59.69) (xy 195.58 72.39) + ) + (stroke + (width 0) + (type default) + ) + (uuid "5f2d1297-56a0-4e1a-8cf9-9758959a214b") + ) + (wire + (pts + (xy 156.21 29.21) (xy 156.21 41.91) + ) + (stroke + (width 0) + (type default) + ) + (uuid "6a2e478b-691d-4c94-b353-eb9a0e5efcf1") + ) + (wire + (pts + (xy 167.64 152.4) (xy 144.78 152.4) + ) + (stroke + (width 0) + (type default) + ) + (uuid "6c71e7c1-d97a-45d2-bb59-1135c0224010") + ) + (wire + (pts + (xy 142.24 170.18) (xy 157.48 170.18) + ) + (stroke + (width 0) + (type default) + ) + (uuid "7d015e4f-d387-472c-8902-bab9a3f87a87") + ) + (wire + (pts + (xy 109.22 81.28) (xy 109.22 137.16) + ) + (stroke + (width 0) + (type default) + ) + (uuid "7d4883b2-c915-4850-b506-994eac5137b5") + ) + (wire + (pts + (xy 203.2 73.66) (xy 203.2 80.01) + ) + (stroke + (width 0) + (type default) + ) + (uuid "7da41075-5b78-472e-81eb-3d50233b246e") + ) + (wire + (pts + (xy 109.22 137.16) (xy 109.22 173.99) + ) + (stroke + (width 0) + (type default) + ) + (uuid "851dfdcc-8d35-4d04-8a40-6e6eeb950bac") + ) + (wire + (pts + (xy 144.78 212.09) (xy 157.48 212.09) + ) + (stroke + (width 0) + (type default) + ) + (uuid "875daf3e-50e4-44bc-b9c0-3cacfe023f1d") + ) + (wire + (pts + (xy 147.32 173.99) (xy 134.62 173.99) + ) + (stroke + (width 0) + (type default) + ) + (uuid "8a4a8934-f7d3-40ad-829d-f64ba4c0f00e") + ) + (wire + (pts + (xy 147.32 167.64) (xy 157.48 167.64) + ) + (stroke + (width 0) + (type default) + ) + (uuid "91ba3bcd-f4d1-4ac7-8704-3729116ea76a") + ) + (wire + (pts + (xy 289.56 148.59) (xy 262.89 148.59) + ) + (stroke + (width 0) + (type default) + ) + (uuid "95052e18-7337-4666-94cc-c80acdeecb4b") + ) + (wire + (pts + (xy 116.84 76.2) (xy 116.84 77.47) + ) + (stroke + (width 0) + (type default) + ) + (uuid "967575da-7c4b-41ed-bd90-2b2ef89cc871") + ) + (wire + (pts + (xy 167.64 194.31) (xy 144.78 194.31) + ) + (stroke + (width 0) + (type default) + ) + (uuid "97b7cf93-1fc5-4962-8d06-044131f30150") + ) + (wire + (pts + (xy 58.42 29.21) (xy 58.42 40.64) + ) + (stroke + (width 0) + (type default) + ) + (uuid "9ecdd259-ba8b-4e8b-9e55-ca857d16485b") + ) + (wire + (pts + (xy 209.55 151.13) (xy 190.5 151.13) + ) + (stroke + (width 0) + (type default) + ) + (uuid "9fc98cd8-254f-4ad8-995c-5301d9da647d") + ) + (wire + (pts + (xy 102.87 71.12) (xy 102.87 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a0b429ef-d95a-4fe4-aedd-2cb815d3f72b") + ) + (wire + (pts + (xy 195.58 72.39) (xy 166.37 72.39) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a103ffa6-aecf-4ac8-9709-750779d2f33b") + ) + (wire + (pts + (xy 124.46 152.4) (xy 124.46 166.37) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a3659480-a02a-4766-b9b8-20de7aee758c") + ) + (wire + (pts + (xy 167.64 224.79) (xy 167.64 223.52) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a5af205b-f659-41dc-b60b-4fc5d4506046") + ) + (wire + (pts + (xy 144.78 152.4) (xy 124.46 152.4) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a66b815b-68ba-4dba-8f4d-9ec05976968c") + ) + (wire + (pts + (xy 166.37 54.61) (xy 184.15 54.61) + ) + (stroke + (width 0) + (type default) + ) + (uuid "a8d4113a-6298-49a0-bc83-18af076c4b24") + ) + (wire + (pts + (xy 209.55 153.67) (xy 187.96 153.67) + ) + (stroke + (width 0) + (type default) + ) + (uuid "ae6f7849-49d0-4a51-8d71-39d907a95d0b") + ) + (wire + (pts + (xy 200.66 77.47) (xy 166.37 77.47) + ) + (stroke + (width 0) + (type default) + ) + (uuid "b24ea75a-0706-4080-91a3-66a3ff33732d") + ) + (wire + (pts + (xy 134.62 209.55) (xy 157.48 209.55) + ) + (stroke + (width 0) + (type default) + ) + (uuid "b2ce88f0-b122-423e-912c-555473ebb3ec") + ) + (wire + (pts + (xy 114.3 83.82) (xy 73.66 83.82) + ) + (stroke + (width 0) + (type default) + ) + (uuid "b2e8e8c7-8302-4c8f-9f2f-2ee64f3bd5de") + ) + (wire + (pts + (xy 144.78 207.01) (xy 144.78 212.09) + ) + (stroke + (width 0) + (type default) + ) + (uuid "ba974bf3-1efc-439d-a056-131ca7cd7dfe") + ) + (wire + (pts + (xy 142.24 171.45) (xy 134.62 171.45) + ) + (stroke + (width 0) + (type default) + ) + (uuid "bc171b68-8a97-42b9-a85b-6c8e8663ffe6") + ) + (wire + (pts + (xy 262.89 162.56) (xy 262.89 153.67) + ) + (stroke + (width 0) + (type default) + ) + (uuid "bdc9fb8e-67a7-4b1e-b476-e1490f8116da") + ) + (wire + (pts + (xy 58.42 29.21) (xy 124.46 29.21) + ) + (stroke + (width 0) + (type default) + ) + (uuid "bf10e761-8b33-4388-9a74-d59213b2c0c8") + ) + (wire + (pts + (xy 214.63 73.66) (xy 203.2 73.66) + ) + (stroke + (width 0) + (type default) + ) + (uuid "bfd07175-cae4-4ad3-becf-c6316fc7f7cb") + ) + (wire + (pts + (xy 73.66 81.28) (xy 109.22 81.28) + ) + (stroke + (width 0) + (type default) + ) + (uuid "c007aac0-1343-4555-b21e-61391c3c5339") + ) + (wire + (pts + (xy 262.89 153.67) (xy 245.11 153.67) + ) + (stroke + (width 0) + (type default) + ) + (uuid "c0e630b4-e156-44da-97ea-3b938b3cce1c") + ) + (wire + (pts + (xy 144.78 194.31) (xy 144.78 152.4) + ) + (stroke + (width 0) + (type default) + ) + (uuid "cd5dee3b-f1a8-43f3-8ff7-ed93b0dbb7b2") + ) + (wire + (pts + (xy 219.71 63.5) (xy 198.12 63.5) + ) + (stroke + (width 0) + (type default) + ) + (uuid "d67aebc0-156e-4e04-9af0-627d488e5105") + ) + (wire + (pts + (xy 73.66 71.12) (xy 102.87 71.12) + ) + (stroke + (width 0) + (type default) + ) + (uuid "de205d1e-3f84-43d6-9fc9-74ee4dede0e8") + ) + (wire + (pts + (xy 116.84 77.47) (xy 135.89 77.47) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e5763246-9533-4c5e-a549-28bc7486ac62") + ) + (wire + (pts + (xy 116.84 62.23) (xy 135.89 62.23) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e61f8b85-8242-4fa0-bcaf-f22420c9bf80") + ) + (wire + (pts + (xy 289.56 162.56) (xy 262.89 162.56) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e6acbcf1-6854-4c00-b95a-9fc49393d68b") + ) + (wire + (pts + (xy 142.24 170.18) (xy 142.24 171.45) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e6bd7345-721f-4c98-89ea-fc1e31608173") + ) + (wire + (pts + (xy 124.46 29.21) (xy 156.21 29.21) + ) + (stroke + (width 0) + (type default) + ) + (uuid "e9a6a7fb-0d29-4c8c-a249-fb7750f53371") + ) + (wire + (pts + (xy 224.79 63.5) (xy 224.79 59.69) + ) + (stroke + (width 0) + (type default) + ) + (uuid "eb09cef7-c4f0-4242-ac1d-ced21252b8f6") + ) + (wire + (pts + (xy 147.32 167.64) (xy 147.32 173.99) + ) + (stroke + (width 0) + (type default) + ) + (uuid "ee58924d-d54d-4507-af92-752a84cde7ab") + ) + (wire + (pts + (xy 114.3 83.82) (xy 114.3 134.62) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f14852bd-73f2-4f15-a670-37a29d394127") + ) + (wire + (pts + (xy 124.46 29.21) (xy 124.46 152.4) + ) + (stroke + (width 0) + (type default) + ) + (uuid "f4ba3c07-d264-4062-b699-9e4a98ce804d") + ) + (wire + (pts + (xy 109.22 173.99) (xy 114.3 173.99) + ) + (stroke + (width 0) + (type default) + ) + (uuid "fc0a1a3c-b130-4859-b37b-81c84f67fd1d") + ) + (wire + (pts + (xy 200.66 68.58) (xy 200.66 77.47) + ) + (stroke + (width 0) + (type default) + ) + (uuid "fcb9df70-e940-48b9-9617-d609150bd0f2") + ) + (wire + (pts + (xy 190.5 134.62) (xy 114.3 134.62) + ) + (stroke + (width 0) + (type default) + ) + (uuid "fde83215-818f-417b-9f43-39c2098fbd04") + ) + (global_label "5V_ext" + (shape output) + (at 279.4 165.1 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "19f53332-40c3-4492-8a76-bdf37954cf4f") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 270.3067 165.1 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + ) + (global_label "5V_ext" + (shape output) + (at 279.4 151.13 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "892c534f-543a-4faf-9a84-8e4a824eebdf") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 270.3067 151.13 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + ) + (global_label "5V_ext" + (shape output) + (at 222.25 135.89 180) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + (uuid "988bb71a-5452-4b28-89f1-d97297d98520") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 213.1567 135.89 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify right) + ) + ) + ) + (global_label "12V" + (shape output) + (at 184.15 54.61 0) + (fields_autoplaced yes) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + (uuid "d6a62597-fb19-4ce2-825d-5309bfe185d9") + (property "Intersheetrefs" "${INTERSHEET_REFS}" + (at 190.6428 54.61 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 167.64 182.88 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "0671087a-3e80-4e41-bc16-ca68ee5723a0") + (property "Reference" "#PWR05" + (at 167.64 189.23 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 167.64 187.96 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 167.64 182.88 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 167.64 182.88 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 167.64 182.88 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "794bfe75-ab96-4b9d-9767-5e1ca8ee60b4") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR05") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 227.33 196.85 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "4222534b-0c81-46ac-b812-5465cd86ce43") + (property "Reference" "#PWR07" + (at 227.33 203.2 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 227.33 201.93 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 227.33 196.85 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 227.33 196.85 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 227.33 196.85 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "3647e629-b191-4af9-8179-5119279b3a41") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR07") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "RF_Module:ESP32-WROOM-32") + (at 58.42 76.2 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "4dcf8093-ffc0-4764-8926-7a97178e5c9f") + (property "Reference" "U1" + (at 60.5633 38.1 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "ESP32-WROOM-32" + (at 60.5633 40.64 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "RF_Module:ESP32-WROOM-32" + (at 58.42 114.3 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf" + (at 50.8 74.93 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "RF Module, ESP32-D0WDQ6 SoC, Wi-Fi 802.11b/g/n, Bluetooth, BLE, 32-bit, 2.7-3.6V, onboard antenna, SMD" + (at 58.42 76.2 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "33" + (uuid "646bac6f-9e66-4b56-8766-35715c8acc46") + ) + (pin "38" + (uuid "86a0f224-59b6-44ab-840d-a1388d6e8401") + ) + (pin "36" + (uuid "72cf63e1-89e3-4a46-b21b-8d183a43a668") + ) + (pin "37" + (uuid "c319a131-3a91-4ba6-af3b-fa7dd768e795") + ) + (pin "20" + (uuid "40a92381-e573-4c5e-86c7-54d2903210dc") + ) + (pin "21" + (uuid "97b38a9c-5e78-4000-bb08-e27437ef758b") + ) + (pin "32" + (uuid "0e464139-9311-4fdd-9325-999d68fa49c5") + ) + (pin "35" + (uuid "0c4ce8d6-57c1-4772-870e-1af3f90fa308") + ) + (pin "34" + (uuid "78076e16-a122-42b2-9931-fdb8505da5ce") + ) + (pin "6" + (uuid "e55086eb-865a-43d4-9826-efa50187f0b9") + ) + (pin "18" + (uuid "0277d0ba-aeae-45d4-be40-0a37aa7edf76") + ) + (pin "30" + (uuid "101c370f-efea-4f3b-8490-c38df08951fa") + ) + (pin "31" + (uuid "24292e75-33b7-41ad-b65d-3dcb06f8d15a") + ) + (pin "19" + (uuid "2a73f498-1102-44d0-ae18-f595e3b6d60f") + ) + (pin "17" + (uuid "87e70284-02d8-41eb-a90f-1750031afe18") + ) + (pin "39" + (uuid "89534b23-bfd4-4b90-872c-ce7fbc065825") + ) + (pin "22" + (uuid "0dd9bb32-6cef-43db-8819-e15e78fc2316") + ) + (pin "8" + (uuid "4f51b296-e4ca-484b-bfa4-fc9fbb498d87") + ) + (pin "25" + (uuid "5d70ad4e-2c98-4f7e-8db1-9846f39661db") + ) + (pin "11" + (uuid "293c7609-f178-4343-a0e4-2aad8e7df3e3") + ) + (pin "26" + (uuid "458444bd-230c-48cf-a46b-9b2e852a8c5c") + ) + (pin "24" + (uuid "248bcc9b-1e2f-4513-ad80-94e6c73b42a0") + ) + (pin "12" + (uuid "6ff7e00f-30ef-4202-bbe3-c3eb41f450cd") + ) + (pin "23" + (uuid "8d913eb4-c556-4a74-bade-5d25ce1dee59") + ) + (pin "28" + (uuid "1f2a7ee1-c59b-47b9-b470-7016c3965c35") + ) + (pin "13" + (uuid "bf8bfe1c-f39b-4fa7-a3c1-babe1170205a") + ) + (pin "2" + (uuid "c3277387-f8d5-4904-90fa-d27cfbba7d69") + ) + (pin "4" + (uuid "772709a1-5033-475d-9191-dfca65c6fe7d") + ) + (pin "27" + (uuid "129dd19c-0255-4c8d-8bb9-d70b5a3f3b8b") + ) + (pin "16" + (uuid "a9522b18-b698-4d8e-8db5-be2b5c845616") + ) + (pin "9" + (uuid "6ee74ccc-f903-4c10-8783-7a7db6bd79c8") + ) + (pin "10" + (uuid "d532fdea-891f-42da-8062-d49b093bb88f") + ) + (pin "7" + (uuid "ee47fb39-7cdd-4799-b0eb-8844fa929ba8") + ) + (pin "15" + (uuid "2a35e8c1-d6f6-408c-8801-aff28ab34b9a") + ) + (pin "29" + (uuid "f2e1e0e2-383a-44ce-b735-cb0b6643e696") + ) + (pin "5" + (uuid "b9543f69-f9c1-4ef9-af74-07705d753aa6") + ) + (pin "3" + (uuid "e45bba80-c901-4b9c-81bf-4af04a065de3") + ) + (pin "1" + (uuid "43a93325-016e-4ff1-bad6-2f4c0d8a3d94") + ) + (pin "14" + (uuid "c87d6089-6327-4bc0-96dd-e71768ddcf79") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "U1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 58.42 111.76 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "51bc3ead-c307-41f7-8a72-ef9999b18731") + (property "Reference" "#PWR01" + (at 58.42 118.11 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 58.42 116.84 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 58.42 111.76 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 58.42 111.76 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 58.42 111.76 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "c8f35414-b01c-4aeb-8fd8-6b23a06c35ea") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR01") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Driver_LED:PCA9685PW") + (at 227.33 168.91 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "58b66b30-d0fb-4db2-8c53-b18c2f2207be") + (property "Reference" "U6" + (at 229.4733 143.51 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "PCA9685PW" + (at 229.4733 146.05 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Package_SO:TSSOP-28_4.4x9.7mm_P0.65mm" + (at 227.965 193.675 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Datasheet" "http://www.nxp.com/docs/en/data-sheet/PCA9685.pdf" + (at 217.17 151.13 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "16-channel 12-bit PWM Fm+ I2C-bus LED controller RGBA TSSOP" + (at 227.33 168.91 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "20" + (uuid "e34ff49d-1707-402c-853a-795e85d39543") + ) + (pin "15" + (uuid "8b634af7-03d7-442f-aa20-fa14adf5b1f4") + ) + (pin "4" + (uuid "03819684-63b3-4a3f-8e4e-8a72ff7f34c2") + ) + (pin "7" + (uuid "187771af-60af-46cf-9838-29d32d6aff2c") + ) + (pin "22" + (uuid "ee847925-b98c-4860-84ec-727fea27ea26") + ) + (pin "26" + (uuid "70eb0b98-f20c-412c-8d57-fd9686aea219") + ) + (pin "14" + (uuid "387f4c9f-e21e-447e-9c7a-65bec72820cb") + ) + (pin "8" + (uuid "2e906393-19a4-4670-94dd-a07a837558e6") + ) + (pin "21" + (uuid "16bf9d16-9e85-4074-8ce9-c7444f1e393a") + ) + (pin "28" + (uuid "d58afe5a-c202-46fc-b62f-322377284e7e") + ) + (pin "9" + (uuid "6cdf5b0a-3153-4a25-89dd-421480ee8e72") + ) + (pin "11" + (uuid "33aee357-a602-4dc4-9af7-ad2fda061ad0") + ) + (pin "25" + (uuid "addeb56d-0ca2-4916-8a3d-549d5c2929cd") + ) + (pin "3" + (uuid "7fd2d4eb-88e9-402c-a51d-0b22435eb396") + ) + (pin "10" + (uuid "d91ad8c1-1dd4-4770-987e-c2a0301a0ae3") + ) + (pin "6" + (uuid "4c3e4d28-ba9a-4c89-a0cf-2e2d775ff6bd") + ) + (pin "5" + (uuid "1b817b07-4751-4dce-9208-be1f4aab08af") + ) + (pin "2" + (uuid "c3285918-7309-4534-8dee-9ae87d63688a") + ) + (pin "17" + (uuid "e8d6ba99-6794-4dc2-b1be-0c75bd6b38e1") + ) + (pin "1" + (uuid "97e9043c-b63e-42f0-81a0-771b08f96a13") + ) + (pin "12" + (uuid "b05b1d32-b614-4119-8dd8-acbc18d8ec72") + ) + (pin "27" + (uuid "e712ee84-c2fd-4a0c-a14f-7e323e9d38a3") + ) + (pin "23" + (uuid "20a1429b-1c7e-4a5b-bfb3-87f940451ed3") + ) + (pin "16" + (uuid "f2a50698-a51f-486b-ab8d-1823bb0ae777") + ) + (pin "18" + (uuid "4f1a9474-ee77-4df1-8a09-f5c443205fbc") + ) + (pin "19" + (uuid "f3dae807-ae96-46ae-928c-a4f9585c99bf") + ) + (pin "24" + (uuid "4e201566-d549-4396-ab7a-f9f28736d602") + ) + (pin "13" + (uuid "a2d3e364-0311-46d8-b6de-6529ead5f075") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "U6") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 124.46 214.63 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "5aadedd8-a613-40b9-870c-876e1dbb2f07") + (property "Reference" "#PWR04" + (at 124.46 220.98 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 124.46 219.71 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 124.46 214.63 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 124.46 214.63 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 124.46 214.63 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "f5e35e30-3307-4f99-a1cf-f2db697281eb") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR04") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Motor:Motor_Servo") + (at 297.18 165.1 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "61beb224-94b3-400f-ab95-0b6e4be798cb") + (property "Reference" "M4" + (at 306.07 163.3968 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Motor_Servo" + (at 306.07 165.9368 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 297.18 169.926 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://forums.parallax.com/uploads/attachments/46831/74481.png" + (at 297.18 169.926 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Servo Motor (Futaba, HiTec, JR connector)" + (at 297.18 165.1 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "3" + (uuid "ec883895-8b44-4032-bc32-2f733fe07da7") + ) + (pin "2" + (uuid "0be6c21a-653f-431c-a096-7cc0b3961a63") + ) + (pin "1" + (uuid "7dd4e8ca-0fa5-4fac-b5f0-af10ded8934d") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "M4") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 289.56 153.67 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "6b6e3665-8c1c-4619-bc06-c31af524a2aa") + (property "Reference" "#PWR08" + (at 289.56 160.02 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 289.56 158.75 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 289.56 153.67 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 289.56 153.67 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 289.56 153.67 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "8608c1c5-ba6b-4d60-b90d-a5d059532eb3") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR08") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Driver_Motor:TMC2209-LA") + (at 151.13 69.85 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "6c0318de-b8ee-42a2-9792-9e929413aeec") + (property "Reference" "U2" + (at 155.8133 95.25 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "TMC2209-LA" + (at 155.8133 97.79 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Package_DFN_QFN:VQFN-28-1EP_5x5mm_P0.5mm_EP3.7x3.7mm_ThermalVias" + (at 151.13 97.79 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.analog.com/media/en/technical-documentation/data-sheets/TMC2209_datasheet_rev1.09.pdf" + (at 151.13 99.822 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "2-phase stepper motor driver, 256 µSteps, 2.8A peak, 2.0A RMS, VS = 4.75..29V, STEP/DIR and UART interface, VQFN-28" + (at 151.13 101.854 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "11" + (uuid "497ac5e0-b15d-4ae8-a265-82b132e13f91") + ) + (pin "15" + (uuid "6b8c6717-9bf6-4c7f-bd9f-88b72475e019") + ) + (pin "13" + (uuid "e10fdcb7-e274-4b9a-b57c-09d125d3d622") + ) + (pin "16" + (uuid "230c0794-79ac-4649-9160-a38ae00ac971") + ) + (pin "29" + (uuid "8d290a25-5386-4cd6-a75f-94d4118365c6") + ) + (pin "2" + (uuid "6fdb9ad1-0797-4255-8d06-b3ab9bdc319e") + ) + (pin "12" + (uuid "bbef1a96-dc27-40c4-8449-6281860ecb99") + ) + (pin "20" + (uuid "284f22e4-e356-456e-b618-d700ef61f68f") + ) + (pin "28" + (uuid "db9c9f02-bee0-4d2d-b86a-10d5d9ea17fa") + ) + (pin "9" + (uuid "06e972d3-27b2-4dbb-b88d-f6d484c0faaa") + ) + (pin "27" + (uuid "038e543d-999b-4613-97c6-2b7acc851a45") + ) + (pin "22" + (uuid "f1a19069-e1c9-4187-89c0-732bf89c40fb") + ) + (pin "26" + (uuid "a80a5502-c10b-4bd6-8c49-83311c37add8") + ) + (pin "5" + (uuid "09bfbabe-0066-4349-94c9-74dbb109be5e") + ) + (pin "19" + (uuid "b2ff5632-024b-4483-b386-25a74089288c") + ) + (pin "24" + (uuid "8300b2df-8368-4310-9f6f-2bd0002c4468") + ) + (pin "7" + (uuid "cce875bf-3ca9-46eb-9d0e-7cfb07c7035a") + ) + (pin "14" + (uuid "c7bc7ba7-1793-43b8-8a30-b87ca1756e91") + ) + (pin "18" + (uuid "1c1de555-baaf-44de-a93d-c286498f235a") + ) + (pin "17" + (uuid "2f784d9b-d97d-45d4-bac6-ffd3422ae2cd") + ) + (pin "21" + (uuid "95d56345-84ca-4b27-847a-c8bab4ca087b") + ) + (pin "3" + (uuid "8a447ddb-0702-4d69-819a-6e0718842d42") + ) + (pin "8" + (uuid "86deaa1b-614a-40dd-ad70-6bf52fc7fd2b") + ) + (pin "6" + (uuid "035dd83c-f77e-4fb8-ae99-5dd41807c10d") + ) + (pin "10" + (uuid "686ce3df-3ebf-4309-ac70-1dccf2edd4b5") + ) + (pin "4" + (uuid "8febfc31-cd8a-430b-9b23-858f267e3038") + ) + (pin "1" + (uuid "4f5ed9d9-ce75-4953-8852-91cee84869de") + ) + (pin "23" + (uuid "ee9dc44c-b8e1-4eb9-ac8a-c91f21fa17f8") + ) + (pin "25" + (uuid "c789807a-1abd-4e1e-a444-63c8273a5314") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "U2") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 167.64 223.52 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "7c59af9a-8fb3-4e73-8a25-e633c8afc519") + (property "Reference" "#PWR06" + (at 167.64 229.87 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 167.64 228.6 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 167.64 223.52 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 167.64 223.52 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 167.64 223.52 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "11e25132-9524-466c-8ece-0e383c26e5ad") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR06") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Sensor_Distance:VL53L0CXV0DH1") + (at 167.64 167.64 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "85ca7544-472a-4bbd-bcce-ea1cc2017ede") + (property "Reference" "U3" + (at 176.53 166.3699 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "VL53L0CXV0DH1" + (at 176.53 168.9099 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Sensor_Distance:ST_VL53L1x" + (at 184.785 181.61 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.st.com/resource/en/datasheet/vl53l0x.pdf" + (at 170.18 167.64 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "2m distance ranging ToF sensor, Optical LGA12" + (at 167.64 167.64 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "5" + (uuid "ee103b1d-ad5f-4c54-97bb-a9472da500ab") + ) + (pin "6" + (uuid "c074c315-a93c-480d-8428-7f27d3bb673c") + ) + (pin "4" + (uuid "29b48ae5-0c5a-4058-9b5e-d161ec710732") + ) + (pin "11" + (uuid "8263b748-c0b3-4f2e-a810-e743915ee50b") + ) + (pin "12" + (uuid "559dbdf5-219e-45ba-990c-4ca7c580cf25") + ) + (pin "2" + (uuid "7241420c-46ab-404d-84f3-5a7f95879b27") + ) + (pin "8" + (uuid "c37d3106-028b-43bb-9f64-92c1ab15dd5f") + ) + (pin "9" + (uuid "9e54c3cb-6b66-44d1-acde-2fca2815ae86") + ) + (pin "1" + (uuid "0090d5e9-8cc6-4d3c-b2d6-d1607cc1fa75") + ) + (pin "3" + (uuid "ff6d178e-cb41-41a3-9f89-6240b9ac1a75") + ) + (pin "7" + (uuid "fc3f0560-a48e-489c-8dd9-3ae6b73de474") + ) + (pin "10" + (uuid "0143a428-cb5a-4a85-bd30-9b1a72a83135") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "U3") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 151.13 95.25 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "87903677-77aa-4ccd-af79-460d6b16aaec") + (property "Reference" "#PWR02" + (at 151.13 101.6 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 151.13 100.33 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 151.13 95.25 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 151.13 95.25 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 151.13 95.25 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "f33c6a3c-50b4-461f-ad4d-cd52731232ec") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR02") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Device:R") + (at 93.98 73.66 270) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "ab20b3f4-c12f-40c0-99bf-829e39e4acc7") + (property "Reference" "R1" + (at 93.98 67.31 90) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "1k" + (at 93.98 69.85 90) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 93.98 71.882 90) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 93.98 73.66 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Resistor" + (at 93.98 73.66 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "c8ab7660-a6ed-46ef-b4c3-d260a0e3b8ce") + ) + (pin "2" + (uuid "9d2b464e-7e2a-4d59-a653-d21bb18f8016") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "R1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Motor:Stepper_Motor_bipolar") + (at 222.25 71.12 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "ad38428b-ea00-42ec-ab69-4498ebdedbed") + (property "Reference" "M1" + (at 227.33 68.999 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Stepper_Motor_bipolar" + (at 227.33 71.539 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 222.504 71.374 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://www.infineon.com/dgdl/Application-Note-TLE8110EE_driving_UniPolarStepperMotor_V1.1.pdf?fileId=db3a30431be39b97011be5d0aa0a00b0" + (at 222.504 71.374 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "4-wire bipolar stepper motor" + (at 222.25 71.12 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "4" + (uuid "9de392c2-9f3f-443c-81df-2e1caf8f2832") + ) + (pin "3" + (uuid "8abb5f49-1000-458f-b9e0-e16131238cce") + ) + (pin "2" + (uuid "5412e7ac-3abc-4d8e-9f0a-8c5f1f4fbbf2") + ) + (pin "1" + (uuid "5b2471e2-306c-4fc5-b878-d303cd5336b5") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "M1") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Sensor_Distance:VL53L0CXV0DH1") + (at 167.64 209.55 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "c1907d04-6673-48f4-a1f7-8eb05938dfc7") + (property "Reference" "U4" + (at 176.53 208.2799 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "VL53L0CXV0DH1" + (at 176.53 210.8199 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Sensor_Distance:ST_VL53L1x" + (at 184.785 223.52 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "https://www.st.com/resource/en/datasheet/vl53l0x.pdf" + (at 170.18 209.55 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "2m distance ranging ToF sensor, Optical LGA12" + (at 167.64 209.55 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "9" + (uuid "4d304ff3-f67f-4235-b4dd-75be80dad386") + ) + (pin "1" + (uuid "fe282659-537c-4452-b3c8-c8f439552ebc") + ) + (pin "5" + (uuid "94fa59dd-2925-41b1-8035-290611343f43") + ) + (pin "7" + (uuid "828b488f-c584-4c75-9de5-88b27448da9b") + ) + (pin "2" + (uuid "e7a3e52a-4a8a-45ac-b073-36e9894aeb35") + ) + (pin "10" + (uuid "6acdff9d-f794-4397-815e-946b73b2fa72") + ) + (pin "12" + (uuid "779efa1e-5ea4-446d-bbff-9d313be399b8") + ) + (pin "8" + (uuid "de87eeaa-3f16-4611-b55f-45d0761bb9cf") + ) + (pin "4" + (uuid "d91a736f-7f0f-4ca1-9e9c-61d7eeaa3b7a") + ) + (pin "6" + (uuid "bbd0daa9-bd19-4aee-bae2-be92d8ed482a") + ) + (pin "11" + (uuid "b0f98b3b-9620-45aa-881f-91ee0b3fe6e0") + ) + (pin "3" + (uuid "8319baf0-2134-4f17-a711-90b73b5ee51a") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "U4") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Interface_Expansion:TCA9548APWR") + (at 124.46 189.23 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "d955c5f3-9ef8-4cf6-8b19-b57cbaaed6e7") + (property "Reference" "U5" + (at 126.6033 163.83 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "TCA9548APWR" + (at 126.6033 166.37 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "Package_SO:TSSOP-24_4.4x7.8mm_P0.65mm" + (at 124.46 214.63 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://www.ti.com/lit/ds/symlink/tca9548a.pdf" + (at 125.73 182.88 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Low voltage 8-channel I2C switch with reset, TSSOP-24" + (at 124.46 189.23 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "11" + (uuid "cb1ae186-a023-464b-949d-961ad405124d") + ) + (pin "10" + (uuid "9b9392b7-8065-4daf-84d1-280832858f67") + ) + (pin "16" + (uuid "2a3ed370-dd14-418d-9507-c35a59a8bb6f") + ) + (pin "20" + (uuid "5f87c8dc-08d3-417b-ab7c-653005b93c66") + ) + (pin "22" + (uuid "2f1ac809-5e86-40d3-b09f-cc5d55dc268b") + ) + (pin "19" + (uuid "0247b696-63ff-41b0-8e31-e2b51c4083b8") + ) + (pin "15" + (uuid "cd910b44-9cff-4b24-a53a-c4178a27f256") + ) + (pin "14" + (uuid "e63860bc-d7d4-48b7-b512-17445ccbda70") + ) + (pin "24" + (uuid "b7b3caf2-46c7-4096-857b-6d67f30f791c") + ) + (pin "21" + (uuid "28867eb4-924f-49c3-a0a8-a5a1b1f3f0cf") + ) + (pin "17" + (uuid "a93a7b26-72b3-4023-aa61-c8a05b4cc6a8") + ) + (pin "2" + (uuid "a2967ad9-66a8-4ab4-a84c-e514f0448b96") + ) + (pin "9" + (uuid "f82c63d5-6906-4649-a3ec-cf99ce7bccca") + ) + (pin "3" + (uuid "308cdfea-e4d1-43c6-a543-8dff8281f1f7") + ) + (pin "4" + (uuid "fccc47e2-c548-40c1-a03c-088053d35590") + ) + (pin "7" + (uuid "2a980cf3-f693-4570-85b8-6ccdef7ca46b") + ) + (pin "6" + (uuid "19ee6c5a-1843-4f9e-812b-7a4addc4c0ec") + ) + (pin "18" + (uuid "27b8f079-9eca-479f-b6de-e7d892837fe5") + ) + (pin "23" + (uuid "c697c339-6bb7-48d7-a4d4-0b647a8200b7") + ) + (pin "12" + (uuid "8b1b73c1-735e-41c6-9b5d-8fd269120fb3") + ) + (pin "5" + (uuid "cb003231-6199-4ea0-9a66-edf7824ac732") + ) + (pin "1" + (uuid "163eda91-e1d1-4f3a-a87b-24d4fc925754") + ) + (pin "8" + (uuid "15cc4424-7250-4cd6-9552-4aba87faf40f") + ) + (pin "13" + (uuid "c9872dc8-1edf-40be-9ca0-ab3213d698ed") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "U5") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "power:GND") + (at 289.56 167.64 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "dd09d2d9-6d90-4ab1-953a-1440efc594ff") + (property "Reference" "#PWR09" + (at 289.56 173.99 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Value" "GND" + (at 289.56 172.72 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Footprint" "" + (at 289.56 167.64 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "" + (at 289.56 167.64 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Power symbol creates a global label with name \"GND\" , ground" + (at 289.56 167.64 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "1" + (uuid "610d99a9-72c8-4a38-84b6-58eab55d1269") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "#PWR09") + (unit 1) + ) + ) + ) + ) + (symbol + (lib_id "Motor:Motor_Servo") + (at 297.18 151.13 0) + (unit 1) + (body_style 1) + (exclude_from_sim no) + (in_bom yes) + (on_board yes) + (in_pos_files yes) + (dnp no) + (fields_autoplaced yes) + (uuid "e963f9a1-5110-4636-bf71-add7eaa1db95") + (property "Reference" "M3" + (at 306.07 149.4268 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Value" "Motor_Servo" + (at 306.07 151.9668 0) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + (justify left) + ) + ) + (property "Footprint" "" + (at 297.18 155.956 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Datasheet" "http://forums.parallax.com/uploads/attachments/46831/74481.png" + (at 297.18 155.956 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (property "Description" "Servo Motor (Futaba, HiTec, JR connector)" + (at 297.18 151.13 0) + (hide yes) + (show_name no) + (do_not_autoplace no) + (effects + (font + (size 1.27 1.27) + ) + ) + ) + (pin "3" + (uuid "61f67087-64fe-439f-a22d-4e4218d02886") + ) + (pin "2" + (uuid "51e097e9-1c9d-4a0d-bd42-a15c0dd9292b") + ) + (pin "1" + (uuid "f34554f9-71e7-4efa-bc39-b0747af58d38") + ) + (instances + (project "ozon" + (path "/8eb30ef0-8ef0-4108-b9d0-4c3ae489af42" + (reference "M3") + (unit 1) + ) + ) + ) + ) + (sheet_instances + (path "/" + (page "1") + ) + ) + (embedded_fonts no) +) diff --git a/specification/README.md b/specification/README.md new file mode 100644 index 0000000..c8cd77d --- /dev/null +++ b/specification/README.md @@ -0,0 +1,26 @@ +# Наброски будущей схемы конвейера + +![](mvp_1.jpg) + +## Список материалов: + +В этот список необходимо доложить энкодер для двигателя! + +| Название | Сумма, ₽ | Количество | +| :--- | :--- | :--- | +| Медная лента для удаления припоя / Оплетка для выпайки диаметр 2 мм длина 1.5 м | 164 | 1 | +| Набор проводов для пайки | 855 | 1 | +| Флюс гель универсальный безотмывочный, для пайки микросхем и компонентов Flux RMA-223-UV-10г | 162 | 1 | +| Припой для пайки с канифолью 1мм 50гр ПОС-61 на катушке (ГОСТ) | 423 | 1 | +| ALIENTEK Паяльник 140 Вт, 7 предметов | 4 985 | 1 | +| Преобразователь DC-DC понижающий с 8-60V до 1-36V 15A max | 805 | 1 | +| Беспаечная макетная плата (breadboard) MB-102, 830 точек, для Arduino и прочих устройств | 1 112 | 2 | +| 120 шт. Провода перемычки для макетных плат, соединительные провода для модулей, контроллеров arduino (10 см) 3 вида по 40 шт папа-папа, мама-мама, папа-мама | 636 | 2 | +| Homeled, Блок питания, 12V, 200W, 180-265 вольт. С клеммами. Импульсный для светодиодных лент и светильников | 599 | 1 | +| Сервопривод MG996R, 10 шт, 180 , металлические шестерни, для Arduino, роботов и RC, размеры стандарт | 2 990 | 1 | +| PWM PCA9685 драйвер на 16 сервоприводов расширитель портов с I2C интерфейсом для Led и Servo (12 bit, I2C) в IIC/I2C/TWI/SPI c тестером сервоприводов 3 режима, набор | 1 178 | 2 | +| Модуль I2C-мультиплексора CJMCU-9548 на базе TCA9548A / PCA9548A | 532 | 2 | +| Модуль VL53L0X лазерный дальномер GY-530 (до 2м, питание 3-5В, I2C) | 1064 | 4 | +| Модуль ESP32 TYPE-C CH340C + плата расширения ESP32 | 1040 | 2 | + +[Ссылка на товары](https://www.ozon.ru/cart?share=wGhPkQd) diff --git a/specification/mindmap.md b/specification/mindmap.md new file mode 100644 index 0000000..3a02098 --- /dev/null +++ b/specification/mindmap.md @@ -0,0 +1,54 @@ +# Алгоритм работы сортировщика + +```mermaid +mindmap + root((Алгоритм работы)) + Этап 1: Ожидание и Движение + Arduino запускает шаговый двигатель + Лента движется с постоянной скоростью + Этап 2: Детекция и Анализ + Объект проходит под датчиком и камерой + Предмет соответствует габаритам? + Да + Есть признак круга в сечении? + Да - Требуется доупаковка + Секция 2 + Нет - Подходит для сортировки + Секция 3 + Нет + Не подходит по габаритам + Секция 1 + Этап 3: Трекинг Синхронизация + Arduino отсчитывает шаги двигателя + Вычисляется момент времени T + Этап 4: Маршрутизация + В момент T сервопривод поворачивает барьер на 45° + Объект смещается в выбранную зону + Сервопривод возвращается в исходное положение + Этап 5: Завершение + Объект попадает в накопитель + Система возвращается в состояние Ожидание +``` + +## Блок схема: + + +```mermaid +flowchart TD + Start[Этап 1: Ожидание и Движение] --> Detect[Этап 2: Детекция и Анализ] + Detect --> SizeCheck{Предмет соответствует
габаритам?} + + SizeCheck -->|Нет| Reject[Секция 1:
Не габарит] + SizeCheck -->|Да| CircleCheck{Есть признак
круга?} + + CircleCheck -->|Да| Repack[Секция 2:
Требуется доупаковка] + CircleCheck -->|Нет| Sort[Секция 3:
Подходит для сортировки] + + Reject --> Track[Этап 3: Трекинг] + Repack --> Track + Sort --> Track + + Track --> Route[Этап 4: Маршрутизация] + Route --> End[Этап 5: Завершение] + End --> Start +``` \ No newline at end of file diff --git a/specification/mvp_1.drawio b/specification/mvp_1.drawio new file mode 100644 index 0000000..d50949f --- /dev/null +++ b/specification/mvp_1.drawio @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/specification/mvp_1.jpg b/specification/mvp_1.jpg new file mode 100644 index 0000000..5373e55 Binary files /dev/null and b/specification/mvp_1.jpg differ diff --git a/specification/specification.md b/specification/specification.md new file mode 100644 index 0000000..9ef3ed6 --- /dev/null +++ b/specification/specification.md @@ -0,0 +1,97 @@ +Привет! Как инженер-конструктор и эксперт по электронике, я с удовольствием помогу тебе спроектировать этот макет. Задача ясна: создаем систему автоматизированной сортировки с точным управлением скоростью и маршрутизацией по высоте. + +Давай разберем твои вопросы по порядку. + +### 1. Выбор двигателя: Шаговый vs Коллекторный + +Для твоей задачи — **максимально точного выдерживания скорости** и управления с микроконтроллера — выбор очевиден. + +| Характеристика | Коллекторный двигатель (DC) | Шаговый двигатель (Stepper) | +| :--- | :--- | :--- | +| **Точность скорости** | Низкая. Скорость зависит от напряжения и нагрузки. Для точности нужен энкодер (обратная связь). | **Высокая**. Скорость задается частотой импульсов. Постоянна при заданной нагрузке. | +| **Управление** | Простое (PWM/ШИМ). | Требует специальный драйвер (например, A4988 или TMC2208). | +| **Пуск/Стоп** | Плавный разгон, сложно точно позиционировать. | **Идеальный**. Можно точно задать количество шагов и мгновенно остановиться. | +| **Крутящий момент** | Высокий на высоких оборотах. | Высокий на низких скоростях (идеально для ленты). | +| **Сложность реализации** | Очень низкая. | Средняя (нужен драйвер и настройка тока). | + +**Мой вердикт:** Используй **шаговый двигатель (например, NEMA 17)**. +Если ты хочешь «выдерживать скорость максимально точно», обычный DC-мотор тебя разочарует: при увеличении веса объекта на ленте скорость будет падать. Шаговый же двигатель будет поддерживать заданный темп, пока не будет достигнут предел его крутящего момента. + +--- + +### 2. Селекторы (Сервоприводы) + +Использование сервоприводов с барьером под 45° — это классическое и эффективное решение для макета. + +**Советы по реализации:** +* **Механика:** Чтобы объект не застревал и не «зажевывался», сделай барьер обтекаемым или используй гладкий пластик/алюминий. +* **Угол:** 45 градусов — оптимально, но убедись, что при срабатывании сервопривод не толкает объект слишком резко, чтобы тот не перевернулся. +* **Логика:** В промышленном подходе, как отмечено в контексте, важно отслеживать «корректность маршрутизации» [1]. Рекомендую добавить в код проверку: действительно ли объект ушел в нужную зону, или произошла ошибка. + +--- + +### 3. Датчик определения высоты + +Для измерения высоты объекта сверху, когда лента движется, тебе подойдут три варианта в зависимости от требуемой точности: + +1. **Ультразвуковой датчик (HC-SR04):** Самый дешевый и популярный. + * *Минус:* Широкий луч (может «цеплять» борта конвейера) и низкая скорость обновления. +2. **ИК-датчик расстояния (Sharp GP2Y0A21YK0F):** Более точный, узкий луч. + * *Минус:* Аналоговый сигнал, чувствителен к цвету объекта (черный поглощает ИК). +3. **Лазерный ToF-датчик (VL53L0X / VL53L1X):** **Мой выбор для этого проекта.** + * *Плюсы:* Измеряет время полета фотона (Time-of-Flight), работает независимо от цвета объекта, очень высокая точность (до миллиметров) и очень узкий луч. + +--- + +### Итоговая архитектура (предложение): + +* **Контроллер:** Arduino (Uno или Mega, если планируешь расширять количество датчиков). +* **Привод ленты:** Шаговый двигатель NEMA 17 $\rightarrow$ Драйвер A4988/TMC2208 $\rightarrow$ Arduino. +* **Сортировка:** 3 $\times$ Сервоприводы SG90 или MG90S (с металлическим редуктором надежнее). +* **Датчик высоты:** VL53L0X (I2C интерфейс). +* **Питание:** Отдельный блок питания 12В для двигателя и 5В для Arduino/сервоприводов (общий минус/земля). + +**Важное замечание из спецификации:** +Не забудь предусмотреть в логике категорию «Не подходит для сортировки» [1]. Если датчик зафиксирует объект, который либо слишком высокий, либо слишком низкий (выходит за допустимые габариты), система должна отправить его в отдельный поток или оставить на ленте до конца [1]. + +Поскольку мы проектируем макет, важно учитывать не только функциональность, но и ограничения, упомянутые в техническом контексте: доступную площадь участка [1], разнообразие форм товаров (включая круглые в сечении) и необходимость бережного обращения с хрупкими или ценными объектами [1]. + + +# Сервоприводы с барьером — это надежный вариант, но в зависимости от того, какие объекты вы будете сортировать, рассмотрите следующие альтернативы: + +### 1. Электромагнитные толкатели (Solenoids) +Вместо плавного поворота барьера, соленоид дает резкий линейный толчок. +* **Плюсы:** Очень высокая скорость срабатывания, компактность [1]. +* **Минусы:** Резкий удар. Если ваши объекты хрупкие, этот вариант **не рекомендуется**, так как он противоречит требованию к «мягкости обращения» [1]. +* **Когда использовать:** Для прочных, тяжелых или однотипных деталей. + +### 2. Откидная платформа (Tipping Platform) +Это участок конвейера, который может наклоняться в сторону или под ним открывается люк. +* **Плюсы:** Позволяет перемещать объекты за счет гравитации. Это один из самых бережных способов перемещения, что важно для ценных или хрупких товаров [1]. +* **Минусы:** Требует более сложной механической интеграции в саму ленту. +* **Когда использовать:** Если приоритетом является сохранность объекта и минимизация механических ударов. + +### 3. Поворотный диск-распределитель (Rotating Disc) +В конце основной ленты устанавливается диск, который поворачивается на определенный угол, направляя объект в один из трех лотков. +* **Плюсы:** Занимает минимум места (оптимально для ограниченной площади участка [1]). Отлично работает с объектами, имеющими круг в сечении [1]. +* **Минусы:** Требует точной синхронизации остановки ленты и поворота диска. +* **Когда использовать:** Если макет должен быть максимально компактным, а объекты имеют схожую форму (например, цилиндры или сферы). + +### 4. Боковой конвейер (Cross-Belt / Diverter) +Это короткий перпендикулярный отрезок ленты, который включается только тогда, когда объект должен быть сброшен в зону. +* **Плюсы:** Самый профессиональный и «мягкий» метод. Идеален для хрупких товаров [1]. +* **Минусы:** Высокая стоимость и сложность (нужно еще 3 маленьких мотора и дополнительные ленты). +* **Когда использовать:** Если вы хотите создать максимально приближенный к реальному промышленному производству макет. + +### Сводная таблица для выбора: + +| Вариант | Бережность (для хрупких [1]) | Компактность (площадь [1]) | Сложность реализации | Работа с круглыми объектами [1] | +| :--- | :--- | :--- | :--- | :--- | +| **Серво-барьер** | Средняя | Высокая | Низкая | Хорошо | +| **Соленоид** | Низкая | Очень высокая | Низкая | Средне | +| **Платформа** | Высокая | Средняя | Средняя | Хорошо | +| **Диск** | Средняя | Очень высокая | Средняя | Идеально | +| **Боковой конвейер**| Очень высокая | Низкая | Высокая | Хорошо | + +**Моя рекомендация:** +Если вы хотите сохранить простоту управления через Arduino, но при этом учесть требования к бережности и компактности [1], я рекомендую остаться на **сервоприводах**, но изменить конструкцию барьера: вместо «толкателя» сделать **«поворотный желоб»** или **«шлюз»**. Это позволит объекту плавно скатываться в нужную зону, не подвергаясь резким ударам. \ No newline at end of file