Як я зробив Wi-Fi ваги, ні звідки не звільнився, а про життя взагалі мовчу

    
 
Із заголовка неважко бачити, що на це пригода мене сподвигло банальне прагнення Займатись плагіатом заголовок хлопців з Madrobots . А це ж дійсно була пригода: одна тільки історія з тривалою покупкою INA125U настільки занудно, що може звести з розуму кого завгодно, крім мене. Втім, можливо, я дечого про себе досі не знаю.
 
Отже, ваги. Я, як звичайно, зробив все неправильно. А саме — не став навіть дивитися на те, що вміють робити типові на поточний момент розвитку інтернету речей представники цього загону, так сказати, скалярних. Тому мої ваги вміють роздільно зважувати трьох різних особин людини (мене, дружину і сферичного гостя у вакуумі), а також — п'ятьох котячих. Результати озвучуються довколишнім смартфоном і публікуються в табличці Google.
 
Ну а тепер — про те, як це зробити, маючи на руках ваги з ІКЕА, операційний підсилювач TI INA125, Arduino Pro Mini, перетворювач Serial — Wi-Fi HiLink HLK-RM04, трохи іншої рассипухі і здоровенне шило в дупі.
 
 
 
 

Є контакт!

Власне, коли я купував на DX.com перетворювач Serial — Wi-Fi, то був не стільки зачарований його можливостями (я про них тоді мав вкрай туманне поняття), скільки ціною — у порівнянні з тим же Wi-Fi-Шілд для Arduino. Зрозуміло, незабаром з'ясувалося, що різниця в ціні не просто так: з коробки перетворювач цей мало чим нагадує Шилд. І, так, розумію — це для вас очевидно, а для мене було одкровенням, що ніяких GET / POST просто так не буде.
 
Проте я майже відразу після покупки спробував HLK-RM04 у справі. Спочатку — і зовсім без мікроконтролера. Достатньо лише підключити живлення (5В) і піти з дефолтного адресою, яка вказана в керівництві. Там же, після деяких сумнівів (як би чого не запороти) вбив свої налаштування Wi-Fi і видав преобразователю статичний IP, щоб потім не шукати його по всій мережі.
 
Ось такі вийшли настройки. Забігаючи вперед — це повний комплект налаштувань для ваг (IP і SSID підставите свої, а ось швидкість порту досить важлива — на цій швидкості HLK-RM04 спілкуватиметься з контролером):
 
 
 
На другому етапі підключив до контролера і подивився, що можна зробити без бібліотеки і без зусиль. Виявилося, що якщо переключити його в режим сервера, підключити до послідовного порту Arduino, і написати код, який щось періодично друкує в цей самий порт, то потім в браузері можна подивитися друковане. Теж, в принципі, непогано, але не дуже надихало. Тим не менше, я це запам'ятав на самий крайній випадок.
 
Загалом, так би і валявся HLK-RM04 в запорошеній коробці, якби не Madrobots. Я розглядав будь-які варіанти: і змусити перетворювач працювати веб-клієнтом, і, якщо не вийде, то хоча б забирати з нього дані через Tasker на смартфоні, і тим же Tasker публікувати, куди мені треба. На щастя для першого варіанту знайшлася підходяща бібліотека WiFiRM04 , яка в кінцевому підсумку веде себе практично як бібліотека Wi-Fi для Arduino.
 
Є, звичайно, і специфіка. Бібліотека ця зібрана в розрахунку на Arduino Mega, у якої і оперативної пам'яті багато, і послідовних портів — вище даху. Тому автор бібліотеки по дефолту широким жестом використовував два порти Arduino Mega для спілкування з HLK-RM04, і в залишку мав підключення до десктопу через USB для одночасної налагодження.
 
І, до речі, з урахуванням також за умовчанням включеного модуля налагодження, навіть крихітна програма за участю бібліотеки WiFiRM04 компілліруется в трохи менше 30Кб, так що перспективи міграції на Arduino Pro Mini здавалися трохи сумнівними. Однак є і хороші новини: автор все ж передбачив і роботу з одним портом, і виключення отладочного модуля.
 
Про перший написано на GitHub, а про другу я дізнався з форуму Arduino.cc .
 
І все ж, якщо чесно, отлаживаемого я на Mega, бо здалося дуже вже клопітно постійно відключати-підключати HLK-RM04 — адже на Pro Mini він займає єдиний порт, тому навіть для банальної завантаження нової версії програми перетворювач потрібно відключати. Але є й хитрість: якщо після завантаження програми в Pro Mini підключити і HLK-RM04, і одночасно десктоп з монітором порту, то видно, що саме контролер відправляє преобразователю. А це дозволяє зрозуміти, в якому взагалі стані той перебуває.
 
У сухому залишку для адаптації WiFiRM04 під Arduino Pro Mini потурбувалися:
 
