MERCI SPACE
Навчальні матеріали

Лабораторна робота 7

Лабораторна робота №07. Python-модуль подавача деталей

Мета: навчитися проєктувати безпечний інтерфейс подавача деталей; реалізувати видачу рівно однієї деталі, контроль готовності, запасу, подвійної подачі, тайм-ауту й заклинювання; перевірити переходи у безпечний стан за допомогою самодостатнього mock без підключення до MERC-I5.

Результати навчання та передумови

Після виконання роботи студент уміє:

  • відокремлювати логіку подавача від конкретних GPIO, сервопривода й протоколу;
  • описувати стани IDLE, FEEDING, COMPLETE, EMPTY і FAULT;
  • видавати одну деталь лише після перевірки передумов;
  • виявляти заклинювання, подвійну подачу й недопустимий тайм-аут;
  • переводити вихід виконавчого механізму в неактивний стан при будь-якій невизначеності;
  • скидати помилку лише після усунення причини та явного підтвердження;
  • оформлювати паспорт модуля й відтворюваний журнал тестів.

Передумови: базові знання Python (dataclass, винятки, Enum), автоматів станів, дискретних сигналів і матеріал ЛР-03–06. Фізичний допуск для цієї редакції роботи не потрібний.

Середовище виконання

СередовищеСтатусПримітка
Google ColabдозволеноЛише самодостатня симуляція зі стандартною бібліотекою Python
Локальний ПКосновнеДля наведеного mock потрібен Python 3.10+; це вимога синтаксису, не паспорт ПЗ стенда
Raspberry PiдозволеноЛише mock, без GPIO, UART, USB і виконавчих виходів
Фізичний комплекс MERC-I5не потрібенРух подавача та перепідключення заборонені в цій редакції

Необхідні знання та матеріали

Обладнання для обов'язкової частини не використовується. Не потрібні Arduino, Raspberry Pi GPIO, сервопривід, блок живлення чи фізична деталь.

Підтверджений інтерфейс подавача Pusher

Джерело: прошивка codes/pusher/pusher.ino і паспорт модуля.

ПараметрЗначення
ТранспортUSB (Serial), 9600 бод, рядки із завершенням \n
Команда «циліндр»CYL або CYLINDER → відповідь OK CYL
Команда «куб»CUBE → відповідь OK CUBE
Повернення в центрCENTER або HOME → відповідь OK CENTER
Довільний кутSET <0…180> → відповідь OK SET <кут>
Невідома командавідповідь ERR UNKNOWN: <текст>
Кути сервоприводацентр 90°, циліндр , куб 180°
Витримка після рухуDWELL_MS = 250 мс
Піни контролерасерво D2, кнопка CYL D4, кнопка CUBE D5, режим INPUT_PULLUP (натиснуто = LOW)
Придушення брязкотуDEBOUNCE_MS = 30 мс
Живлення сервоприводаокреме 5–6 В, спільна земля з контролером

Ці значення підтверджені прошивкою. Прошивка не підтверджує: модель контролера й ревізію подавача, фактичну модель сервопривода на конкретному екземплярі, наявність зворотного зв'язку про фактичне положення та поведінку при заклинюванні.

Ризики та правила безпеки

Ризики

НебезпекаМожливий наслідокЗапобіжний захід у цій роботі
Несподіваний рух штовхача або заслінкизащемлення, удар, пошкодження деталітільки mock; жодного імпорту GPIO/serial і жодного фізичного виходу
Подвійна подачазаклинювання наступного модуляодна підтверджена подія на один запит; розбіжність переводить mock у FAULT
Втрата сигналу або тайм-аутневідоме положення деталідеактивація виходу, помилка й заборона автоматичного продовження
Непідтверджене живленняпошкодження Raspberry Pi або модуляне підключати; 24 V і 5 V не подавати на GPIO 3,3 V

Заборонені дії

  • запускати або переносити на контролер codes/pusher/pusher.ino;
  • вважати SERVO_PIN 2, кути 0/90/180°, 115200 бод або затримку 250 ms параметрами MERC-I5;
  • подавати живлення, змінювати проводку, рухати механізм вручну чи обходити блокування;
  • автоматично скидати FAULT або повторювати команду після тайм-ауту;
  • створювати електричний обрив чи коротке замикання на штатному комплексі.

Умови негайної зупинки

