Термостат в моём гараже греет пол, и за первый месяц порог срабатывания я перешивал шесть раз: то рассаде холодно, то счета за электричество обидны. Каждый перезапуск — это ноутбук у столба, кабель, компиляция, двенадцать секунд записи и надежда, что ничего не задело. На седьмой раз дошло: я перешиваю прошивку там, где нормальные люди крутят ручку настройки.
Решение старое, как сами терминалы: устройство слушает порт и понимает команды set, show, save. Порог меняется за секунду из монитора — или с телефона через Bluetooth из урока про HC-05, — а прошивка остаётся при себе навсегда. Словарь уже готов из урока про командный парсер, меню по цифрам — из урока про текстовое меню; здесь достраиваем недостающее: значения с диапазонами и сохранение на годы.
Спасать настройки без питания умеет EEPROM, и это уже соседний раздел: куда класть ящик настроек и зачем magic-число, разобрано в уроке про структуру настроек. Здесь — терминальная половина дела: грамматика команд, отказ с внятным ответом и момент записи в память. Ещё нам пригодится show при старте: устройство само рассказывает, чем живёт, — половина всех ночных диагнозов и без подключённого ноутбука.
Грамматика: глагол, имя, значение
Формат строки: set porog 55. Три слова через пробел — глагол, имя настройки, значение. Короткие глаголы не экономия букв ради красоты: по ним два indexOf режут строку на три куска, и дальше работает switch. show печатает настройки парами имя=значение — той же записью, которой плоттер рисует в уроке про график: одна запись обслуживает и человека, и график. Отдельное имя у каждой настройки — тоже не каприз: по имени отказ говорит, что именно не понравилось, а show печатает значения поимённо, без догадок.
Диапазон проверяется до записи: порог живёт в 0–100, гистерезис в 1–20. Прислал 555 — получи ERR porog: nuzhno 0-100, и значение не трогается. Молчаливое согласие обходится дорого: однажды я принял любую цифру без проверки, и термостат с порогом 555 грел пол до рассвета. Каждый отказ печатает имя и допустимый диапазон — через месяц по журналу вспоминаешь словарь устройства без всякой документации. И проверка дешевле бессонной ночи: она стоит один if, а ночь с порогом 555 — целое расследование.
Две строки вежливости, которые стоят копейки. help печатает словарь и диапазоны — первая строчка диагностики любого моего устройства. Эхо в ответах: не просто OK, а set porog=55 — ответ повторяет исполненное. Пара help и эхо превращает журнал в стенограмму разговора, а уровни важности строк мы уже раскладывали в уроке про уровни логов.
#include <EEPROM.h> struct Nastroyki { uint16_t magic; // 0xBEEF: отличаем настроенную плату от свежей int8_t porog; // порог срабатывания, 0..100 int8_t gistr; // гистерезис, 1..20}; Nastroyki n; void zavodskie() { n.porog = 40; n.gistr = 5;} void setup() { Serial.begin(9600); pinMode(13, OUTPUT); EEPROM.get(0, n); if (n.magic != 0xBEEF) { // свежая плата: EEPROM читается единицами zavodskie(); n.magic = 0xBEEF; EEPROM.put(0, n); } Serial.print("Start: porog="); Serial.print(n.porog); Serial.print(" gistr="); Serial.println(n.gistr); Serial.println("Komandy: set porog 0-100 / set gistr 1-20 / show / save / defaulty / help");} void loop() { if (Serial.available() > 0) { String cmd = Serial.readStringUntil(10); cmd.trim(); int p1 = cmd.indexOf(" "); String glagol = cmd; String imya = ""; String znach = ""; if (p1 > 0) { glagol = cmd.substring(0, p1); int p2 = cmd.indexOf(" ", p1 + 1); if (p2 > 0) { imya = cmd.substring(p1 + 1, p2); znach = cmd.substring(p2 + 1); } else { imya = cmd.substring(p1 + 1); } } if (glagol == "show") { Serial.print("porog="); Serial.println(n.porog); Serial.print("gistr="); Serial.println(n.gistr); } else if (glagol == "set" && imya == "porog") { int v = znach.toInt(); if (v >= 0 && v <= 100) { n.porog = v; Serial.print("OK set: porog="); Serial.println(v); } else Serial.println("ERR porog: nuzhno 0-100"); } else if (glagol == "set" && imya == "gistr") { int v = znach.toInt(); if (v >= 1 && v <= 20) { n.gistr = v; Serial.print("OK set: gistr="); Serial.println(v); } else Serial.println("ERR gistr: nuzhno 1-20"); } else if (glagol == "save") { EEPROM.put(0, n); // один проход по ячейкам Serial.println("OK save"); } else if (glagol == "defaulty") { zavodskie(); Serial.println("OK defaulty: ne zabudte save"); } else if (glagol == "help") { Serial.println("set porog 0-100 / set gistr 1-20 / show / save / defaulty"); } else { Serial.println("ERR: spisok komand — help"); } }}| Путь поменять настройку | Скорость | Живёт после перезагрузки | Цена |
|---|---|---|---|
| Константа в скетче | перешивка: минуты и кабель | да | каждая правка — новый образ прошивки |
| Сериал + RAM | секунда из монитора | нет: до первого сброса | настройки пропадают при отключении питания |
| Сериал + EEPROM | секунда из монитора | да, годы | ресурс ячейки — около 100000 циклов записи |
| Потенциометр на корпусе | поворот руки | да | пин, шкала и грубый диапазон без дробей |
Сохранение: отдельный глагол, а не побочный эффект
Каждый set меняет только копию в RAM; в EEPROM настройки переезжают по команде save. Почему не писать сразу: ресурс ячейки — порядка 100000 циклов записи, арифметика износа посчитана в разделе про EEPROM. При настройке вслепую порог крутят десяток раз за минуту — пиши каждый set, и сотая доля ресурса уедет за одну сессию. save одним put проходит по ящикам один раз; механика put и get разобрана в соседних уроках EEPROM-раздела. У экономии есть и вторая сторона: save переписывает весь ящик, а не одно поле, — так проще считать износ: одна команда, один проход, один круг ресурса.
При старте скетч читает ящик и проверяет magic: свежая плата читается единицами, magic не сходится — ставим заводские значения и пишем их, приём из урока про magic-число и дефолты. Без этой проверки свежая плата включилась бы с порогом 255 — так читается нетронутая EEPROM — и грел бы пол, пока не сгорит реле.
defaulty — тот же набор заводских чисел одной командой: откат после неудачной настройки за секунду, без перешивки и без ползаний по журналу. Логика у меня простая: set — эксперимент в RAM, save — подпись под ним, defaulty — крест на эксперименте. Три глагола закрывают весь жизненный цикл настройки, и ничего больше в словаре не нужно. А откат документирует себя сам: в журнале остаётся цепочка set, save и defaulty — видно, что было, что стало и зачем.
Настройки бывают зависимые, и это отдельная глава валидации. Гистерезис живёт не сам по себе, а вычитается из порога: set porog 2 и set gistr 20 дают нижнюю границу минус восемнадцать — лампа не погаснет никогда. Поэтому связанную пару я проверяю на границе, а не порознь: либо ограничиваю gistr сверху порогом, либо считаю нижнюю границу через максимум с нулём. Ограничение честнее: ERR gistr: bolshe porog-1 — отказ снова говорит, что не так.
Крайние значения, наоборот, законны — резать их «для безопасности» не надо. Порог 0 и порог 100 влезают в диапазон и исполняются как написано: 100 — это вечный нагрев, и пусть его ставит человек, который понимает цену. Диапазон — граница здравого смысла, а не вкуса: всё внутри исполняется, всё снаружи возвращается с именем и границей — единое правило на весь словарь.
Терминал в симуляторе
В движке симулятора EEPROM нет — save переписывает переменные-слепок, и после перезапуска симулятора они вернутся к заводским. На реальной плате на месте тех же строк стоит EEPROM.put из блока выше. Логика словаря — глагол, проверка диапазона, ответ с эхом — совпадает до последней буквы, так что привычка отрабатывается честная. Перезапустите симулятор после save — настройки вернутся к заводским, и это наглядная демонстрация того, что делает отсутствие EEPROM.
Макетка живая: ползунок A0 изображает показание датчика от 0 до 100, лампа на пине 12 — реле нагревателя. set porog 70 переносит загорание выше; гистерезис держит лампу от мигания у порога: загорается выше porog, гаснет только ниже porog минус gistr. Полоса гистерезиса видна в журнале: значения у порога держат прежний svet, пока не выйдут за границу. Отказ тоже попробуйте: set porog 555 упрётся в ERR с диапазоном и ничего не изменит — значение уходит только через законную дверь.
- Формат команды: глагол, имя, значение — set porog 55; два indexOf режут строку, дальше switch по глаголу
- Диапазон проверяется до записи, отказ печатает имя и допустимые границы: ERR porog: nuzhno 0-100
- set меняет копию в RAM, save пишет в EEPROM одним put — ресурс ячейки около 100000 циклов не сжигается настройкой вслепую
- При старте magic-число отличает настроенную плату от свежей: не сошлось — заводские значения и запись
- help, эхо в ответах и show при старте — словарь всегда под рукой, журнал читается как стенограмма
- 1. Почему set не пишет в EEPROM сразу, а ждёт save?
- 2. set porog 555 отправлен, диапазон 0–100. Что сделает честный конфигуратор?
- 3. Плата только что из коробки, EEPROM нетронутая. Что прочитает magic-проверка?
- 4. После отключения света устройство вернуло старый порог. Первая гипотеза?
- 5. Зачем гистерезис в конфигураторе термостата?
Частые вопросы по теме урока
- Почему set не пишет в EEPROM сразу, а ждёт save?
- Порог при настройке крутят десятки раз за минуту. set меняет копию в RAM, save одним put проходит по ячейкам один раз — ресурс расходуется только по делу, десяток set за минуту десятка put не стоит. Ресурс ячейки около 100000 циклов: настройка вслепую выжигает его за одну сессию
- set porog 555 отправлен, диапазон 0–100. Что сделает честный конфигуратор?
- Диапазон проверяется до записи, а отказ печатает имя настройки и допустимые границы. Молчаливое согласие однажды грело пол у меня до рассвета; значение уходит только через законную дверь. Ответит ERR porog: nuzhno 0-100 и не тронет значение
- Плата только что из коробки, EEPROM нетронутая. Что прочитает magic-проверка?
- Нетронутая EEPROM читается всеми единицами — каждый байт 255. Magic из [урока про magic-число и дефолты] отличает свежую плату и подставляет заводские значения. Единицы, то есть 255: magic не сойдётся, ставим заводские значения
- После отключения света устройство вернуло старый порог. Первая гипотеза?
- set живёт только в RAM. show при старте печатает прочитанное из EEPROM, флаг sohranen честно говорит, что сохранения не было — жаловаться не на что. save после set отправлен не был: в RAM настройка жила до перезагрузки
- Зачем гистерезис в конфигураторе термостата?
- Без гистерезиса значение возле порога дёргает реле десятки раз в минуту. Полоса в единицы-другие шкалы держит реле спокойным; ширина полосы тоже крутится командой set. Чтобы лампа не мигала у порога: гаснет ниже porog минус gistr, зажигается выше porog