1) У at_drv.cpp зробити так:
 
 
#define DEFAULT_BAUD1			9600
#define DEFAULT_BAUD2			9600

 
2) Там же — ось так:
 
 
// use Serial1 as default serial port to communicate with WiFi module
#define AT_DRV_SERIAL Serial
// use Serial2 to communicate the uart2 of our WiFi module
#define AT_DRV_SERIAL1 Serial

 
3) Там же — закомментировать # define _DEBUG_
 
 
// #define _DEBUG_

 
4) Там же — не забути поставити свій улюблений цифровий пін в # define ESCAPE_PIN
 
 
#define ESCAPE_PIN				4

 
3) У wl_definitions.h змінити MAX_SOCK_NUM на 1
 
В обох випадках, що з Mega, що з Pro Mini працездатність цього гібрида вужа з їжаком перевіряв на практиці — відправляв запити на сервер, який міг фіксувати їх отримання.
 
 

Своя ноша то тягне, то не тягне

На наступному етапі я зайнявся вивченням питання про отримання даних з ваг. Теорія свідчила, що залежно від типу ваги комплектуються одним, двома або чотирма тензодатчиками. Відкривши звичайні ікеевскіе ваги я усвідомив, що тут, загалом, класична схема: кожній п'яті — по датчику, то є всього чотири датчика. Бентежило тільки кількість проводів.
 
. оригінальна плата ваг
 
 
По всьому виходило, що кожен датчик обладнаний трьома цими корисними штуками. Однак та ж теорія свідчила, що взагалі-у (постійних) резисторів два висновки.
 
. тензодатчик
 
 
Вдумливе вивчення Aliexpress показало наступне: хитрі китайці випускають датчики у вигляді половинки досить популярного вимірювального моста (міст Вітстоуна).
 
 
 
Сам міст виглядає ось так (картинка з Вікіпедії, посилання на яку поруч):
 
 image
 
І, в принципі, все було зрозуміло. Але я ніяк не міг второпати, як чотири напівмоста з'єднуються в один міст і навіщо це потрібно. Відповідь на першу частину завдання дала рідна плата ваг. Ті ж китайці, щоб не витрачати даремно мідь, підключили готові спільні точки куди треба: до джерела струму і до приймача потенціалу. А все інше з'єднали так, щоб отримати міст. У підсумку кожне фабричне пів плеч стало чвертю плеча:
 
 
 
Ну й добре.
 
Принцип вимірювання ваги досить простий: навантаження змінює опір тензодатчиків, що закономірним чином виливається в зміна напруги на виході моста. Причому напруга змінюється лінійно залежно від ваги. Загалом, все добре, якби не одне але: вихідна напруга моста занадто мало, щоб вимірювати його безпосередньо АЦП Arduino. Всемогутній інтернет говорить нам, що для успіху необхідний відповідний підсилювач — з досить великим коефіцієнтом посилення, з досить малим шумом.
 
Той же самий інтернет радить використовувати інструментальний операційний підсилювач INA125 виробництва всім відомої Texas Instruments. Безліч відповідних схемотехнічних рішень повторюють один одного як під копірку і практично не відходять від класичної схеми в даташіте. І все ж я вирішив бути оригінальним і вибрав для повторення ось таку схему :
 
 
 
Тут R1: 39 Ом, R2: 1 кОм, C1: 100 нФ, C2: 100 мкФ. До майданчиків 1, 2, 3 і 4 підключається вимірювальний міст, причому якщо я хоч щось розумію, то при підключенні до мосту особливо вибирати точки не потрібно, головне, щоб V +, V-, Vref і GND йшли через один, т. е., наприклад: Vref, V +, GND, V-. З іншого боку, я згодом не став ризикувати, а просто подивився позначення на рідній платі ваг, там було: E +, E-, S + і S-, і відповідним чином підключив все це до підсилювача (E + = Vref, E-= GND, S + = V +, S-= V-).
 
Здавалося б, все ясно: купив деталей — і сиди паяй підсилювач. Однак навіть у ДС виявилося якось пекельно складно придбати 1 (Один) підсилювач TI INA125, а особливо — в DIP. Ціни лежать діапазоні від 100 до 800 рублів, термін поставки — від завтра до через місяць, розташування контор і магазинів таке, що навіть у депресії починається депресія.
 
З силами я збирався приблизно місяць. Потім плюнув на все і замовив в бутіку Чип-і-Діп, але через їх сайт. Таким чином зекономив на насінні: якщо не помиляюся, то мінімальна партія з 20 резістров коштувала чи не менше, ніж один цей нещасний резистор в офлайновом магазині. Заодно з'ясував, що SOL16 — це набагато, немає НАБАГАТО менше, ніж я думав. І просто так в навіс його НЕ розпаяти.
 