Для майбутньої фізичної апробації зупинка обов'язкова при людині в робочій зоні, невідомому положенні механізму, втраті зв'язку, суперечливих датчиках, подвійному виробі, заклинюванні, незвичному звуку/нагріві або пошкодженому кабелі. У симуляції будь-яка неочікувана відповідь, виняток або зависання означає: завершити процес, не переходити до наступного кроку, зберегти журнал.

Безпечний стан

У моделі безпечний стан означає: команда привода неактивна, нові видачі заборонені, причина відмови збережена, стан FAULT або EMPTY однозначний, а відновлення потребує усунення причини й явного підтвердження. Це навчальне визначення не доводить фактичний безпечний стан подавача MERC-I5.

Передпусковий чекліст

  • Виконується саме mock-файл без бібліотек GPIO, serial або мережевих сокетів.
  • У коді немає pin, IP-адрес, паролів, апаратних команд і прихованого автозапуску.
  • Початковий запас і сценарій відмови задані у тесті.
  • Тайм-аут додатний, а повтор після помилки не виконується автоматично.
  • Термінал і каталог для результатів не містять персональних даних.
  • Фізичний подавач не під'єднаний до процесу.

Хід виконання роботи

  1. Відокремити еталонний API від непідтвердженої апаратної реалізації.
  2. Реалізувати детермінований mock та автомат станів.
  3. Перевірити штатну видачу однієї деталі.
  4. Перевірити порожній запас, заклинювання й подвійну подачу.
  5. Перевірити граничні значення та правила скидання помилки.
  6. Заповнити таблиці вимірювань, паспорт модуля й звіт.

Методичні вказівки й теоретичні відомості

1. Контракт подавача

Команда issue_one() є транзакцією, а не коротким імпульсом: перед нею перевіряють готовність і запас; під час неї очікують рівно одне підтвердження; після неї фіксують завершення. Нуль підтверджень до тайм-ауту — заклинювання або втрата сигналу, більше одного — подвійна подача. В обох випадках автоматичне продовження небезпечне.

ПередумоваУспішний результатНегативний результатРеакція
ready=True, запас > 0рівно одна подія, запас зменшено на 1немає подіївихід вимкнено, FAULT
одна подіяCOMPLETE, потім IDLEдві подіївихід вимкнено, FAULT
причина відмови усуненаручний reset_fault()причина активнаскидання відхилено

2. Чому історичний Arduino-файл не є паспортом

Файл codes/pusher/pusher.ino — це штатна прошивка подавача Pusher. Вона використовує бібліотеку Arduino Servo, серво на піні D2, кнопки на D4/D5 у режимі INPUT_PULLUP, три фіксовані кути (, 90°, 180°) і текстовий serial-протокол на 9600 бод.

Що з неї можна взяти як підтверджене: набір команд, формат відповідей OK … / ERR UNKNOWN: …, придушення брязкоту кнопок і повернення у центральне положення після кожної видачі.

Що вона не доводить: наявність зворотного зв'язку про фактичну видачу деталі. Контролер відповідає OK CYL одразу після запису кута — це підтвердження прийняття команди, а не підтвердження результату. Ваш Python-модуль повинен трактувати відповідь саме так і мати окрему ознаку успішної подачі (наприклад, спрацювання сенсора на конвеєрі) із власним тайм-аутом.

3. Автомат станів

Текстовий опис рисунка: зі стану IDLE валідний запит переводить подавач у FEEDING; рівно одне підтвердження веде через COMPLETE назад до очікування, відсутній запас — у EMPTY, а тайм-аут чи подвійна подача — у заблокований FAULT, вихід з якого можливий лише після усунення причини та ручного підтвердження.

Mermaid
flowchart LR
    A["IDLE: вихід неактивний"] -->|"issue_one + передумови OK"| B["FEEDING"]
    B -->|"рівно одна подія"| C["COMPLETE"]
    C --> A
    A -->|"запас = 0"| D["EMPTY"]
    B -->|"тайм-аут / 0 або >1 подія"| E["FAULT: вихід неактивний"]
    E -->|"причину усунено + ручне підтвердження"| A

Рис. 1. Ілюстративний автомат станів безапаратного подавача

4. Конфігурація mock поза кодом

Runtime-реалізація має читати несекретні параметри з окремого файла, наприклад feeder.mock.json:

