Протокол на конектора на Edge
Конекторът на Edge използва същия плик (envelope), hello, сигнал за
активност и жизнен цикъл на задачите като агентите и конекторите на Axis
(Протокол на агента и конектора) и добавя съобщенията и
задачите за контрол на достъпа. Go типовете в
entrosity-shared-go/proto/edge са единственият източник на истина; тази
страница описва предназначението и поведението.
Както останалата част от протокола, тези типове само се разширяват: полета се добавят, но никога не се преименуват или пренасочват, за да продължат да работят конекторите на място.
Транспорт
- WebSocket:
wss://hub.entrosity.com/edge/api/connector/v1/wsсAuthorization: Bearer <connector key>. JSON текстови рамки, по един плик в рамка, най-много 1 MiB; сървърът изпраща ping на всеки 30 секунди. - Първото съобщение трябва да е
hello(capabilities: ["access_control", "edge_update"],pending_job_ids); сървърът отговаря сhello.ackи след това доставя чакащите задачи.edge_updateозначава, че конекторът инсталира самообновявания (Самообновяване); на конектори без нея никога не се предлага обновяване. Всичко предиhelloзатваря връзката (1008). - Конекторът изпраща
heartbeatвсяка минута. Той е онлайн от своетоhello, докато връзката не се затвори или след 3 минути без съобщение. - Задачите следват
job.assign→job.ack→job.progress→job.result. Задача, изпратена безjob.ackдо 60 секунди, се изпраща отново; задача, неприключила преди срока си, ставаtimeout. - Кодове за затваряне:
4003ключът е отменен (конекторът е премахнат, тенантът е спрян);4009по-нова връзка на същата инсталация е поела;1008нарушение на правилата.
HTTP крайни точки (/api/connector/v1)
| Метод и път | Предназначение |
|---|---|
POST /enroll | {enrollment_token, hostname, domain, version} → {connector_id, connector_key, ws_url}. Токенът трябва да е токен за конектор на Edge. Същият компютър (тенант, име на хост, домейн), регистриран отново, сменя ключа си и затваря старата връзка. Ограничено за IP адрес (EDGE_ENROLL_RATE_PER_MINUTE). |
GET /ws | WebSocket връзката. |
POST /heartbeat | HTTP резервен вариант на heartbeat. |
GET /jobs/pending, POST /jobs/{id}/ack, …/progress, …/result | HTTP резервен вариант (polling) на жизнения цикъл на задачите. |
POST /events | HTTP резервен вариант на edge.events: тялото е EventsChunk (приема се gzip; 4 MiB компресирано, 8 MiB разкомпресирано), отговорът е EventsAck. |
POST /controllers/status | HTTP резервен вариант на edge.controller.status. |
GET /releases/{id}/msi?exp=&sig= | Изтегляне на MSI файла на издание на конектора по подписаната връзка от задача update_agent. Без ключ на конектора: връзката (валидна 24 часа, подписана с ключ, извлечен от EDGE_MASTER_KEY) е разрешението; изтекла или променена връзка получава 403 download_link_invalid. |
Всички освен /enroll и изтеглянето на издания изискват ключа на конектора. Всичко, което прави
конекторът, е ограничено в неговия тенант.
Съобщения
Конектор → сървър
| Тип | Payload | Кога |
|---|---|---|
edge.events | {first_seq, events: [Event]}: 1–500 последователни събития (events[i].seq = first_seq + i) | Когато опашката на конектора има събития. |
edge.controller.status | {controllers: [{controller_id, online, applied_version, applied_hash?, error?, doors?: [{index, open, locked}], info?: ControllerInfo}]} (до 1000 контролера) | Поне всяка минута и веднага след промяна. |
Сървър → конектор
| Тип | Payload | Кога |
|---|---|---|
edge.events.ack | {acked_seq}, в отговор (reply_to) на edge.events | Всяко събитие до acked_seq е съхранено; конекторът ги премахва от опашката си. |
Задачи
| Задача | Payload | Резултат | Време за изпълнение / срок | Лента на конектора |
|---|---|---|---|---|
edge.config.apply | {controller: ControllerRef, version, hash, snapshot: ConfigSnapshot} | {version, hash, cards} | 10 мин / 7 дни, приоритет 10 | config (по една) |
edge.controller.remove | {controller_id} | {} | 1 мин / 30 дни | config |
edge.door.open | {controller: ControllerRef, door_index, pulse_ms, requested_by?} | {} | 30 с / 30 с, приоритет 100 | door (4 паралелно) |
edge.discover | {driver, transport?, addresses?: ["host:port", …]} (до 256; празно за симулатора показва всеки симулиран контролер) | {controllers: [ControllerInfo]} | 2 мин / 5 мин | discover (по една) |
edge.controller.test | {controller: ControllerRef} | ControllerInfo | 1 мин / 5 мин | discover |
update_agent | UpdateJob (component: edge-connector, вижте Самообновяване) | UpdateResult | 30 мин / 24 ч | update |
ControllerRefе{controller_id, driver, target};target(edge.Target) е{transport: "tcp"|"rs485", address, unit_id?, pin?}:addressеhost:port, серийно устройство (COM3) или Modbus шлюзhost:port;unit_idе адресът по шината RS-485 1–31, липсва при TCP.pin(uint32, 1–4294967294; липсва, когато не е зададен) е ПИН кодът на контролера (TrackBase002: всяка командна рамка го носи). Не е част от ключа на целта (транспорт, адрес, адрес по шината), така че нов ПИН е същият контролер. Edge го пази шифрован и го добавя, когато доставя задачата: payload-ите на задачите в базата данни не съдържат ПИН. Конекторът го пази вcontrollers.jsonи никога не го връща:ControllerInfoв резултатите от търсене и проверка, както и отчетите за състоянието, са без него.ControllerInfoе{driver, target, model, serial, firmware, door_mode?}.edge.config.applyкара контролера да пази точно моментната снимка. Нова задача за контролер отменя неговата по-стара отворена задача за прилагане. Задача, по-стара от вече приложената от конектора конфигурация (доставена със закъснение), е успешна с по-новата версия. Конекторът пази последната приложена моментна снимка и при стартиране я възстановява на контролерите, които са я загубили. Нов ПИН, адрес, транспорт или адрес по шината се изпраща като прилагане на непроменената конфигурация (същата версия и хеш): конекторът само обновява целта, без пълно презаписване.edge.controller.removeспира четенето на контролера и забравя конфигурацията му. Изпраща се, когато контролерът бъде премахнат или деактивиран; деактивиран контролер, който бъде активиран отново, получава (принудително)edge.config.apply, което го регистрира отново в конектора.edge.door.openосвобождава ключалката заpulse_ms(времето за отключване на вратата; 3 с, ако липсва). Контролерът отчитаdoor_opened_remoteсrequested_byв подробностите. Краткият срок гарантира, че вратата никога не се отваря дълго след заявката.
Самообновяване (update_agent)
Същият тип задача и payload като при агентите и конекторите на Axis
(Протокол), с
component: edge-connector:
{"release_id": "…", "component": "edge-connector",
"target": {"version": "1.4.0", "url": "https://…/api/connector/v1/releases/…/msi?exp=…&sig=…",
"sha256": "…", "size_bytes": 9437184, "signature": "…"},
"rollback": {"version": "1.3.2", "url": "…", "sha256": "…", "size_bytes": 9412608, "signature": "…"}}
| Поле | Значение |
|---|---|
release_id | Идентификаторът в Edge на предложеното издание. |
target | MSI файлът за инсталиране: version, url (подписана връзка за изтегляне, валидна 24 часа, добавена при доставката на задачата), sha256, size_bytes и signature – подписът Ed25519 (base64) на "rmm-release-v1\n<component>\n<version>\n<sha256>\n<size>\n" с EDGE_RELEASE_SIGNING_KEY. |
rollback | Същото за версията, с която работи конекторът, ако Edge още я има: защитният механизъм я инсталира отново, ако новата версия не стартира. |
JobResult.Result е UpdateResult: {scheduled, from_version, to_version, rollback} (scheduled: инсталаторът се стартира след минута
от планираната задача; rollback: пази се предишен MSI за защитния
механизъм). Задачата е успешна, щом инсталирането е планирано; новата
версия се вижда в следващото hello на конектора.
Edge предлага обновяване, когато конекторът изпрати hello, и на всеки 5
минути (releases.rollout): най-новото издание от канала за обновления на
конектора (stable: стабилни издания; beta: бета и стабилни), което е
по-ново от версията на конектора, само на конектори, които обявяват
edge_update, не и докато има отворена задача update_agent за
конектора, и същата версия не отново в рамките на час. Какво прави
конекторът: Конектор на Edge → Самообновяване.
Кодове на грешки при задачи
job.result.error_code на задачите на Edge:
| Код | Значение |
|---|---|
capacity_exceeded | Моментната снимка има повече карти, отколкото контролерът побира, или паметта за карти на контролера е пълна. |
controller_unreachable | Контролерът не отговори. |
config_rejected | Контролерът или проверката на моментната снимка в конектора отказа конфигурацията. |
unsupported | Драйверът не може да достигне контролера по тази връзка (trackbase002 по RS-485/серийна връзка). |
unknown_driver | Тази версия на конектора няма драйвер с това име. |
invalid_payload | Payload-ът не може да бъде декодиран или е невалиден. |
exec_failed | Конекторът се рестартира, преди задачата да приключи; при update_agent – папката за обновяване или планираната задача не могат да бъдат създадени. |
update_unsigned | update_agent: компилацията няма публичен ключ за изданията и отказва обновявания. |
signature_invalid | update_agent: подписът на изданието не е потвърден. |
download_failed, hash_mismatch | update_agent: MSI файлът не може да бъде изтеглен или не съответства на SHA-256 и размера на изданието. |
Моментна снимка на конфигурацията
{
"timezone": "Europe/Sofia",
"door_mode": "two_unidirectional",
"doors": [{"index": 1, "lock_relay": 1, "open_pulse_ms": 3000, "held_open_seconds": 30}],
"readers": [{"channel": "wiegand1", "door_index": 1, "direction": "in"}],
"schedules": [{"slot": 1, "name": "Office", "intervals": [{"day": 0, "start": 480, "end": 1080}]}],
"holidays": ["2026-12-25"],
"cards": [{"kind": "wiegand26", "facility_code": 12, "card_number": 3456,
"card_id": "…", "grants": [{"door_index": 1, "schedule_slot": 1}]}]
}
| Поле | Значение |
|---|---|
timezone | IANA часовата зона, в която са изразени графиците и празниците: тази на обекта, иначе тази по подразбиране на тенанта, иначе UTC. |
door_mode | one_bidirectional (една врата, четец за вход и изход) или two_unidirectional (две врати с по един четец). |
doors | index на вратата 1–2, lock_relay 1–4, open_pulse_ms, held_open_seconds (0: никога не генерира door_held_open). |
readers | Само включените четци: channel (wiegand1, wiegand2, ibutton1, ibutton2), вратата, която обслужва, direction (in, out). |
schedules | Графиците, които вратите на контролера използват, номерирани с slot 1–255. intervals са [start, end) в минути от полунощ; day 0 = понеделник … 6 = неделя, 7 = празнични прозорци. |
holidays | Дати (YYYY-MM-DD, в timezone) от предишния ден до 90 дни напред, на които важат празничните прозорци вместо тези на деня от седмицата. |
cards | Всяка карта, която може да минава през поне една от вратите (най-много 2000): идентификаторът (wiegand26 с facility_code 0–255 и card_number 0–65535 или ibutton с ibutton_id, 2–16 шестнадесетични цифри), card_id в Edge и нейните grants (врата и слот на график). |
hash е SHA-256 (hex) на JSON на моментната снимка; всеки списък е
подреден детерминистично, така че една и съща конфигурация винаги има един
и същ хеш. Конекторът отчита хеша, който контролерът пази, в
applied_hash; различен хеш на контролер, който е онлайн и не се
синхронизира, кара сървъра да изпрати конфигурацията отново.
Референтното решение (ConfigSnapshot.Allows, реализирано от драйверите и
симулатора): непознат идентификатор → unknown_card; няма разрешение за
вратата → access_denied/no_access; разрешение, чийто график има отворен
прозорец в момента (празничните прозорци в празник) → access_granted;
иначе access_denied с holiday в празник, в противен случай
outside_schedule.
Събития
{"seq": 1042, "controller_id": "…", "occurred_at": "2026-09-28T06:00:12Z",
"type": "access_granted", "door_index": 1, "reader_channel": "wiegand1",
"direction": "in", "credential": {"kind": "wiegand26", "facility_code": 12, "card_number": 3456}}
| Поле | Значение |
|---|---|
seq | Дава се от опашката на конектора: строго нарастващ за всяка инсталация на конектор. |
controller_id, occurred_at, type | Задължителни. Типове: access_granted, access_denied, unknown_card, door_opened_remote, door_forced, door_held_open, door_closed, controller_online, controller_offline, config_applied, tamper. |
door_index | 1–2; липсва при събития на контролера. |
reader_channel, direction | Четецът, при събития с карти. |
credential | Прочетената карта. |
reason | За access_denied: no_access, outside_schedule, holiday. |
detail | Свободен текст, най-много 512 знака (кой е отворил врата дистанционно, съобщения на драйвера). |
Точно веднъж: сървърът пази за всеки конектор най-високия пореден номер, който е съхранил. Част (chunk) на или под него е дубликат и само се потвърждава; частта над него се съхранява и курсорът се премества, в една транзакция. Конекторът пази събитията на диска, докато не бъдат потвърдени, така че изгубено потвърждение, рестартиране или прекъсване водят само до повторно изпращане. По WebSocket част без потвърждение до 15 секунди се изпраща отново по HTTP.
При приемането сървърът определя контролера, вратата, четеца, картата и
картодържателя на всяко събитие (историята ги пази дори след изтриване) и
предава новите записи на уеб приложението. occurred_at повече от 24 часа
в бъдещето или повече от година в миналото се заменя с часа на
пристигане, с бележка в подробностите.
controller_online и controller_offline се записват от сървъра при
промени в edge.controller.status.
Ограничения
| Ограничение | Стойност |
|---|---|
| Карти на контролер | 2000 |
| Графици на контролер (слотове) | 255 |
| Врати на контролер, релета | 2, 4 |
| Адреси по шината RS-485 | 1–31 |
Събития в една част edge.events | 500 (конекторът освен това пази частта под 256 KiB) |
| Контролери в един отчет за състоянието | 1000 |
| Съобщение по WebSocket | 1 MiB |
| Адреси при едно търсене | 256 |