Я майже засмутився, але вирішив все ж пошукати якесь рішення проблеми. Знайшлося в тому ж бутіку. Це готові хустки-перехідники, де тарган SOL16 раптово розчепірювати на майданчики з цілком осудним 2.5 мм кроком. На радощах купив штуки три. Навіщо вони мені — розуму не прикладу, все жаба.
 
. тарган на платі
 
 
Інші деталі, проте, розпаяв внавес. І ще здуру розпаяв вилки, як на Arduino. Думав, що буду підключати через розетки, для простоти. Однак реальність виявилася більш сувора, і в підсумку довелося замотати вилки, та й всю плату взагалі ізоляційною стрічкою. Тут ще один архітектурний косяк: ізолента — біла.
 
. до HLK-RM04 підключився через вилки (крок 2 мм). Виявилося — правильно, інакше б замучився паяти-відпаювати: адже на час налагодження потрібно відключатися від модуля, щоб звільнити порт контролера
 
 
Дивно (я завжди дивуюся, коли щось, що вийшло з-під моїх рук працює), але готова плата, підключена і до ваг, і до Arduino, стала видавати адекватні результати. Так що тепер у мене на руках були всі компоненти вагів: ваги з перетворювачем напруги і контролер з блоком зв'язку. Тому я відкинув пафос і обізвав першу версію програми альфою. Як виявилося — не дарма.
 
. майже бета: альфа використовувала світлодіод на платі контролера
 
 
В принципі, альфа непогано працювала, поки контролер був підключений до комп'ютера (на цей час я користувався або Mega, або Pro Mini, але — без HLK-RM04, по зазначеній вище причини браку послідовних портів). Однак при переході на автономне живлення виникло відчуття, що вся конструкція збожеволіла. Невелике розслідування показало, що я знову зіткнувся з проблемою, знайомої за Автомату світла і музики АСИМ-АУ-2-6 . А саме — постійні флуктуації значень при читанні аналогового пина контролера, які я пов'язую з імпульсною природою блоку живлення.
 
 Можете помилуватися на ці самі флуктуації в стані спокою
17.05.2014 23:55:51	user	28
17.05.2014 23:56:03	user	21
17.05.2014 23:56:28	user	11
17.05.2014 23:56:41	user	58
17.05.2014 23:56:53	user	10
17.05.2014 23:57:05	user	22
17.05.2014 23:57:18	user	30
17.05.2014 23:57:30	user	26
17.05.2014 23:57:42	user	9
17.05.2014 23:57:55	user	22
17.05.2014 23:58:07	user	28
17.05.2014 23:58:20	user	22
17.05.2014 23:58:32	user	29
17.05.2014 23:58:45	user	13
17.05.2014 23:58:57	user	26
17.05.2014 23:59:10	user	22
17.05.2014 23:59:22	user	44
17.05.2014 23:59:34	user	22
17.05.2014 23:59:47	user	58
17.05.2014 23:59:59	user	13
18.05.2014 0:00:11	user	29
18.05.2014 0:00:24	user	14
18.05.2014 0:00:36	user	51
18.05.2014 0:00:49	user	22
18.05.2014 0:01:01	user	11
18.05.2014 0:01:14	user	30
18.05.2014 0:01:26	user	27
18.05.2014 0:01:38	user	9
18.05.2014 0:01:51	user	29
18.05.2014 0:02:03	user	28
18.05.2014 0:02:16	user	9
18.05.2014 0:02:41	user	22
18.05.2014 0:02:53	user	8
18.05.2014 0:03:06	user	28
18.05.2014 0:03:18	user	27
18.05.2014 0:03:30	user	22
18.05.2014 0:03:43	user	27
18.05.2014 0:03:55	user	31
18.05.2014 0:04:07	user	22
18.05.2014 0:04:20	user	28
18.05.2014 0:04:32	user	22
18.05.2014 0:04:45	user	10
18.05.2014 0:04:57	user	24
18.05.2014 0:05:09	user	27
18.05.2014 0:05:22	user	22
18.05.2014 0:05:34	user	27
18.05.2014 0:05:47	user	23
18.05.2014 0:05:59	user	22
18.05.2014 0:06:12	user	14
18.05.2014 0:06:24	user	28
18.05.2014 0:06:36	user	59
18.05.2014 0:06:49	user	55
18.05.2014 0:07:01	user	27
18.05.2014 0:07:14	user	58
18.05.2014 0:07:26	user	27
18.05.2014 0:07:38	user	22
18.05.2014 0:07:51	user	24
18.05.2014 0:08:03	user	28
18.05.2014 0:08:16	user	57
18.05.2014 0:08:28	user	28
18.05.2014 0:08:53	user	28
18.05.2014 0:09:05	user	28
18.05.2014 0:09:18	user	24
18.05.2014 0:09:30	user	23
18.05.2014 0:09:43	user	8
18.05.2014 0:09:55	user	28
18.05.2014 0:10:07	user	13
18.05.2014 0:10:20	user	22
18.05.2014 0:10:32	user	36
18.05.2014 0:10:45	user	30
18.05.2014 0:10:57	user	26
18.05.2014 0:11:10	user	59
18.05.2014 0:11:22	user	57
18.05.2014 0:11:34	user	29
18.05.2014 0:11:47	user	27
18.05.2014 0:11:59	user	27
18.05.2014 0:12:11	user	28
18.05.2014 0:12:24	user	27
18.05.2014 0:12:36	user	17
18.05.2014 0:12:49	user	11
18.05.2014 0:13:01	user	21
18.05.2014 0:13:14	user	53
18.05.2014 0:13:26	user	54
18.05.2014 0:13:38	user	56

 
 
 
Вивчення форуму Arduino.cc показало, що я не один такий щасливий. Один з рекомендованих методів боротьби з цим ефектом — підтягнути аналоговий пін до землі, використовувати проводи мінімальної довжини. Але й проведення в мене були не довше 10 сантиметрів, та й аналоговий пін, як неважко бачити зі схеми, вже підтягнуть до землі. Тому довелося звернутися до перевіреної методикою: по-перше, додав в програму якесь порогове значення ваги, свідомо більше спостережуваних флуктуацій. А, по-друге, так як флуктуації були дуже помітними, розраховував вага як середнє досить великого, хоча і не нескінченно великого, кількості вимірів. Якщо точніше, то усереднюються тисячі вимірів, що більш-менш призводить флуктуації (та й значення взагалі) у більш-менш розумні межі.
 