JSON
{
  "backend": "mock",
  "stock": 2,
  "timeout_s": 1.0,
  "fault_injection": {
    "jammed": false,
    "double_feed": false
  }
}

Значення, створені безпосередньо в safe-smoke коді нижче, є лише детермінованими test fixtures. У навчальному runtime JSON потрібно завантажити через json.loads(Path(...).read_text(encoding="utf-8")), відхилити зайві ключі та передати перевірені значення в FeederConfig/MockFeeder; файл фізичного профілю не створюють до підтвердження сигналів.

5. Першоджерела

Виконання лабораторної роботи

Крок 1. Зафіксувати межі й контракт

Створіть робочий каталог, випишіть дозволені методи issue_one(), safe_stop() і reset_fault(), а також стани з рис. 1. Не додавайте апаратний backend.

Text
Вхід: запит на одну деталь + стан готовності + запас + сигнали відмови
Вихід: номер виданої деталі або контрольована помилка
Безпечний стан: actuator_on=False; автоматичний повтор заборонено

Очікуваний результат: односторінковий контракт без pin, напруг, кутів і фізичних команд.

Критерій правильності: для кожної операції визначені передумова, результат, помилка й безпечна реакція.

Якщо результат не отримано: поверніться до таблиці контракту; невідомий апаратний параметр не вигадуйте, а віднесіть до попереджувального блока.

Крок 2. Реалізувати й запустити повний mock

Збережіть блок як lab07_feeder_mock.py і виконайте його тільки локально або в Colab.

Python
# safe-smoke
from __future__ import annotations

import json
from dataclasses import dataclass
from enum import Enum


class FeederState(str, Enum):
    IDLE = "IDLE"
    FEEDING = "FEEDING"
    COMPLETE = "COMPLETE"
    EMPTY = "EMPTY"
    FAULT = "FAULT"


class FeederFault(RuntimeError):
    pass


@dataclass(frozen=True)
class FeederConfig:
    timeout_s: float = 1.0

    def __post_init__(self) -> None:
        if not 0.0 < self.timeout_s <= 30.0:
            raise ValueError("timeout_s має бути в межах (0, 30]")


class MockFeeder:
    """Детермінований mock: не імпортує GPIO/serial і нічого не рухає."""

    def __init__(self, stock: int, config: FeederConfig | None = None) -> None:
        if stock < 0:
            raise ValueError("stock не може бути від'ємним")
        self.config = config or FeederConfig()
        self.stock = stock
        self.state = FeederState.IDLE if stock else FeederState.EMPTY
        self.ready = True
        self.jammed = False
        self.double_feed = False
        self.actuator_on = False
        self.issued = 0
        self.last_error: str | None = None

    def _fail(self, message: str) -> None:
        self.actuator_on = False
        self.state = FeederState.FAULT
        self.last_error = message
        raise FeederFault(message)

    def issue_one(self) -> int:
        if self.state == FeederState.FAULT:
            raise FeederFault("спочатку усуньте причину й виконайте ручне скидання")
        if not self.ready:
            self._fail("подавач не готовий")
        if self.stock == 0:
            self.actuator_on = False
            self.state = FeederState.EMPTY
            raise FeederFault("запас порожній")

        self.state = FeederState.FEEDING
        self.actuator_on = True
        if self.jammed:
            self._fail("тайм-аут: підтвердження деталі відсутнє")
        if self.double_feed:
            self._fail("отримано більше одного підтвердження")

        self.stock -= 1
        self.issued += 1
        result = self.issued
        self.actuator_on = False
        self.state = FeederState.COMPLETE
        self.state = FeederState.IDLE if self.stock else FeederState.EMPTY
        return result

    def safe_stop(self, reason: str) -> None:
        self.actuator_on = False
        self.state = FeederState.FAULT
        self.last_error = reason

    def reset_fault(self, *, cause_removed: bool, operator_ack: bool) -> None:
        if self.state != FeederState.FAULT:
            raise FeederFault("скидання дозволене лише зі стану FAULT")
        if self.jammed or self.double_feed or not cause_removed or not operator_ack:
            raise FeederFault("причина активна або немає явного підтвердження")
        self.last_error = None
        self.state = FeederState.IDLE if self.stock else FeederState.EMPTY


def expect_fault(action, fragment: str) -> None:
    try:
        action()
    except FeederFault as exc:
        assert fragment in str(exc)
    else:
        raise AssertionError("очікувався FeederFault")


