Лабораторна робота 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 | не потрібен | Рух подавача та перепідключення заборонені в цій редакції |
Необхідні знання та матеріали
- цей файл, програма ЛР-07 і перелік непідтверджених параметрів модулів;
- правила експлуатації та політика допуску студентів;
- Python зі стандартними модулями
dataclasses,enumіjson; - текстовий редактор і термінал;
- прошивка
codes/pusher/pusher.ino— для читання й аналізу протоколу, не для перепрошивання контролера; - паспорт модуля Pusher і паспорт Parts Feeder;
- симуляційний сценарій
feeder_jamуdata/fault-scenarios.json.
Обладнання для обов'язкової частини не використовується. Не потрібні 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°, циліндр 0°, куб 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-адрес, паролів, апаратних команд і прихованого автозапуску.
- Початковий запас і сценарій відмови задані у тесті.
- Тайм-аут додатний, а повтор після помилки не виконується автоматично.
- Термінал і каталог для результатів не містять персональних даних.
- Фізичний подавач не під'єднаний до процесу.
Хід виконання роботи
- Відокремити еталонний API від непідтвердженої апаратної реалізації.
- Реалізувати детермінований mock та автомат станів.
- Перевірити штатну видачу однієї деталі.
- Перевірити порожній запас, заклинювання й подвійну подачу.
- Перевірити граничні значення та правила скидання помилки.
- Заповнити таблиці вимірювань, паспорт модуля й звіт.
Методичні вказівки й теоретичні відомості
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, три фіксовані кути (0°, 90°, 180°) і текстовий serial-протокол на 9600 бод.
Що з неї можна взяти як підтверджене: набір команд, формат відповідей OK … / ERR UNKNOWN: …, придушення брязкоту кнопок і повернення у центральне положення після кожної видачі.
Що вона не доводить: наявність зворотного зв'язку про фактичну видачу деталі. Контролер відповідає OK CYL одразу після запису кута — це підтвердження прийняття команди, а не підтвердження результату. Ваш Python-модуль повинен трактувати відповідь саме так і мати окрему ознаку успішної подачі (наприклад, спрацювання сенсора на конвеєрі) із власним тайм-аутом.
3. Автомат станів
Текстовий опис рисунка: зі стану IDLE валідний запит переводить подавач у FEEDING; рівно одне підтвердження веде через COMPLETE назад до очікування, відсутній запас — у EMPTY, а тайм-аут чи подвійна подача — у заблокований FAULT, вихід з якого можливий лише після усунення причини та ручного підтвердження.
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:
{
"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. Першоджерела
- Python:
dataclassesта Python:enum— офіційна документація мови для структури mock. - Arduino Servo library — офіційний опис бібліотеки, згаданої в історичному файлі; він не підтверджує параметри MERC-I5.
- Програма MERC-I5, вихідні дані і правила безпеки — локальні джерела істини щодо обсягу й обмежень.
Виконання лабораторної роботи
Крок 1. Зафіксувати межі й контракт
Створіть робочий каталог, випишіть дозволені методи issue_one(), safe_stop() і reset_fault(), а також стани з рис. 1. Не додавайте апаратний backend.
Вхід: запит на одну деталь + стан готовності + запас + сигнали відмови
Вихід: номер виданої деталі або контрольована помилка
Безпечний стан: actuator_on=False; автоматичний повтор заборонено
Очікуваний результат: односторінковий контракт без pin, напруг, кутів і фізичних команд.
Критерій правильності: для кожної операції визначені передумова, результат, помилка й безпечна реакція.
Якщо результат не отримано: поверніться до таблиці контракту; невідомий апаратний параметр не вигадуйте, а віднесіть до попереджувального блока.
Крок 2. Реалізувати й запустити повний mock
Збережіть блок як lab07_feeder_mock.py і виконайте його тільки локально або в Colab.
# 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))
Команди перевірки:
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.
Запитів: 1
Підтверджених деталей: 1
Зміна запасу: -1
Автоматичний повтор: 0
Очікуваний результат: усі чотири інваріанти виконані.
Критерій правильності: номер видачі 1, запас 1, стан IDLE, вихід неактивний.
Якщо результат не отримано: зупиніть аналіз на першому порушеному інваріанті; не послаблюйте перевірки, щоб «пропустити» тест.
Крок 4. Перевірити негативні сценарії
Зіставте тести empty, jam і double з очікуваною реакцією.
| Сценарій | Очікуваний стан | Вихід | Автоматичне продовження |
|---|---|---|---|
| запас 0 | EMPTY | вимкнений | ні |
| немає підтвердження | FAULT | вимкнений | ні |
| подвійне підтвердження | FAULT | вимкнений | ні |
Очікуваний результат: усі відмови виявлено до наступної видачі.
Критерій правильності: після FAULT наступна команда не виконується без успішного reset_fault().
Якщо результат не отримано: викличте safe_stop("невідома розбіжність"), зафіксуйте стан і не додавайте автоматичних повторів.
Крок 5. Перевірити граничні умови й відновлення
Поясніть, чому stock=-1, timeout_s=0, скидання з активним jam і скидання без operator_ack мають відхилятися. За бажанням додайте твердження для верхньої межі timeout_s=30.0 і значення понад неї.
python lab07_feeder_mock.py
Очікуваний результат: граничні валідні значення прийняті, невалідні — завершуються контрольованою помилкою; апаратних дій немає.
Критерій правильності: жодна невалідна конфігурація не переходить у FEEDING.
Якщо результат не отримано: додайте перевірку в __post_init__ або перед переходом стану; не маскуйте виняток загальним except.
Крок 6. Оформити паспорт і вимірювання
Заповніть підтверджені програмні поля; апаратні поля залиште як «не підтверджено», посилаючись на адресний блок вище.
| Поле паспорта | Результат цієї редакції |
|---|---|
| Призначення | видача рівно однієї деталі |
| Python API | issue_one, safe_stop, reset_fault |
| Конфігурація | stock, timeout_s, ін'єкції відмов mock |
| Безпечний стан mock | вихід False, FAULT/EMPTY, без автоповтору |
| Фізична модель/інтерфейс | не підтверджено |
| Апаратна перевірка | не виконувалася |
Очікуваний результат: паспорт не змішує перевірений mock із непідтвердженим обладнанням.
Критерій правильності: кожне числове значення має джерело або явно назване симуляційним.
Якщо результат не отримано: вилучіть припущені pin, кути, напруги й час; перенесіть їх у перелік даних, які треба виміряти.
Сценарії перевірки
| Тип | Вхідні умови | Очікування | Метрика |
|---|---|---|---|
| позитивний | запас 2, готовність True | видано 1, запас 1, IDLE | issued=1 |
| негативний | запас 0 | EMPTY, вихід вимкнений | 0 рухових запитів |
| негативний | jammed=True | FAULT, ручне відновлення | 0 автоповторів |
| негативний | double_feed=True | FAULT | видано 0 підтверджених одиниць |
| граничний | timeout_s=0 | конфігурацію відхилено | ValueError |
| граничний | скидання без підтвердження | скидання відхилено | стан лишається FAULT |
Таблиці вимірювань і метрики
Використовуйте монотонний таймер лише для продуктивності mock; він не є фізичним часом подачі.
| Запуск | Сценарій | Запити | Успішні видачі | Відмови | Автоповтори | Кінцевий стан | Час mock, ms |
|---|---|---|---|---|---|---|---|
| 1 | штатний | 0 | |||||
| 2 | порожній | 0 | |||||
| 3 | jam | 0 | |||||
| 4 | double | 0 |
Обов'язкові метрики: частка пройдених тестів, кількість неконтрольованих винятків (має бути 0), кількість автоматичних повторів після відмови (має бути 0) і кількість випадків активного виходу після завершення (має бути 0).
[!WARNING] [ПОТРЕБУЄ ДОПРАЦЮВАННЯ: АПАРАТНА АПРОБАЦІЯ ПОДАВАЧА] Не підтверджено: фактичний час одного циклу, повторюваність видачі, пороги тайм-ауту, ознаки заклинювання, здатність виявляти подвійну подачу та реальний безпечний стан; фізична апробація в цьому чаті не дозволена. Що потрібно додати: затверджений протокол випробування, відповідального оператора, перевірений драйвер і схему, журнал щонайменше серії штатних циклів та контрольованих безпечних відмов без навмисного короткого замикання. Як завершити: після окремого дозволу виконати спершу read-only перевірки, потім покроковий запуск на мінімальній затвердженій швидкості; звірити один запит/одну деталь, тайм-аут і знеструмлений стан; додати датований журнал та видалити блок лише при проходженні критеріїв.
Вимоги до звіту
Дотримайтеся загальних вимог. Додатково подайте:
- автомат станів і контракт API;
- повний текст
lab07_feeder_mock.py; - JSON-результат smoke-тесту й таблицю всіх сценаріїв;
- пояснення, чому відповідь
OK CYLвід прошивки не є підтвердженням фактичної видачі деталі; - паспорт mock-модуля та перелік даних для апаратного backend;
- явну фразу: «На фізичному обладнанні не перевірялося».
Критерії оцінювання
| Складова | Частка | Критерії |
|---|---|---|
| Підготовка | 10% | межі роботи, safety-чекліст, джерела й відсутність непідтверджених параметрів |
| Реалізація | 40% | автомат станів, рівно одна видача, безпечний стан, ручне відновлення, читабельний mock |
| Перевірка | 25% | позитивний, три негативні та граничні тести; відтворюваний код 0 |
| Аналіз | 15% | інваріанти, причини відмов, обмеження історичного ескізу й метрики |
| Звіт | 10% | паспорт, таблиці, джерела, висновки й чесний статус фізичної перевірки |
| Разом | 100% |
Критична помилка безпеки — апаратний запуск, пряме підключення або автоматичне скидання невідомої відмови — означає, що практична частина не зараховується до усунення порушення.
Контрольні питання
- Чому команда подавача є транзакцією, а не лише імпульсом?
- Яка різниця між
EMPTYіFAULT? - Як виявити подвійну подачу, не знаючи конкретного типу датчика?
- Чому розрив зв'язку не дозволяє автоматично повторити видачу?
- Які умови повинні бути виконані перед
reset_fault()? - Чому pin і кути з історичного Arduino-файла не можна переносити у драйвер MERC-I5?
- Які факти потрібні, щоб визначити фізичний безпечний стан?
- Чим mock-тест відрізняється від доказу апаратної безпеки?
Висновки
У роботі створюється відтворюваний, апаратно незалежний контракт подавача з контрольованими станами, захистом від порожнього запасу, подвійної подачі й заклинювання. Фізичні pin, кути, сигнали, тайм-аути та результати не припускаються: їхня відсутність зафіксована безпосередньо біля заблокованих фрагментів. До появи паспорта, електричної схеми й окремого дозволу результатом ЛР є лише перевірений mock, а не драйвер фізичного MERC-I5.