Але це виявилася не єдина проблема. Я настільки звик покладатися на чужі схеми, що спочатку не зрозумів, чому ваги в якийсь момент починають показувати одну і ту ж величину. А потім стало ясно: це стеля вимірювань. Я не геній, тому витратив досить багато часу і на з'ясування цього нехитрого факту, і на вирішення проблеми.
 
Суть проблеми в тому, що коефіцієнт посилення INA125 задається резистором R1 за формулою:
 
 
 
Таким чином, посилення за версією авторів схеми встановлено на рівні 1500 (1542,5, якщо точніше). Мабуть, воно підбиралося під конкретний екземпляр ваг, про що я не замислювався. Для моїх ваг, якщо судити за результатами, це виявилося занадто. Тому я додав ще один резистор 39 Ом і отримав трохи інший результат: посилення в районі 773.
 
При цьому слід пам'ятати, що виходячи з того ж даташіта INA125 , напруга на виході підсилювача не перевищує 3.8В. Звідси отримуємо максимальну величину читання аналогового пина Arduino: (3,8 * 1024) / 5 = 778. До цього моменту я також встиг прикластися до ваг, тому з'ясував, що співвідношення між вагою і значенням на аналоговому піне становить приблизно 7,25.
 
Резюме: при поточному посиленні теоретичний межа вимірювань ваг становить близько 107 кг, що мене більш ніж влаштовує. Але якщо буде щось турбувати, тоді, напевно, поміняю резистор і підберіть до експлуатаційної кордоні в 150 кг. І пост-резюме для цікавих. Ваги я калібрував за іншими підлоговим ваг. Зрозуміло, що точність там плюс-мінус тапок, ну так і у оригіналу вона така ж.
 
Ніяких еталонних ваг не використав. Просто три виміру на одних, три виміру на інших. І середнє арифметичне кожного екземпляра ділимо один на одного. Звідси і коефіцієнт між тим, що отримує Arduino і реальним вагою.
 
 

Одні за всіх

Отже, у мене були відкалібровані ваги і залізячки, здатні передавати свідчення… куди-небудь, загалом, здатні. Залишилося зовсім небагато: написати робочу програму з многопользовательским багатовидову режимом і, по можливості, більш-менш зручним способом перемикання видів і користувачів.
 
Пояснення просте. Я хотів отримувати ідентифікуються результати зважування для себе, дружини і дати погратися гостям, не змішуючи їх результати зі своїми. А ще іноді хотів би зважувати наших котів, причому з мінімумом зусиль. Ви ж знаєте, як зважують котів, так? Береш кота на руки, встаєш на ваги, запам'ятовуєш вага, відпускаєш кота, знову встаєш на ваги. Потім з більшого вичитаєш меншу.
 
Загалом, слово за слово, а визначилося два види (люди і коти) і вісім користувачів. Тому, нагадую, більш-менш зручне управління було вкрай бажаним.
 
Від ідеї використання рідної РК-дисплея я відмовився практично відразу, як почитав про реверс-інжиніринг протоколу спілкування з ним. А куплений з нагоди 0.96-дюймовий OLED-екран я встиг убити десь в процесі експериментів (напевно, він все ж був на 3.3В, а не на 5В, як обіцяли китайці). Та й по честі, я страшенно облажався: на фото дисплей здавався досить великим, а за фактом виявився крихітним — з висоти зростання все одно нічого не було б видно. Крім того, ще на етапі тестування з'ясував, що разом бібліотека дисплея і бібліотека WiFiRM04 не уживаються. Очевидно, пам'яті у Pro Mini для них обох занадто мало.
 