normal = MockFeeder(stock=2)
assert normal.issue_one() == 1
assert normal.stock == 1 and not normal.actuator_on

empty = MockFeeder(stock=0)
expect_fault(empty.issue_one, "запас")
assert empty.state == FeederState.EMPTY and not empty.actuator_on

jam = MockFeeder(stock=1)
jam.jammed = True
expect_fault(jam.issue_one, "тайм-аут")
expect_fault(lambda: jam.reset_fault(cause_removed=False, operator_ack=True), "причина")
jam.jammed = False
jam.reset_fault(cause_removed=True, operator_ack=True)
assert jam.issue_one() == 1

double = MockFeeder(stock=1)
double.double_feed = True
expect_fault(double.issue_one, "більше одного")
assert double.state == FeederState.FAULT and not double.actuator_on

try:
    FeederConfig(timeout_s=0.0)
except ValueError:
    pass
else:
    raise AssertionError("нульовий тайм-аут має бути відхилений")

print(json.dumps({"checks": 6, "result": "PASS", "hardware": False}, ensure_ascii=False))

Команди перевірки:

Bash
python -m py_compile lab07_feeder_mock.py
python lab07_feeder_mock.py

Очікуваний результат: один JSON-рядок із "result": "PASS", "checks": 6 і "hardware": false.

Критерій правильності: процес завершується з кодом 0; усі твердження виконані; немає мережі, GPIO, serial, пауз або вводу користувача.

Якщо результат не отримано: не підключайте подавач; збережіть traceback, перевірте, що блок скопійовано повністю, і виправте лише mock.

Крок 3. Проаналізувати позитивний сценарій

Простежте вручну перший тест: IDLE → FEEDING → COMPLETE → IDLE, запас 2 → 1, actuator_on після завершення дорівнює False.

Text
Запитів: 1
Підтверджених деталей: 1
Зміна запасу: -1
Автоматичний повтор: 0

Очікуваний результат: усі чотири інваріанти виконані.

Критерій правильності: номер видачі 1, запас 1, стан IDLE, вихід неактивний.

Якщо результат не отримано: зупиніть аналіз на першому порушеному інваріанті; не послаблюйте перевірки, щоб «пропустити» тест.

Крок 4. Перевірити негативні сценарії

Зіставте тести empty, jam і double з очікуваною реакцією.

СценарійОчікуваний станВихідАвтоматичне продовження
запас 0EMPTYвимкненийні
немає підтвердженняFAULTвимкненийні
подвійне підтвердженняFAULTвимкненийні

Очікуваний результат: усі відмови виявлено до наступної видачі.

Критерій правильності: після FAULT наступна команда не виконується без успішного reset_fault().

Якщо результат не отримано: викличте safe_stop("невідома розбіжність"), зафіксуйте стан і не додавайте автоматичних повторів.

Крок 5. Перевірити граничні умови й відновлення

Поясніть, чому stock=-1, timeout_s=0, скидання з активним jam і скидання без operator_ack мають відхилятися. За бажанням додайте твердження для верхньої межі timeout_s=30.0 і значення понад неї.

Bash
python lab07_feeder_mock.py

Очікуваний результат: граничні валідні значення прийняті, невалідні — завершуються контрольованою помилкою; апаратних дій немає.

Критерій правильності: жодна невалідна конфігурація не переходить у FEEDING.

Якщо результат не отримано: додайте перевірку в __post_init__ або перед переходом стану; не маскуйте виняток загальним except.

Крок 6. Оформити паспорт і вимірювання

Заповніть підтверджені програмні поля; апаратні поля залиште як «не підтверджено», посилаючись на адресний блок вище.

Поле паспортаРезультат цієї редакції
Призначеннявидача рівно однієї деталі
Python APIissue_one, safe_stop, reset_fault
Конфігураціяstock, timeout_s, ін'єкції відмов mock
Безпечний стан mockвихід False, FAULT/EMPTY, без автоповтору
Фізична модель/інтерфейсне підтверджено
Апаратна перевіркане виконувалася

Очікуваний результат: паспорт не змішує перевірений mock із непідтвердженим обладнанням.

Критерій правильності: кожне числове значення має джерело або явно назване симуляційним.

Якщо результат не отримано: вилучіть припущені pin, кути, напруги й час; перенесіть їх у перелік даних, які треба виміряти.

