Из заголовка нетрудно видеть, что на это приключение меня сподвигло банальное стремление сплагиатить заголовок парней из 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 показало следующее: хитрые китайцы выпускают датчики в виде половинки довольно популярного измерительного моста (мост Витстоуна).
Сам мост выглядит вот так (картинка из Википедии, ссылка на которую рядом):
И, в принципе, все было понятно. Но я никак не мог взять в толк, как четыре полумоста соединяются в один мост и зачем это нужно. Ответ на первую часть задачи дала родная плата весов. Те же китайцы, чтобы не тратить зря медь, подключили готовые общие точки куда надо: к источнику тока и к приемнику потенциала. А все остальное соединили так, чтобы получить мост. В итоге каждое фабричное полуплечо стало четвертью плеча:
Ну и ладно.
Принцип измерения веса довольно прост: нагрузка изменяет сопротивление тензодатчиков, что закономерным образом выливается в изменение напряжения на выходе моста. Причем напряжение меняется линейно в зависимости от веса. В общем, все хорошо, если бы не одно но: выходное напряжение моста слишком мало, чтобы измерять его напрямую АЦП 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.
Поэтому самым разумным оказался стационарный источник питания.
P.S. Если где увидите компромат, будьте добры, скажите — я звездочками, что-ли, запикаю.
Автор: spc