Тому з коштів виведення залишалися пищалки і світлодіоди. Ліричний відступ: зараз до мене їде сегментний світлодіодний дисплей, який, можливо, прикручена до ваг, але, можливо і ні — аж надто мені подобається те, що вийшло. Світлодіодом я планував показувати готовність ваг до роботи, а пищалкой — озвучувати поточний статус, успішну відправку показань і помилки.
 
Що стосується засобів введення, то мені хотілося обійтися абсолютним мінімумом. От уявіть: ви стоїте на вагах, а ще якісь кнопочки, перемикачі — до чого це? Одночасно не хотілося і бути прикутим до смартфону, якщо припустити, що потрібна настройка активувалася б смартфоном. Тобто, в ідеалі управління з моєї ідеї повинно бути таким, що й серед ночі з закритими очима можна було б без питань зважитися.
 
І тут я подумав: «Гей, чувак, у тебе ж під ногами чотири прекрасних датчика ваги (ну ок, один синтетичний). Чому б не використати їх як всемогутню кнопку? ». Чесно скажу: половина особистості (діяльна) була в захваті від настільки витонченого вирішення проблеми; друга половина (лінива) — у глибокій тузі. Але ідея захопила, і через деякий час проб і помилок на світ з'явилася наступна концепція управління: перемикання режимів і користувачів натисканнями на стіл ваг, а індикація поточного статусу — звуковими сигналами.
 
Загалом «меню» приблизно таке:
 
1) Перше натискання після зважування або відразу після включення повідомляє поточний статус;
2) Наступні однократні натиснення перемикають користувачів в межах категорії;
3) Дворазове натискання перемикає режими (люди / коти);
4) Триразове натиснення виконує скидання налаштувань в значення за замовчуванням.
 
Звукова індикація теж досить проста. Один довгий сигнал означає людей, два довгих — котів. Подальше кількість коротких сигналів — номер користувача в черзі. У цій версії азбуки морзе, наприклад, тире-крапка-точка означає, що зважуємо людину номер два; а тире-тире-крапка — кота номер один.
 
Зрозуміло, на стіл ваг досить натискати носком стопи (і нема чого на карачках повзати). І, зрозуміло, тимчасові інтервали для натискань і аудіосигналів підібрані так, щоб це було комфортно для управління і сприйняття. Тобто є і акустичний фидбек натискання, і достатня пауза, щоб встигнути натиснути кілька разів поспіль за таймаут, відведений на команду.
 
Залишилася дрібничка — якось дізнатися власну вагу. А для цього я скористався хитрими інтернет-сервісами, і тому вага оголошує найближчий Android-девайс (планшет там або смартфон). У теорії, можна схрестити і з iOS, але мені не на чому перевірити. Затримка виходить украй невелика, а з урахуванням того, що це взагалі-ваги, які без інтернету і не повинні працювати, то концепція цілком життєздатна.
 
   
 

Чи є життя після HTTPS або як змусити ваги говорити

Результати вимірювань я відразу планував викладати в інтернет. Точно так само, як це роблю з поточним кліматом будинку і поряд з ним. Але з ряду причин мій улюблений Народний моніторинг для цієї мети не підходив. Зате цілком підходили таблиці Google, але вони вимагали підключення через HTTPS, чого Arduino витягнути ніяк не може — ні з рідним мережевим Шілд, ні, тим більше, з нерідною.

Але виявилося, є дуже навіть привабливе рішення — сервіс Pushing Box , така вся з себе спеціальна уведомлялка якраз для інтернету речей, якщо вірити опису. І вона, розумниця, бере всю цю SSL-магію на себе. А вивчення того ж Google призвело до рецептом схрещування Arduino і таблиць Google і ось ще важливо про кнопочку Submit .

У двох словах: робите форму в Google, заводите еккаунт на Pushing Box. Потім в Pushing Box:

1) Додаєте форму Google як сервіс CustomURL, де URL повинен бути види:

https://docs.google.com/forms/d/ID ВАШЕЙ ФОРМЫ/formResponse




2) Додаєте сценарій, який виглядає, наприклад, так:

?entry.11234123=$status$&&submit=Submit


Тут entry.11234123 — ім'я поля форми, яке можна подивитися в її вихідному коді. множинні поля, як зазвичай, об'єднуються через &.



3) З коду Arduino (або що там у вас) додавання даних проводиться HTTP POST / GET такого вигляду:

http://api.pushingbox.com/pushingbox?devid=v0123456789ABCDE&status=open


Тут devid — ідентифікатор, виданий Pushing Box, «open» — передане значення.