Сценарії перевірки

ТипВхідні умовиОчікуванняМетрика
позитивнийзапас 2, готовність Trueвидано 1, запас 1, IDLEissued=1
негативнийзапас 0EMPTY, вихід вимкнений0 рухових запитів
негативнийjammed=TrueFAULT, ручне відновлення0 автоповторів
негативнийdouble_feed=TrueFAULTвидано 0 підтверджених одиниць
граничнийtimeout_s=0конфігурацію відхиленоValueError
граничнийскидання без підтвердженняскидання відхиленостан лишається FAULT

Таблиці вимірювань і метрики

Використовуйте монотонний таймер лише для продуктивності mock; він не є фізичним часом подачі.

ЗапускСценарійЗапитиУспішні видачіВідмовиАвтоповториКінцевий станЧас mock, ms
1штатний0
2порожній0
3jam0
4double0

Обов'язкові метрики: частка пройдених тестів, кількість неконтрольованих винятків (має бути 0), кількість автоматичних повторів після відмови (має бути 0) і кількість випадків активного виходу після завершення (має бути 0).

[!WARNING] [ПОТРЕБУЄ ДОПРАЦЮВАННЯ: АПАРАТНА АПРОБАЦІЯ ПОДАВАЧА] Не підтверджено: фактичний час одного циклу, повторюваність видачі, пороги тайм-ауту, ознаки заклинювання, здатність виявляти подвійну подачу та реальний безпечний стан; фізична апробація в цьому чаті не дозволена. Що потрібно додати: затверджений протокол випробування, відповідального оператора, перевірений драйвер і схему, журнал щонайменше серії штатних циклів та контрольованих безпечних відмов без навмисного короткого замикання. Як завершити: після окремого дозволу виконати спершу read-only перевірки, потім покроковий запуск на мінімальній затвердженій швидкості; звірити один запит/одну деталь, тайм-аут і знеструмлений стан; додати датований журнал та видалити блок лише при проходженні критеріїв.

Вимоги до звіту

Дотримайтеся загальних вимог. Додатково подайте:

  1. автомат станів і контракт API;
  2. повний текст lab07_feeder_mock.py;
  3. JSON-результат smoke-тесту й таблицю всіх сценаріїв;
  4. пояснення, чому відповідь OK CYL від прошивки не є підтвердженням фактичної видачі деталі;
  5. паспорт mock-модуля та перелік даних для апаратного backend;
  6. явну фразу: «На фізичному обладнанні не перевірялося».

Критерії оцінювання

СкладоваЧасткаКритерії
Підготовка10%межі роботи, safety-чекліст, джерела й відсутність непідтверджених параметрів
Реалізація40%автомат станів, рівно одна видача, безпечний стан, ручне відновлення, читабельний mock
Перевірка25%позитивний, три негативні та граничні тести; відтворюваний код 0
Аналіз15%інваріанти, причини відмов, обмеження історичного ескізу й метрики
Звіт10%паспорт, таблиці, джерела, висновки й чесний статус фізичної перевірки
Разом100%

Критична помилка безпеки — апаратний запуск, пряме підключення або автоматичне скидання невідомої відмови — означає, що практична частина не зараховується до усунення порушення.

Контрольні питання

  1. Чому команда подавача є транзакцією, а не лише імпульсом?
  2. Яка різниця між EMPTY і FAULT?
  3. Як виявити подвійну подачу, не знаючи конкретного типу датчика?
  4. Чому розрив зв'язку не дозволяє автоматично повторити видачу?
  5. Які умови повинні бути виконані перед reset_fault()?
  6. Чому pin і кути з історичного Arduino-файла не можна переносити у драйвер MERC-I5?
  7. Які факти потрібні, щоб визначити фізичний безпечний стан?
  8. Чим mock-тест відрізняється від доказу апаратної безпеки?

Висновки

У роботі створюється відтворюваний, апаратно незалежний контракт подавача з контрольованими станами, захистом від порожнього запасу, подвійної подачі й заклинювання. Фізичні pin, кути, сигнали, тайм-аути та результати не припускаються: їхня відсутність зафіксована безпосередньо біля заблокованих фрагментів. До появи паспорта, електричної схеми й окремого дозволу результатом ЛР є лише перевірений mock, а не драйвер фізичного MERC-I5.

MERCI SPACE