Умный фуд‑вендинг: от железа до оплаты
Re: Умный фуд‑вендинг: от железа до оплаты
- Меню холодильника, которое менеджер может прямо сейчас обновить.
- Плагин шлюза управления холодильником есть и готов. Надо настроить
- Кнопку открытия холодильника на странице холодильника по которой можно будет нажать. Сработает вебхук на открытие кнопки и дверь должна фактически открыться. Через шлюз управления.
- Железки (мозги) холодильника приедут завтра и послезавтра
Ну и вроде как бы все. Осталось теперь все это срастить.
На сращивание - 4 дня и до конца недели надо все повесить на холодильник.
Re: Умный фуд‑вендинг: от железа до оплаты
Пока останавливаюсь на этом:
1. Общий поток
Клиент сканирует QR и попадает на страницу холодильника, например:
Код: Выделить всё
/sh52001/Жмёт «ОТКРЫТЬ ХОЛОДИЛЬНИК»: физически открывается дверь, на фронте включается “режим сканирования”.
Каждый скан штрих‑кода:
– фронт получает код с камеры,
– вызывает
Код: Выделить всё
scanAndAddToCart(barcode)– та в фоне добавляет товар в корзину так, как будто юзер нажал обычную кнопку “В корзину”.
Woodmart по своему механизму:
– обновляет корзину WooCommerce,
– показывает справа стандартную мини‑корзину (off‑canvas).
Когда клиент закончил набирать товары, жмёт «ПЕРЕЙТИ К ОПЛАТЕ» → редирект на стандартный WooCommerce checkout с уже заполненной корзиной.
2. Что нужно реализовать по слоям
Фронт (JS на странице /sh52001/)
Сканер (камера в браузере).
– выводим оверлей камеры с рамкой под штрих‑код,
– по успешному чтению вызываем
Код: Выделить всё
scanAndAddToCart(barcode)Функция scanAndAddToCart(barcode).
– по штрих‑коду на стороне фронта или бэкенда определяем
Код: Выделить всё
product_id– делаем
Код: Выделить всё
fetchКод: Выделить всё
$.ajaxвызывает
Код: Выделить всё
WC()->cart->add_to_cart( $product_id, 1 );возвращает JSON с результатом;
– при успешном добавлении:
даём отработать стандартному механизму темы Woodmart (мини‑корзина справа, обновление суммы и т.п.),
при первом добавленном товаре меняем состояние основной кнопки на “ПЕРЕЙТИ К ОПЛАТЕ”.
Фактически задача scanAndAddToCart — “программно нажать add to cart” через AJAX, а не изобрести свой mini‑cart.
Состояния UI (одна плавающая кнопка).
– «ОТКРЫТЬ ХОЛОДИЛЬНИК» — команда открыть дверь, включить режим сканирования;
– «СКАНИРОВАТЬ ТОВАР» — открыть оверлей камеры, ждать штрих‑код;
– «ПЕРЕЙТИ К ОПЛАТЕ» —
Код: Выделить всё
window.location = wc_get_checkout_url()Код: Выделить всё
'/checkout/'Сохранение сеанса на фронте.
– в
Код: Выделить всё
localStorageID “сеанса холодильника” (если он есть на бэкенде),
последнее состояние (дверь открыта/закрыта, были ли сканы),
– при перезагрузке страницы:
подтягиваем текущее состояние из сервера при необходимости,
ориентируемся по корзине (если в ней уже есть позиции с этого холодильника — сразу предлагаем “Перейти к оплате” или “Сканировать ещё”).
Бэкенд (мини‑плагин типа “fridge-scenarios”)
REST‑/AJAX‑эндпоинт для добавления по штрих‑коду.
– принимает штрих‑код (и, опционально, ID сеанса),
– ищет товар по метаполю / отдельной таблице штрих‑кодов / артикулу,
– выполняет
Код: Выделить всё
WC()->cart->add_to_cart( $product_id, 1 );– возвращает JSON со статусом (ok/error) и, при желании, краткой инфой о товаре. Если вызывать именно WooCommerce‑механику добавления (а не вручную трогать сессию), Woodmart автоматически отработает своё поведение мини‑корзины.
Дополнительные эндпоинты (по необходимости).
–
Код: Выделить всё
open door / close door–
Код: Выделить всё
get current session items– возможно, endpoint “finish session”, чтобы на бэке помечать, что холодильник закрыт и сеанс завершён.
Корзина WooCommerce уже сама по себе живёт в сессии/куках, поэтому товары, добавленные через этот эндпоинт, никуда не исчезают даже при вылете вкладки — пользователь может сразу идти на
Код: Выделить всё
/checkout/3. Основные плюсы такого подхода
– Не завязаны на наличие кнопок “В корзину” в шаблоне холодильника: всё делается через один AJAX/REST‑вызов.
– Используем готовую мини‑корзину Woodmart как UI для списка взятых товаров (без изобретения собственного мини‑листа).
– Поток для клиента простой: открыть → сканировать → мини‑корзина → переход к оплате, всё в рамках одного экрана.
– Корзина устойчива к вылетам страницы, а логику защиты (QR‑режим, таймаут, повторные открытия двери и т.п.) можно внедрять поверх этой архитектуры, не ломая базовый сценарий.
Re: Умный фуд‑вендинг: от железа до оплаты
1. Кнопка с переходом в режим “СКАНИРОВАТЬ товар” (изменение текста и цвета после клика “ОТКРЫТЬ”).
2. Сканер со стилизацией под лазерный сканер и узкой щелью под EAN 13
Надо еще подумать над логикой "защиты от дурака". Для себя уже пометил, но работать над этим лучше уже не стадии отладки сценариев.
UPD: утром проверил сканнер код читает.
Сегодня будем работать над добавлением товара из холодильника в корзину и оплатой.
Re: Умный фуд‑вендинг: от железа до оплаты
Лучше левый вариант. Но надо доработать:
1. Столешницу сделать с выносом вперед.
2. Сделать выдвижной ящик под приборы и салфетки
Вверху криво нарисован телек.
На нем будет отображаться меню холодильника
Данная функция реализована через отдельный плагин и уже сейчас работает на раздаче. Вывод через СПЕЦИАЛЬНЫЙ браузер телевизора с обновлением странице раз в N часов.
Пример на фото.
Re: Умный фуд‑вендинг: от железа до оплаты
Встречайте: (H R C F) ESP32-S3 SIM7670G Макетная Плата 4G
Оснащен высокопроизводительным 32-битным двухъядерным процессором Xtensa® LX7 с тактовой частотой до 240 МГц
Поддержка Wi-Fi 2,4 ГГц (802.11 b/g) и Bluetooth® 5 (LE), встроенная антенна
Встроенная объемом 512 КБ и ПЗУ 384 КБ, составная PSRAM объемом 2 МБ и внешняя флэш-память емкостью 16 МБ
ESP32-S3-SIM7670G-4G (далее совместно именуемая «плата разработки») - это многофункциональная высокопроизводительная плата разработки микроконтроллера. Плата содержит модуль связи SIM7670G 4G, универсальный интерфейс камеры , слот совместим с TF-карты, красочную подсветку RGB, слот совместим с батареи 18650, микросхему измерения напряжения батареи, интерфейс зарядки солнечной батареи и другие периферийные устройства.
Используемый ESP32-S3R2 представляет собой интегрированную маломощную систему на кристалле (SoC), которая потребляет Wi-Fi и BLE5.0, а также имеет внешнюю флэш-память объемом 16 МБ и PSRAM объемом 2 МБ. Аппаратный ускоритель шифрования, , HMAC и модуль цифровой подписи (Digital Signature) внутри Soc могут соответствовать требованиям безопасности Интернета вещей. Используемый модуль связи SIM7670G 4G обеспечивает мобильную сеть, а в сочетании с ESP32-S3R2 он может реализовать портативный WIFI, передачу данных IoT и другие функции. Разнообразие рабочих состояний с низким энергопотреблением соответствует потребностям в энергопотреблении таких сценариев применения, как Интернет вещей (IoT), мобильные устройства, наружный мониторинг и умные дома.
Комплект достаточно внушительный.
Состав на фото.
Re: Умный фуд‑вендинг: от железа до оплаты
Сценарий: ESP32-S3 + SIM7670 (A7670), TCP‑шлюз на Node.js и интеграция с WordPress
Исходные данные:
– Устройство: ESP32-S3 с SIM7670 (A7670), связь через LTE/4G.
– Оператор: Билайн (APN: internet.beeline.ru, логин/пароль: beeline).
– На сервере уже есть Node.js проект с HTTP API:
– API listening on port 3005
– API Key: gsm-secret-key-2026
– API URL: http://localhost:3005
Цель: построить нормальный GSM‑шлюз для холодильников, который:
– держит постоянное TCP‑соединение с устройствами;
– мгновенно передает команды (например, OPEN для замка);
– обменивается телеметрией;
– при этом WordPress работает только с HTTP API шлюза, а не лезет напрямую к железу.
Ниже вся архитектура и протокол в одном месте.
Роли компонентов
Устройство (ESP32-S3 + SIM7670)
– Постоянный TCP‑клиент.
– Подключается к TCP‑серверу, отправляет HELLO с ID и секретом.
– Периодически шлет ping с телеметрией.
– Принимает команды (OPEN и т.п.) и отправляет события (DOOR_OPENED и т.д.).
Node‑шлюз (порт 3005 + отдельный TCP‑порт)
– На Node.js:
– TCP‑сервер для холодильников (отдельный порт, например 4000).
– REST API (уже есть: http://localhost:3005, с API‑ключом).
– Держит карту подключений fridge_id → socket.
– Принимает телеметрию и события от устройств.
– По HTTP‑запросам от WordPress отправляет команды нужным холодильникам по TCP.
WordPress + плагин (например stolovka-fridges-gateway.php)
– Не общается с устройствами напрямую.
– При нажатии в админке «Открыть холодильник» делает HTTP‑запрос к Node‑шлюзу, например:
POST http://localhost:3005/open
X-API-Key: gsm-secret-key-2026
fridge_id=123
– Node‑шлюз находит TCP‑сокет для fridge_id=123 и отправляет через него команду OPEN.
Протокол TCP (JSON по строкам)
Все сообщения – одна строка JSON + символ перевода строки \n. Это сильно упрощает парсинг.
2.1. Устройство → Node
Регистрация (HELLO)
Отправляется сразу после установления TCP‑соединения:
Код: Выделить всё
{"type":"hello","id":"fridge-123","secret":"ABCDEF123456","fw":"1.0.0"}
– secret – секретный ключ (из админки/БД).
– fw – версия прошивки (по желанию).
Пинг с телеметрией
На регулярном интервале (например, каждые N секунд):
Код: Выделить всё
{"type":"ping","id":"fridge-123","t":4.5,"door":0,"comp":1,"mains":1}
– t – температура (С).
– door – состояние двери (0/1).
– comp – состояние компрессора (0/1).
– mains – наличие 220В (0/1).
События
Например, дверь открылась:
Код: Выделить всё
{"type":"event","id":"fridge-123","event":"DOOR_OPENED"}
Код: Выделить всё
{"type":"event","id":"fridge-123","event":"DOOR_CLOSED"}
2.2. Node → Устройство
Подтверждение регистрации
После успешного HELLO:
Код: Выделить всё
{"type":"hello_ok","mode":"idle"}
Команда открыть замок
Код: Выделить всё
{"type":"cmd","cmd":"OPEN"}
– срабатывает openLock();
– ждёт, пока концевик двери покажет открытие;
– отправляет событие DOOR_OPENED.
Позднее можно добавить другие команды:
Код: Выделить всё
{"type":"cmd","cmd":"LOCK"}
{"type":"cmd","cmd":"REBOOT"}
Скетч на ESP32-S3 + SIM7670 (через TinyGSM) делает следующее:
– настраивает APN Билайн;
– поднимает модем и GPRS;
– устанавливает TCP‑соединение с Node‑шлюзом;
– отправляет HELLO сразу после connect;
– периодически шлет ping;
– читает входящие строки JSON и выполняет команды.
Настройки APN под Билайн:
Код: Выделить всё
const char APN[] = "internet.beeline.ru";
const char GPRS_USER[] = "beeline";
const char GPRS_PASS[] = "beeline";
Код: Выделить всё
void sendHello() {
String payload = "{";
payload += ""type":"hello",";
payload += ""id":"fridge-123","; // TODO: взять из настроек
payload += ""secret":"ABCDEF123456""; // TODO: взять из настроек
payload += "}\n";
client.print(payload);
}
Код: Выделить всё
void loop() {
ensureConnected(); // если TCP отвалился, пробуем пересоздать сеть и коннект
if (client.connected()) {
// Периодический пинг
unsigned long now = millis();
if (now - lastPingMs >= PING_INTERVAL_MS) {
lastPingMs = now;
sendPing(); // JSON: type=ping + телеметрия
}
// Обработка входящих команд
handleIncoming(); // парсит строки JSON, ищет type=cmd, cmd=OPEN и т.п.
}
delay(10);
}
Node‑шлюз: TCP‑сервер + HTTP‑API
Идея: Node‑процесс слушает TCP‑порт для устройств и HTTP‑порт (3005) для WordPress.
4.1. TCP‑сервер (удерживает сокеты устройств)
Псевдокод на Node.js (упрощённый пример):
Код: Выделить всё
const net = require('net');
const socketsById = new Map(); // fridge_id -> socket
const server = net.createServer(socket => {
let buffer = '';
socket.on('data', data => {
buffer += data.toString('utf8');
let index;
while ((index = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, index).trim();
buffer = buffer.slice(index + 1);
if (!line) continue;
try {
const msg = JSON.parse(line);
if (msg.type === 'hello') {
const fridgeId = msg.id;
// TODO: проверить секрет msg.secret по БД или конфигу
socketsById.set(fridgeId, socket);
socket.fridgeId = fridgeId;
socket.write(JSON.stringify({ type: 'hello_ok', mode: 'idle' }) + "\n");
}
if (msg.type === 'ping') {
// обновить телеметрию в БД/кэше
}
if (msg.type === 'event') {
// обработать событие (лог, уведомление, HTTP‑hook в WP и т.д.)
}
} catch (e) {
console.error('Bad JSON from device:', e);
}
}
});
socket.on('close', () => {
if (socket.fridgeId) {
socketsById.delete(socket.fridgeId);
}
});
socket.on('error', err => {
console.error('Socket error:', err);
});
});
server.listen(4000, () => {
console.log('TCP gateway listening on port 4000');
});
В существующем Express‑API (порт 3005) добавляется endpoint:
Код: Выделить всё
app.post('/open', (req, res) => {
const apiKey = req.header('X-API-Key') || req.query.api_key;
if (apiKey !== 'gsm-secret-key-2026') {
return res.status(403).json({ error: 'forbidden' });
}
const fridgeId = req.body.fridge_id || req.query.fridge_id;
if (!fridgeId) {
return res.status(400).json({ error: 'fridge_id required' });
}
const socket = socketsById.get(fridgeId);
if (!socket) {
return res.status(503).json({ error: 'device_offline' });
}
const cmd = JSON.stringify({ type: 'cmd', cmd: 'OPEN' }) + "\n";
socket.write(cmd);
res.json({ status: 'queued' });
});
– POST http://localhost:3005/open
– Заголовок X-API-Key: gsm-secret-key-2026
– Тело: fridge_id=fridge-123
Node‑шлюз найдёт сокет холодильника и отправит ему команду OPEN по TCP.
Интеграция с WordPress
В плагине stolovka-fridges-gateway.php:
– Кнопка «Открыть холодильник» в админке вызывает REST‑handler плагина.
– Этот handler делает внутренний HTTP‑запрос к Node‑шлюзу.
Примерно так:
Код: Выделить всё
$response = wp_remote_post('http://localhost:3005/open', [
'headers' => [
'X-API-Key' => 'gsm-secret-key-2026',
],
'body' => [
'fridge_id' => $fridge_id, // ID устройства, привязанное к записи/терминалу
],
]);
if (is_wp_error($response)) {
// обработать ошибку
}
Вывод
– Для мгновенных команд и большого количества устройств (холодильников) постоянное TCP‑соединение + отдельный Node‑шлюз – нормальная и правильная архитектура.
– WordPress в такой схеме работает только с HTTP API Node‑шлюза, не общаясь напрямую с железом.
– Устройство (ESP32-S3 + SIM7670) остаётся максимально простым: TCP‑клиент, который шлет HELLO, pинг и события, и выполняет команды, приходящие от Node‑шлюза.
Re: Умный фуд‑вендинг: от железа до оплаты
Т.е. новая схема железа будет входить уже в рабочую (боевую) модель.
Основной причиной введением новой схемы стало глобальное отключение интернета (по белым спискам). В случае отсутствия оного команды на устройство будут приходить через CMC сообщения. Так же будут приходить критические предупреждения на сервер о слишком высокой температуре в холодильнике, отсутствии питания холодильника, долго открытой двери. Это критические показатели, при которых эксплуатация холодильника с продукцией внутри недопустима.
На текущий момент идет работа над сценариями и логикой работы данного канала.
Re: Умный фуд‑вендинг: от железа до оплаты
За последние дни был небольшой, но показательный эволюционный путь железа и софта - от «давайте прикрутим ардуино нано и реле» до нормальной архитектуры с ESP32-S3 - 4G модемом - Node-шлюзом - WordPress-плагином и фронтендом на сайте.
Из железа в итоге выбрал связку:
- контроллер на базе ESP32-S3
- 4G модем (SIM7670 / A7670 класс)
- питание и обвязка под холодильник
Со стороны сервера родился отдельный Node.js-шлюз - который:
- принимает HTTP-запросы от сайта
- общается с железом по своему протоколу
- публикует команды открытия через MQTT / TCP (внутренности можно менять, фронту всё равно)
Что сделано на стороне сайта
Параллельно пришлось приручить фронт:
- сделан отдельный плагин «шлюза холодильников» на WordPress
- в нём таблица холодильников с их ID - статусами - последним контактом и т.д.
- у каждого холодильника есть свой fridge_id вида sh52001 (буквенный префикс + номер)
- эти ID живут в базе и используются как ключи во всей системе
есть специальная ссылка формата:
- домен/?fridge_id=sh52001
- проверяет, что fridge_id валидный (регулярка типа буквы+цифры)
- проверяет наличие такого холодильника в базе
- текущий fridge_id
- время жизни сессии
Если fridge_id кривой или холодильника нет в базе:/sh52001/
- рендерится аккуратное сообщение «Неверная ссылка на холодильник - попробуйте отсканировать QR-код ещё раз или обратитесь к сотруднику»
- QR-код является условным «ключом» - без него сессия холодильника не заведётся
- сама страница /sh52001/ не содержит в себе секретов - она просто оболочка для UI и знает fridge_id через сессию - а не из URL
Для фронта сделан отдельный плагин плавающей кнопки. Он рендерит модуль вида:
- div class="fridge-module fridge-module--idle" data-fridge-id="sh52001"
- button ОТКРЫТЬ ХОЛОДИЛЬНИК
- div для статусов
Основная логика берётся из функции sfg_get_current_fridge_id() шлюзового плагина - она:
- читает fridge_id из cookies / сессии
- валидирует его
- при проблемах возвращает пустую строку - а на фронте уже показываем «Неверная ссылка»
- считывает fridge_id из data-fridge-id
- «Неверная ссылка - отсканируйте QR-код ещё раз» и никуда не стучится
- POST /wp-json/sfg/v1/open
- тело - { fridge_id: 'sh52001' }
На ответ «ok» от REST меняем подпись кнопки и состояние модуля - дальше уже подключается логика «сканирования товаров», но это отдельная тема.
Связка WordPress → Node-шлюз
REST-контроллер в плагине шлюза реализует маршрут /sfg/v1/open. Внутри он:
- принимает fridge_id из POST
- валидирует по БД (есть ли такой холодильник)
- и только после этого стучится в Node-шлюз
- проверяет, что wp_remote_post отработал без ошибок
- проверяет, что Node вернул JSON со статусом ok
- и уже этот статус отдаёт фронту
- cURL error 7 - Failed to connect to localhost port 3005
Node-шлюз и команда OPEN
На стороне Node в итоге получился простой HTTP-обработчик:
- принимает POST /api/fridge/open
- проверяет X-API-Key
- парсит fridge_id
- публикует команду для конкретного холодильника - через MQTT или TCP-сокет
- возвращает JSON вида:
Код: Выделить всё
{ "status": "ok", "fridge_id": "sh52001", "message": "Fridge MQTT command published" }
Код: Выделить всё
curl -X POST -H "X-API-Key: ..." -d "fridge_id=sh52001" http - 127.0.0.1 - 4000 - api - fridge - open
ESP32 - и зачем всё это было
Самое вкусное в этой истории - финальная проверка. На плате ESP32-S3 с модемом поднимается прошивка - которая:
- держит связь с сервером
- принимает команду OPEN для конкретного fridge_id
- мигает светодиодом
- дёргает пин реле (в боевом варианте)
- сканируем QR-код на холодильнике
- открывается фронтенд
- жмём «ОТКРЫТЬ ХОЛОДИЛЬНИК»
- WordPress отправляет REST-запрос на Node
- Node публикует команду OPEN
- ESP32 получает её - и честно моргает светодиодом на плате
- QR-код
- сессия холодильника
- WordPress-плагин (сессии - БД - REST)
- фронтовая кнопка
- Node-шлюз
- ESP32 + 4G модем
Вывод
В процессе стало ясно несколько вещей:
- правильнее сразу проектировать архитектуру с отдельным шлюзом между сайтом и железом - а не пытаться «допилить» ардуино нано и прямые запросы
- WordPress как фронт и панель управления отлично уживается с Node-шлюзом и ESP32 - если всё общается через нормальный HTTP API
- очень легко залипнуть в дебаге там - где виноват один порт или пустой data-fridge-id
- есть готовый каркас для масштабирования на десятки - сотни холодильников
- есть понятный протокол от фронта до железа
- и есть честно заработанный моргнувший светодиод - как символ того, что цепочка «клиент у холодильника → сайт → сервер → железо» реально замкнулась