Повертаючись до оголошення результатів. Цим займається зв'язка Pushing Box і Newtifry , який існує в двох іпостасях: сервісу Pushing Box і аппа для Android (є аналогічний сервіс для iOS).

Причому я завів відразу кілька сценаріїв — по одному на кожного користувача-людини і один загальний для котів. Просто для того, щоб потім можна було гнучко налаштувати чий смартфон і коли оголошує вагу.

У підсумку мій HTTP-запит до Pushing Box виглядає приблизно так:

http://api.pushingbox.com/pushingbox?devid=v0123456789ABCDE&name=имя&name1=username&weight=99.9


Рядок вистраждана. Виявилося, що Newtifry відмінно передає кирилицю TTS Android, але та ж кирилиця перетворюється на кракозябри в таблиці Google. Тому я передаю в Pushing Box одне і те ж ім'я двічі: кирилицею і латиницею. Перше через Newtifry озвучується смартфоном, а другий через CustomURL відправляється в таблицю.

До речі, позначку про час Google проставляє автоматично, тому про неї у мене голова не болить:



Конструкція

У моїй конфігурації для складання ваг потрібно:

1) Ваги з 4 тензодатчиками. Наскільки я зрозумів, це досить популярна зараз конструкція, але не можу гарантувати, що всякі зроблені саме так. Важливий момент: кліренс ваг повинен бути таким, або у ваг повинен бути такий корпус, щоб туди помістилася вся електроніка. Інакше вам доведеться класти коробочку поруч.

2) Перетворювач Serial-Wi-Fi HLK-RM04. Ось, наприклад .

3) Інструментальний підсилювач INA125 в будь-якому корпусі, з яким зумієте розібратися.

4) Обважування для підсилювача (конденсатор 100 мкФ, 100 нф, резистор 1 кОм, резистор завдання коефіцієнта посилення).

5) Світлодіод

6) Пищалка. У мене — п'єзокерамічним, тому підключається до контролера безпосередньо (досить високий опір). Для динаміків вам доведеться зробити деяку подобу підсилювача, так що вирішуйте самі, що вам більше подобається.

7) Arduino Pro Mini. Ось, наприклад .

8) Проведення, блок живлення 5В та інші паяльники.

Начерк схеми виглядає таким чином:



Необхідні коментарі. Я взяв першу ліпшу малювалки, але в її базі була тільки Arduino nano. У наших цілях різниці немає ніякої, але майте на увазі, що в моїй версії — Pro Mini.

Номінал резистора залежить від світлодіода, який вам попадеться.

Типову схему включення INA125 ви мали щастя спостерігати вище. Там вам знадобиться тільки підібрати резистор R1 під свої ваги. Це, на жаль, тільки досвідченим шляхом.

Пищалка — п'єзокерамічним. НЕ МОЖНА підключати до контролера звичайні динаміки безпосередньо. Уб'єте, причому не динамік.

Дивно, але факт — все, що мені було потрібно, якимось дивом помістилося в оригінальному корпусі ваг. Хіба що я варварським способом видалив звідти навіть найменший натяк на батарейний відсік. А дисплей, нехай і неробочий, залишив — він чудово маскує неподобство всередині.

Алгоритм

Якщо чесно, це перший раз, коли я малюю алгоритм. До того ж — після того, як все зробив. Зате ось намалював і побачив принаймні одну річ, яку можна оптимізувати в наступній версії програми.



З кодом ситуація аналогічна резистору для коефіцієнта посилення. Від вас буде потрібно підібрати три величини:

1) tare — «порожній» вагу ваг при включенні
2) treshold — відхилення від порожнього ваги (якщо буде)
3) співвідношення між значенням на аналоговому вході Arduino і вагою. Поточне — 7.25, задається в розділі процедурі sendWeight

Секретний код

#include <avr/pgmspace.h> // для PROGMEM
#include <SimpleTimer.h> // http://playground.arduino.cc//Code/SimpleTimer
#include <WiFiRM04.h>

char ssid[] = "YOUR SSID"; //  your network SSID (name) 
char pass[] = "YOUR NETWORK KEY";    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;            // your network key Index number (needed only for WEP)

int status = WL_IDLE_STATUS;
char server[] = "api.pushingbox.com";    // name address for Google (using DNS)
WiFiRM04Client client;

#define ledPin 8
#define weightPin A0
#define tonePin 10
#define weightPowerPin 9

byte tare = 28; // idle weight
byte treshold = 30; // weight sensor value treshold

int i;
int scaleTimeoutID;
byte nameSelectorCount = 0; // counter for name selection
byte stepCount = 0;
byte human = 0; // default setting
byte cat = 0; // default setting

float wt;
long weight, brutto, netto;
long prevWeight = 0;


boolean idleWeight = false;
boolean clearToSend = false;
boolean ishuman = true;
boolean nameSelect = false;
boolean stepCounting = false;
boolean stepTimeout = false;
boolean firstStep = true;


// строки в PROGMEM
prog_char statusString_0[] PROGMEM = "devid1&name="; // User 1
prog_char statusString_1[] PROGMEM = "devid2&name="; // User 2
prog_char statusString_2[] PROGMEM = "devid3&name="; // User 3
prog_char statusString_3[] PROGMEM = "devid4&name="; // Cats
prog_char statusString_4[] PROGMEM = "Юзер 1"; // cyrillic names
prog_char statusString_5[] PROGMEM = "Юзер 2";
prog_char statusString_6[] PROGMEM = "Юзер 3";
prog_char statusString_7[] PROGMEM = "Кот 1";
prog_char statusString_8[] PROGMEM = "Кот 2";
prog_char statusString_9[] PROGMEM = "Кот 3";
prog_char statusString_10[] PROGMEM = "Кот 4";
prog_char statusString_11[] PROGMEM = "Кот 5";
prog_char statusString_12[] PROGMEM = "&name1=User 1"; // latin names for Google Spreadsheet
prog_char statusString_13[] PROGMEM = "&name1=User 2";
prog_char statusString_14[] PROGMEM = "&name1=User 3";
prog_char statusString_15[] PROGMEM = "&name1=Cat 1";
prog_char statusString_16[] PROGMEM = "&name1=Cat 2";
prog_char statusString_17[] PROGMEM = "&name1=Cat 3";
prog_char statusString_18[] PROGMEM = "&name1=Cat 4";
prog_char statusString_19[] PROGMEM = "&name1=Cat 5";
prog_char statusString_20[] PROGMEM = " HTTP/1.1";
prog_char statusString_21[] PROGMEM = "Host: api.pushingbox.com";
prog_char statusString_22[] PROGMEM = "User-Agent: Arduino";


// табличка указателей на строки
PROGMEM const char *statusString[] = 	   
{   
  statusString_0,
  statusString_1,
  statusString_2,
  statusString_3,
  statusString_4,
  statusString_5,
  statusString_6,
  statusString_7,
  statusString_8,
  statusString_9,
  statusString_10,
  statusString_11,
  statusString_12,
  statusString_13,
  statusString_14,
  statusString_15,  
  statusString_16,
  statusString_17,
  statusString_18,
  statusString_19,
  statusString_20,
  statusString_21,
  statusString_22};
  
char statusStringBuf[40];    // буфер для чтения строк состояния


SimpleTimer scaleTime;

void setup() {

Serial.begin(9600);

pinMode(weightPowerPin, OUTPUT);
digitalWrite(weightPowerPin, HIGH);

pinMode(weightPin, INPUT); // weight sensor

pinMode(ledPin, OUTPUT); // led Power

pinMode(7, OUTPUT);
digitalWrite(7, LOW); // led GND

  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
      while(true) {
   }
  }
  // attempt to connect to Wifi network:
  while (status != WL_CONNECTED) { 
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:    
    status = WiFi.begin(ssid, pass);
  
    // wait 10 seconds for connection:
    delay(10000);
   } 


}

void loop() {
scaleTime.run();
averageWeight();

// CHECK FOR IDLE WEIGHT
if ((idleWeight == false) && (weight < (tare+treshold))) { // check if there is still somebody on table
    idleWeight = true; // set flag to enable measurements in case there is nobody on table
    digitalWrite(ledPin, HIGH); // turn on ready led

    }

// DETECT STEP-ON
if ((idleWeight == true) && (weight > (tare+treshold))) { // check if there is still somebody on table


        idleWeight = false;
    digitalWrite(ledPin, LOW); // turn on ready led
        tone(tonePin, 450, 100); // audio confirmation for step-on

        if (stepCounting == false) {

      scaleTime.setTimeout(250, stepDetector);
          
          stepCounting = true; }
    }

// READY TO SEND
if (clearToSend == true) { // check for upload permission
    sendWeight(); // upload
    }


}

// Detect step-on
void stepDetector() {

    averageWeight();
    
    if (weight < (tare+treshold)) { // step detected

          if (firstStep == false) { // do not count first step

	stepCount = stepCount + 1; // count step-ons: only announce current name after first step-on; switch names on consecutive step-ons.
          
          if (stepTimeout == false) { // set timeout to confirm last step: i.e. if no one steps on table for specified time, scales must switch to stby or weighing mode
            scaleTimeoutID = scaleTime.setTimeout(1500, stepTotal);

            stepTimeout = true; }

//          tone(tonePin, 450, 100); // audio confirmation for step-on

          if (stepTimeout == true) {
            
            scaleTime.restartTimer(scaleTimeoutID);
          }            
            
          } else {
            
            statusNotify(); // announce current status
            
            stepCount = 0;
          }
                
        firstStep = false;
          
      } else {

        if (ishuman == true) {
	getWeight(); 
                netto = weight;
	}
    else {
	getWeight();
                if (clearToSend == true) {
	  brutto = weight;
	  tone(tonePin, 550, 800);
	  delay(3000);
	  getWeight();
	  if (brutto > weight) {
	    netto = brutto - weight;
	  } else {
	    netto = weight - brutto;
	    }
                  netto = netto+29;
	}
            }
 
        
      }


      stepCounting = false;

}

void stepTotal() {
  
// tone(tonePin, 550, 1000);
 
 if (stepCount == 1) {
   if (ishuman == true) {
     human = human+1;
     if (human > 2) {
       human = 0;
     }
     
   } else {
     cat = cat+1;
     if (cat > 4) {
       cat = 0;
     }
     
   }
 }
 
 if (stepCount == 2) {
   if (ishuman == true) {
     ishuman = false;

   } else {
     ishuman = true;

   }
 }
 
 if (stepCount > 2) {
   ishuman = true;
   human = 0;
   cat = 0;
   firstStep = true;
 }
 
 delay(1500);
 
 statusNotify();
 
 stepTimeout = false;
 stepCount = 0;

  
}

void statusNotify() {
 
               if (ishuman == true) {
                
                tone (tonePin, 550, 600);
                delay(800);
                makeSound(human+1, 300);
              } else {
                
                tone (tonePin, 550, 600);
                delay(1000);
                tone (tonePin, 550, 600);
                delay(1000);
                makeSound(cat+1, 300);
              } 
  
}

    
// MEASURE WEIGHT

void getWeight() {


averageWeight();
        
    if (weight > (tare+treshold)) { // object on table still heavier than idle weight


	digitalWrite(ledPin, LOW); // turn off ready led
	delay(3000); // let everything settle
	averageWeight();

                prevWeight = weight;  // save initial weight for future reference

                averageWeight();

	if ((((prevWeight - treshold) < weight) && (weight < (prevWeight + treshold))) && prevWeight > (tare)) { // check if measurement average consistent with initial load (ensure object is on table)
	    clearToSend = true; // set flag for upload

	    } else { makeSound(2, 300); // measurement error
		     clearToSend = false; // prevent upload
		     idleWeight = false; // set flag to grant subsequent measurements
		}
	}
}


// HTTP SENDER

void sendWeight() {
  netto = netto - tare;
  wt = (float)netto / 7.25;


 if (client.connect(server, 80)) {

   client.print("GET /pushingbox?devid=");

    if (ishuman == true) {
          
          strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[human]))); // devid
          client.print(statusStringBuf);
          
          strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[human+4]))); // name - name for audio notificaion
          client.print(statusStringBuf);
          
          strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[human+12]))); // name1 - name for Google
          client.print(statusStringBuf);
          
          
    } else {

          strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[3]))); // devid
          client.print(statusStringBuf);
          
          strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[cat+7]))); // name - name for audio notificaion
          client.print(statusStringBuf);
          
          strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[cat+15]))); // name1 - name for Google
          client.print(statusStringBuf);
    }
 
    client.print("&weight=");
    client.print(wt);
    
    for (byte clientCounter=20; clientCounter<23; clientCounter++) {
      strcpy_P(statusStringBuf, (char*)pgm_read_word(&(statusString[clientCounter])));
      client.println(statusStringBuf);
    }
    client.println();
    client.stop();  
    makeSound(3, 300); // confirm upload
      }
else {
    makeSound(2, 300); // upload error
    }

clearToSend = false; // clear flag
firstStep = true;


}

// BEEPER

void makeSound(byte count, byte duration) {

for (i = 0; i<count; i++) {
    tone(tonePin, 550, duration);
    delay(600);

    }

}

// CALCULATE AVERAGE WEIGHT
void averageWeight() {
  weight = 0;
	for (i = 0; i<999; i++) {
	    weight = weight+analogRead(weightPin); // capture measurements
	    }
	
	weight = (weight/1000); // measurement average
}




Мабуть, це все, що я можу розповісти. Останнє: я впевнений, що тут чудові, технічно грамотні і геніальні в плані ідей люди. Я вірю, що ви можете зробити краще. Робіть. А я ось зробив так, як зробив, і нічого більше з мене не молодий :)

Ах так, трохи не забув. Чому з блоком живлення, а не на батарейках. По-перше, і це найголовніше — я так і не навчився працювати з енергозбереженням. А, по-друге, так виходить, що типовий код підключення до Wi-Fi з бібліотекою WiFiRM04 виконується до півхвилини, тому якщо кожен раз включати, то доведеться довго чекати. Хіба хто буде користуватися такими вагами? І одночасно HLK-RM04 дуже багато їсть — 140 мА згідно даташіту по режиму Only Wi-Fi.

Тому найрозумнішим виявився стаціонарний джерело живлення.

ps. якщо де побачите компромат, будьте ласкаві, скажіть — я зірочками, что-ли, запікані.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.