Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Действительно умная коробка

Компоненты и расходные материалы

Акриловый лист (например, плексиглар 3 мм)
× 1
Arduino MKR FOX 1200
× 1
Усилитель тензодатчиков SparkFun HX711
× 1
датчик нагрузки 5 кг
× 2
Adafruit BME280
× 1

Необходимые инструменты и машины

Лазерный резак (универсальный)
3D-принтер (общий)

Приложения и онлайн-сервисы

Tinamous
Sigfox

Об этом проекте

Платформа Really Smart Box превращает Really Useful Box (TM) в интеллектуальный ящик для хранения, подключенный к Интернету, для мониторинга запасов. Основанный на Sigfox Arduino MKR FOX 1200, он определяет вес вещей, хранящихся в ящике, а также температуру и влажность и использует маломощное радио Sigfox для передачи этой информации.

Пример использования - Хранение нити в 3D-принтере:

Если у вас есть 3D-принтер, вы, скорее всего, будете заботиться о том, как хранится ваша нить, это верно не только для нити для принтера, но и для многих других вещей, которые необходимо хранить в приемлемых диапазонах температуры и влажности (например, малярный герметик может стать непригодным для использования, если подвергнуть его воздействию). до отрицательных температур).

Как один из людей, отвечающих за техническое обслуживание 3D-принтеров в моем локальном производственном помещении, мне необходимо обеспечить наличие достаточного запаса нити накала и ее сухость.

С помощью Really Smart Box я могу контролировать вес нити накала и знать, становится ли он низким, а также контролировать уровень влажности в коробке, чтобы определить, нуждается ли силикагель в замене.

Пример использования - Контроль запасов расходных материалов:

Подрядчик по уборке может пожелать сохранить запас освежителя воздуха, мыла для рук или других расходных материалов на территории клиента, клиент, скорее всего, не разрешит подрядчику доступ к Wi-Fi или питание такого устройства, когда оно отсутствует, однако компания-подрядчик должна знать, когда отправлять новые запасы, добавляя накладные расходы ко времени уборщиков и лишние документы, которые никому не нравятся.

Платформа Really Smart Box просто помещается в ящик для хранения, поскольку она использует Sigfox, ей не нужно подключаться к сети клиентов, и она имеет низкое энергопотребление, поэтому работает от набора батарей. Поскольку содержимое коробки будет меняться очень редко, Arduino можно держать в состоянии низкого энергопотребления большую часть времени, что помогает продлить срок службы батареи.

Платформе можно сообщить вес типа предметов, хранящихся в коробке (то есть освежителей воздуха), и, следовательно, рассчитать, сколько их находится в коробке. Затем его можно отправить в компанию-подрядчика по уборке, чтобы предупредить их, когда им нужно будет доставить больше топлива на объект клиента.

Создание платформы:

Платформа имеет простую конструкцию, основной корпус сделан из двух кусков акрила, вырезанного лазером (я использовал 3 мм, для тяжелых предметов, таких как нить для принтера, лучше было бы толще) с парой тензодатчиков между ними.

Я вручную утопил отверстия под винты для тензодатчика, чтобы получить более красивую отделку, мне еще предстоит найти лазерный резак, который выполняет зенковку!

Акрил можно обрезать до любого размера, чтобы он соответствовал корпусу, который вам нравится, однако следите за расположением тензодатчиков и длиной провода на них, так как он довольно короткий. Верхний акриловый лист немного меньше нижнего, чтобы он не цеплялся за стенки коробки.

Нижний лист имеет вырез, позволяющий установить электронику на акрил без прокладок, а припаянные ножки устройства торчат сквозь него. Я использовал метчик M3 на угловых участках выреза, чтобы напрямую прикрутить к нему печатную плату. По углам также были установлены ножки, напечатанные на 3D-принтере, чтобы винты датчика нагрузки, не находящиеся заподлицо с акрилом, не повлияли на баланс.

Вес измеряется с помощью двух датчиков веса 5 кг. Набор из 4 штук обычно используется в весах для ванных комнат, и это могло бы быть лучше, однако мне не удалось найти хороший способ прикрепить их к акрилу, а также обеспечить расстояние, необходимое для электроники.

Тензодатчики нуждаются в некоторой прокладке сверху и снизу, чтобы обеспечить небольшой прогиб, а фактические датчики тензодатчика (белый бит на картинке) тензодатчика толще, чем монтажный блок. Это достигается под датчиком нагрузки с помощью двух торцевых пластин "Really Smart Box", напечатанных на 3D-принтере, которые имеют небольшой блок для подъема датчика нагрузки вверх, а наверху датчика нагрузки есть несколько вырезанных лазером акриловых прокладок.

Весоизмерительные ячейки подключены к усилителю весоизмерительной ячейки HX711. У него есть два канала (A и B), которые можно выбрать, что идеально подходит для этого использования.

Каждый тензодатчик состоит из тензодатчиков в конфигурации моста Уитстона, это создает несбалансированную пару делителей потенциала, когда тензодатчик находится под нагрузкой, сопротивление тензодатчиков изменяется и, следовательно, создается разница между двумя делителями потенциала. , это усиливается и измеряется HX711, который выполняет для нас аналого-цифровое преобразование.

Я использовал два датчика веса 5 кг для этого проекта, вы можете получить разные рейтинги (например, 1 кг и 10 кг), которые работают точно так же, но с разной чувствительностью.

При размещении датчиков веса убедитесь, что стрелка на конце ячейки указывает вниз (в направлении нагрузки). Ячейки имеют резьбовые отверстия M5 на одном конце (обычно на фиксированном конце) и M4 на другом (сторона, на которую возлагается нагрузка.

Красный / черный провода - это питание, они питают верхнюю и нижнюю часть делителей потенциала и распределяются между двумя датчиками веса. Зеленый и белый - это провода считывания от середины делителей потенциала, они подключены к каналам A и B на HX711.

HX711 поддерживает 3 коэффициента усиления, однако они также используются для выбора канала. Коэффициенты усиления 128 и 64 доступны на канале A, тогда как при выборе усиления 32 выбирается канал B. это означает, что наш второй канал не будет таким чувствительным, как основной, это нормально для этого приложения.

HX711 можно подключить к любым цифровым выводам на Arduino, я использовал D0 (данные) и D1 (часы), тогда усилитель просто нужно подключить к источнику питания 3v3 Arduino.

Вы можете узнать больше о тензодатчиках и HX711 в отличном руководстве по тензодатчикам SparkFun.

Наконец, BME280 подключен к шине I2C и используется для измерения температуры и влажности внутри коробки, его также можно использовать для измерения давления, однако это, вероятно, не представляет особого интереса, и у нас есть только 12 байтов данных sigfox, с которыми можно поиграть, так что это не сообщается.

Электроника установлена ​​на плате ThingySticks Arduino Prototype, я добавил держатель батареи (термоклей приклеен к нижнему акриловому листу) и подключил антенну, которая имеет красивую плоскую конструкцию, поэтому идеально подходит для платформы.

Калибровка тензодатчика:

Прежде чем мы сможем использовать платформу, необходимо откалибровать весоизмерительные ячейки. Каждый весоизмерительный датчик уникален, он сделан путем прикрепления тензодатчика к металлическому блоку и просверленных в нем отверстий для обеспечения достаточного изгиба, который можно ощутить без щелчка, из-за этого нам необходимо откалибровать каждый датчик веса для его реакции на вес. .

После калибровки мы применяем уравнение y =mx + c к измеренному значению АЦП (x), чтобы получить фактический вес (y). Поэтому нам нужно найти c (смещение) и m (наклон) для нашего тензодатчика.

Я снял верх основной платформы и по очереди прикрепил небольшой акриловый квадратик к каждому датчику веса и контролировал измеренные значения (это маршрутизация в прошивке для этого, которую можно запустить, отправив букву «c» на последовательный порт.

Первоначально были измерены показания для пустой платформы, это дает значение смещения (c), затем, поместив груз известного веса на ячейку, разница в показаниях дает нам наклон.

  Наклон =(измерено - смещение) / вес (г).  

Я использовал небольшую банку с освежителем воздуха (около 230 г) и катушку с нитью для принтера (около 1,5 кг), чтобы проверить значения, обе дали примерно одинаковый наклон, что обнадеживает.

Естественно, смещение, измеренное с помощью небольшой акриловой подушки, отличается от смещения, полученного с полным верхним листом, также они также имеют небольшую разницу в наклоне при использовании обоих датчиков веса, поэтому требуется вторичная калибровка. На данный момент используется смещение нуля на одну точку (тара), оно установлено в прошивке, но также может быть установлено с помощью последовательного USB-соединения или через сообщение нисходящего канала Sigfox после развертывания.

Подключение к Sigfox:

Подключив Really Smart Box, я сначала использовал последовательный порт USB для контроля вывода, чтобы помочь отладить и настроить систему. Таким образом, вы можете увидеть отдельные весоизмерительные ячейки, изменения и шум. Однако это не сработает для развернутого бокса, поскольку он должен быть полностью беспроводным.

С помощью Sigfox мы можем отправлять 12 байтов данных до 140 раз в день в нашу онлайн-службу, этого более чем достаточно для Really Smart Box. Приведенная ниже структура данных, используемая в Arduino, описывает, как мы используем 12 байтов.

  typedef struct __attribute__ ((упаковано)) sigfox_message {uint8_t status; // флаги состояния int8_t влажность; // влажность ::int:8 - некоторые датчики (HTU21D) читают -ve влажность) int8_t temperature; // температура ::int:8 (без десятичных знаков). int16_t zeroWeight; // zeroWeight ::int:16:endian int16_t weight; // вес ::int:16:с прямым порядком байтов int16_t itemCount; // itemCount ::int:16:little-endian (100-кратное фактическое количество элементов, чтобы учесть 2,01 (поскольку вес не будет точно совпадать) int8_t driftCorrection; // Коррекция дрейфа для изменений нулевого веса, примененного к весам int8_t filler; // Здесь ничего не видно, двигайтесь дальше .... int8_t lastStatus; // Последний статус sigfox} SigfoxMessage;  

Первый байт (статус) разбивается на битовые флаги для обозначенных проблем:

  // status ::uint:8 -> Разделить на 8 бит // B7 - Первый запуск // B6 - Ошибка HX711 // B5 - Ошибка BME280 // B4 - Авария по температуре // B3 - Авария по влажности // B2 - Аварийный сигнал веса // B1 - Низкий запас // B0 - запасной  

Эта структура сжимается до 12 байтов, однако нам нужно распаковать ее на конце Sigfox, чтобы отправить на Tinamous. Для этого мы используем настраиваемую конфигурацию полезной нагрузки, и лучше всего решить эту проблему, поскольку структура данных определена. Наш:

  firstRun ::bool:7 hx711Fault ::bool:6 bmeFault ::bool:5 temperatureAlarm ::bool:4 влажностьAlarm ::bool:3 weightAlarm ::bool:2 lowStock ::bool:1 b0::bool:0 status ::int:8 влажность ::int:8 temperature ::int:8 zeroWeight ::int:16:вес с прямым порядком байтов ::int:16:little-endian itemCount ::int:16:little -endian  

Пользовательская полезная нагрузка разбивает наши 12 байтов по мере анализа.

Обратите внимание, что нам нужно указать прямой порядок байтов для всего, что больше 1 байта, поскольку Sigfox по умолчанию использует обратный порядок байтов, а Arduino использует прямой порядок байтов (т.е. младший байт стоит первым в многобайтовых словах).

Также обратите внимание, что разделение логических флагов в первом байте не продвигает маркер байта, как это происходит со всеми другими чтениями, поэтому байт состояния, который содержит все флаги, также читается, чтобы пропустить первый байт.

Включенные во флаги сигнальные флажки температуры, влажности и диапазона веса, мы могли бы использовать онлайн-сервис (например, Tinamous) для отслеживания температуры, влажности и веса вне диапазона, однако они могут быть недолговечными (несколько часов) и наш ящик могут отправлять нечасто (один или два раза в день), что в результате может быть легко пропущено, поэтому они помечаются на устройстве и отправляются (и сбрасываются после успешной отправки).

Количество элементов фактически установлено в 100 раз больше фактического количества элементов. Я хотел разрешить такие значения, как 2,2 (из-за ошибки веса или других элементов в поле) без принудительного округления, аналогично 2,95 можно округлить до 2, если мы не будем осторожны, и это будет больше наводить на мысль о 3 элементах в коробка и небольшая ошибка. Я также не хотел использовать число с плавающей запятой, которое потребовало бы больше места, поэтому я использовал 16-битное слово и применил коэффициент, позволяющий легко преобразовать (он также подписан, чтобы разрешить нулевую ошибку, что может привести к уровню запасов -1 или -2 и т. д.)

Для включения связи Sigfox нужно сделать очень немногое. В Arduino добавляется библиотека Sigfox и вызываются соответствующие функции для вывода данных в соответствии с примерами Arduino для библиотеки Sigfox, однако нам необходимо зарегистрировать наше устройство в Sigfox.

Отправка «s» на последовательный порт Really Smart Box распечатывает Sigfox ID и PAC-код, они используются для активации устройства на бэкэнде Sigfox. Затем мы переходим к бэкэнд-службе активации Sigfox и следуем указаниям мастера, сначала выбирая наше устройство, затем страну / поставщика, а затем некоторые детали.

И, наконец, наше устройство активировано и внесено в список:

Sigfox назначает устройства группировке типов устройств, что разумно, поскольку обычно у вас может быть много (сотни, тысячи и т. Д.) Устройств одного и того же типа, с которыми вы хотите действовать как группа. Определив тип устройства, мы можем настроить пользовательский обратный вызов для передачи полученных данных в нашу онлайн-службу. Для этого я использую Tinamous (подсказка:см. Мое имя в профиле - возможно, мой выбор будет необъективным).

Использование действительно умного бокса:

После подключения, соединения болтами, прошивки прошивки и установки батарей платформа просто помещается в действительно полезную коробку (tm) и готова к работе.

Питание должно быть подано как можно позже, так как после включения устройства оно отправит первое сообщение Sigfox через 2 минуты и запросит данные нисходящего канала вместе с ним. Эти данные могут включать команду «Обнулить» для обнуления веса платформы. В противном случае требуется либо последовательное соединение USB, либо ожидание следующего запроса нисходящего канала - они выполняются каждые 12 часов.

После запуска и запуска платформа будет публиковать сообщения Sigfox каждые 15 минут, чтобы отправлять данные о весе, количестве предметов, температуре, влажности и состояниях сигналов тревоги. Температура, влажность и вес измеряются каждую минуту, чтобы убедиться, что они не выходят за пределы допустимого диапазона, при этом сигналы тревоги помечаются для следующей передачи, если они были повышены.

Мониторинг с помощью Tinamous:

Tinamous поддерживает пользовательские обратные вызовы Sigfox, добавляя «Sigfox Bot» в нашу учетную запись. Инструкции о том, как это сделать, можно найти в моем руководстве по Hackster.io «Get Your Sigfox On».

Когда вы добавляете Sigfox Bot в свою учетную запись Tinamous, если вы включаете настройки API, Sigfox Bot будет искать ваши устройства и добавлять их в вашу учетную запись Tinamous, однако вам не нужно этого делать, поскольку устройство будет автоматически добавлено, когда данные опубликовано.

Когда вы добавите бота, нам будет представлен экран конфигурации обратного вызова, который поможет настроить обратные вызовы Sigfox.

Затем вы можете создать собственный обратный вызов в Sigfox, обратите внимание, что Really Smart Box использует обратный вызов DATA -> BIDIR, который обрабатывает обычный обратный вызов UPLINK и обратные вызовы BIDIR (восходящий и нисходящий).

Здесь пригодится пользовательская полезная нагрузка, полученная ранее, вставьте ее из исходного кода в пользовательскую полезную нагрузку и обновите раздел полей, чтобы отразить это.

Lat и Lng указываются в этом обратном вызове, который дает приблизительное местоположение, однако Sigfox на Arduino поддерживает улучшенную настройку местоположения, но для этого требуется второй обратный вызов. Если вы используете функцию геолокации, не указывайте широту / долготу в этом сообщении, так как Really Smart Box будет перемещаться между местоположениями.

После того, как это настроено, его также необходимо включить для нисходящего канала, по умолчанию он отключен, даже если был установлен BIDIR.

Обратите внимание на снимок экрана под параметром «Нисходящий канал» отмечен «отмеченным», это необходимо сделать вручную и может быть недоступно, если для параметра «Тип устройства» не задано значение «ОБРАТНЫЙ ВЫЗОВ» для данных по нисходящему каналу (Тип устройства -> Изменить -> Данные по нисходящему каналу). .

С помощью обратного вызова Downlink мы также хотим указать обратный вызов SERVICE -> ACKNOWLEDGE, чтобы знать, что наше устройство получило данные downlink. При нажатии на Sigfox Bot в Tinamous отображаются другие конфигурации обратного вызова, следуйте инструкциям для обратных вызовов ACKNOWLEDGE и GEOLOC.

Обратите внимание, что вам нужно скопировать заголовок авторизации из первого обратного вызова восходящего канала / двунаправленного канала, поскольку это пароль, который односторонне зашифрован в Tinamous и больше не доступен для отображения.

С нашими обратными вызовами данные, опубликованные нашим устройством, теперь должны отправляться в Tinamous. Мы также можем добавить обратные вызовы по электронной почте в Sigfox, которые могут помочь подтвердить, что данные поступают (но также могут очень быстро стать очень шумными).

Настройка устройства Tinamous:

Как только устройство Sigfox будет замечено на Tinamous (либо через поиск api, либо через обратный вызов), оно будет показано на странице «Устройства», отсюда мы сможем редактировать свойства. Поля добавляются автоматически по мере их поступления из обратного вызова Sigfox, поэтому лучше подождать, пока устройство публикует данные.

Я установил время «Не отправлять отчеты после» на 1 час (в настоящее время он публикуется каждые 15 минут), чтобы я мог определить, сломано ли устройство, и, при необходимости, получить уведомление об этом.

Я не хотел видеть все поля, отправленные устройством, на странице диаграммы / сведений (их будет много, если вы включите все флаги), поэтому Tinamous настроен только на отображение веса и количества предметов. Здесь также были применены удобные для человека ярлыки и единицы измерения.

Поле «Счетчик предметов» в 100 раз больше фактического количества предметов, поэтому к этому полю применяется калибровка, чтобы уменьшить его в 100 раз.

Установлены некоторые данные нисходящего канала, которые приведут к обнулению Really Smart Box и применению ограничений диапазона температуры и влажности при следующем запросе сообщения по нисходящему каналу (через 2 минуты после включения, затем один раз каждые 12 часов).

Просмотр информации о действительно умном боксе:

Теперь поля устройства настроены, мы можем отслеживать их на странице сведений об устройстве (обратите внимание, что я не обнулял платформу в настоящее время, поэтому он считает, что присутствует 1/2 единицы - я также заменил акриловую крышку на 5 мм. версия, которая тяжелее, но лучше справляется с нитью принтера).

Мы также можем видеть взаимодействия обратного вызова Sigfox из раздела Sigfox. Обратите внимание, что данные нисходящего канала отправляются и подтверждаются, однако Arduino сообщает об ошибке. Подробнее об этом в конце.

На вкладке «Местоположение» мы также можем увидеть, где находится наш Really Smart Box, что может быть полезно, если вы забыли, у какого клиента он находится, или если он в фургоне.

И, естественно, нам нужен красивый вид на панели инструментов Really Smart Box, ниже показан вес содержимого коробки, предполагаемые единицы в нем и количество устройств, которые не отчитываются, чтобы мы могли определить, сломано ли одно из них.

Получение уведомлений с помощью Tinamous:

Затем я настроил Tinamous для отправки электронного письма и sms, когда количество элементов мало. Я сделал это, указав рабочий диапазон от 3 до 300 для поля количества элементов. Если значение выходит за пределы этого диапазона, даже измерение вне диапазона увеличивается.

Добавив уведомление в Tinamous, мы можем получить уведомление, когда это произойдет.

Мы могли бы указать только интересующее нас поле, но оставив это поле пустым, мы получим уведомления для любого поля, выходящего за пределы допустимого диапазона.

Точно так же для устройств оставьте поле пустым для всех устройств (т.е. единственного, которое у нас есть в настоящее время!)

Настройте повторные уведомления так, чтобы они срабатывали только один раз, пока он не будет сброшен (каждый день), иначе уведомления каждые 15 минут очень быстро раздражают!

Затем выберите способ получения уведомлений, я установил его для электронной почты и смс, затем создаю уведомление:

Заключение:

Теперь я могу развернуть Really Smart Box и (надеюсь) забыть о нем. Когда уровень запасов станет низким, я получу уведомление, и я смогу проверить на панели инструментов, как там дела. Используя Sigfox, мне не нужно беспокоиться о питании устройства, кроме случайной замены батареи, и не требуется настройка Wi-Fi на месте, что делает развертывание чрезвычайно простым.

Я планирую разместить это устройство в одном из наших ящиков для хранения волокон в Кембридже Makespace, чтобы контролировать уровень запасов волокна.

Проблемы, которые необходимо решить:

Излишне говорить, что это не законченный проект производственного качества, необходимо решить несколько проблем:

Sigfox Downlink:

Sigfox позволяет отправлять 4 сообщения в день в ответ на сообщение аплинка. Really Smart Box использует их для повторного обнуления весов, установки верхнего и нижнего диапазонов температуры и влажности, а также веса предмета. Однако при попытке заставить это работать, даже если сообщение о нисходящем канале, похоже, отправляется и подтверждается (как показано в бэкэнде Sigfox), Arduino сообщает об ошибке статуса 62, которая не сопоставляется ни с одним флагом ошибки. условия, перечисленные для микросхемы ATA8520, копаясь в драйверах, команда использует запрос нисходящего канала, который также не указан в таблице данных, поэтому необходимо провести дополнительное исследование.

Только отладка:

Запуск связи Sigfox с отключенной отладкой приводит к активации настройки низкого энергопотребления Arduino с отключением последовательного порта USB.

Режим низкого энергопотребления:

Как описано для настройки отладки Sigfox, использование библиотеки с низким энергопотреблением Arduino приводит к отключению последовательного порта USB, поэтому в настоящее время эта функция отключена.

Дрейф:

Никакой компенсации дрейфа не добавлено, без сомнения, датчики веса будут дрейфовать при постоянной нагрузке.

Шум / невертикальные измерения:

Возможно, Really Smart Box может быть в задней части фургона (например, мобильный уборщик, плотник, сантехник и т. Д.). Было бы хорошо добавить к платформе акселерометр и пропускать циклы измерения, когда ящик не устойчив. Точно так же, если ящик не вертикальный, вес не пройдет через датчики нагрузки, как ожидалось.

Код

  • Код Arduino Really Smart Box
Код Arduino Really Smart Box Arduino
Добавьте библиотеки для Arduino MKR FOX 1200, HX711, AdaFruit BME280, Arduino Low Power. Используйте Arduino IDE для обычного программирования.
 // Really Smart Box // Измеряет вес содержимого действительно умного ящика // Сделано из двух листов акрила с 2 датчиками веса между ними // помещено в действительно smart box. // Также включает BME280 для измерения температуры и давления внутри коробки .// Автор:Стивен Харрисон // Лицензия:MIT # include  #include  #include  #include  #include  // ---------------------------------- ---- // BME280 на порте I2C. Adfruit_BME280 bme; // -------------------------------------- // Усилитель тензодатчика HX711 // 0 :D0 - DOUT // 1:D1 - CLK // начальное усиление 128. HX711 scale (0, 1, 128); // Массивы для тензодатчиков. Индекс 0 ==Канал A, Индекс 1 ==Канал B. Поплавковое усиление [] ={128,32}; // Калибровочные коэффициенты .// Мы используем y =mx + c (c =offset, m =scaleFactor) ./ / для преобразования измеренного значения в вес .// Установите это на смещение, сообщаемое весоизмерительными датчиками .// без веса на них. float offset [] ={0,54940}; // Установите коэффициент, вычисляемый при размещении веса на весах. // Сначала установите смещение, перезапустите Arduiono, чтобы это вступило в силу, // поместите вес на весы и разделите необработанное измеренное значение на вес .// using scaleFactor =измеренное значение / weight.float scaleFactor [] ={378.f, 260.9f}; // ---------------------- ---------------- // Sigfox // Это структура данных, которую мы публикуем в Sigfox. // Разделите биты как логические флаги из первого байта состояния, но байт все еще нужен to be // included otherwise humidity becomes the status// firstRun::bool:7 hx711Fault::bool:6 bmeFault::bool:5 temperatureAlarm::bool:4 humidityAlarm::bool:3 weightAlarm::bool:2 lowStock::bool:1 b0::bool:0// status::int:8 humidity::int:8 temperature::int:8 zeroWeight::int:16:little-endian weight::int:16:little-endian itemCount::int:16:little-endiantypedef struct __attribute__ ((packed)) sigfox_message { uint8_t status; // status::uint:8 -> Split to 8 bits // B7 - First run, B6 - HX711 fault, B5 BME280 fault, B4 Temperature alarm, B3 - Humidity alarm, B2 - weight alarm, B1 - Low stock, B0 - spare int8_t humidity; // humidity::int:8 (yes some sensors (HTU21D read -ve humidity) int8_t temperature; // temperature::int:8 (no decimal places). int16_t zeroWeight; // zeroWeight::int:16:little-endian int16_t weight; // weight::int:16:little-endian int16_t itemCount; // itemCount::int:16:little-endian (100x actual item count to allow for 2.01 (as weight won't match exactly) int8_t driftCorrection; // Drift Correction for changes in zero weight applied to the scales. int8_t filler; int8_t lastStatus; // Last sigfox status} SigfoxMessage;// Time the last Sigfox message was published atlong lastPublish =0;// Time the last Sigfox downlink was requested.// Allowed max 4 per day of these.long lastDownlink =0;uint8_t lastSigfoxStatus =0;// --------------------------------------// Application/state// If the fist cycle (after a reset) for the measure/publish// cycle (this is used to request a downlink message from Sigfox).// Note that only 4 of them are allowed per day so becareful// when deploying code.bool isFirstCycle =true;// Application mode// 0:Normal// 1:Calibrationint mode =0;// Which channel should be read during calibration.int calibrate_channel =0;// The last average value measured for each channel.float lastAverage[] ={0,0};// The current weight of the contents of the boxfloat currentWeight =0;// The weight of the units the box will hold.// Updatable via Sigfox downlink message.float unitWeight =238;// Different to tare as it would be a manual// zero'd at a set reading from scales// This will most likely change with drift (time/temperature/etc)// and should be set once the scale is in place but not loaded.// Updatable via Sigfox downlink message.float zeroWeight =0;bool bmeOk =true;bool hx711Ok =true;// Alarms and alarm rangesfloat minTemperature =5.f;float maxTemperature =60.f;float minHumidity =0.f;float maxHumidity =60.f;float maxWeight =10000; // 10kgbool temperatureAlarm =false;bool humidityAlarm =false;bool weightAlarm =false;float currentTemperature =0;float currentHumidity =0;float stockLevel =0;bool lowStock =false;float minStock =5;// Setup the Arduino.void setup() { pinMode(LED_BUILTIN, OUTPUT); //Initialize serial:Serial.begin(9600); // NB:The sensor I'm using (from random eBay seller) // does not use the default address. bmeOk =bme.begin(0x76); if (!bmeOk) { Serial.println("Could not find a valid BME280 sensor!"); } // Delay for USB Serial connect and for the BME's first reading. задержка (5000); Serial.println("Really Smart Box..."); printHeader();}int delayCounter =0;void loop() { switch (mode) { case 0:measureAndPublish(); //Sleep for 1 minutes // Causing problems with USB connected. //LowPower.sleep(1 * 60 * 1000); delay(60 * 1000); ломать; case 1:calibrate(); задержка (1000); ломать; } // Check for user input via the serial port. checkSerial(); // measure is done on RTC timer tick (once per minute) delay(100);}void measureAndPublish() { // turn the LED on to indicate measuring. digitalWrite(LED_BUILTIN, HIGH); printBmeValues(); measureTemperatureAndHumidity(); measureWeight(true); // Weight, temperature and humidity are read every minute // however we only publish occasionally. if (shouldPublish()) { publishMeasurements(); } digitalWrite(LED_BUILTIN, LOW); }// Main measurement loop. Reads the weight from the load cells// and stores if no noise from the previous read.void measureWeight(bool printDetails) { scales.power_up(); задержка (500); float delta =readDelta(printDetails); if (printDetails) { Serial.print("\t"); Serial.print(delta, 2); } // If the delta is between -1 and 1 (i.e. no noise) // update the change in overall weight and units contained // otherwise ignore and try again later on. // This ensures we use only stable readings when both channels have not changed for // two sets of measurements. if (delta <1.f &&delta> -1.f) { // Remember the previous measured weight so we can get a delta. float lastWeight =currentWeight; // Compute the weight. Take the weight of both load cells // added together then subtract the zero'd weight. currentWeight =lastAverage[0] + lastAverage[1] - zeroWeight; // Compute the difference in weight of the items in the box // compated to the last time we had a stable reading. float itemsWeightDelta =currentWeight - lastWeight; updateStockLevels(); if (printDetails) { Serial.print("\t"); Serial.print("\t"); Serial.print(currentWeight, 2); Serial.print("\t"); // divide by unit weight to estimate the stock level in the box Serial.print(currentWeight / unitWeight, 2); Serial.print("\t"); // the change in weight, (i.e. the weight if the items added) Serial.print(itemsWeightDelta, 2); Serial.print("\t"); // divide by unit weight to estimate the units removed/added Serial.print(itemsWeightDelta / unitWeight, 2); } } checkWeightLimits(); if (printDetails) { Serial.println(); } // put the ADC in sleep mode and switch // off the LED now we're done measuring. scales.power_down(); }void updateStockLevels() { stockLevel =currentWeight / unitWeight; // Unlike other alarms the low stock level // is reset if the stock is re-stocked. lowStock =stockLevel  maxWeight ) { weightAlarm =true; } if (lastAverage[0]> (maxWeight /2)) { weightAlarm =true; } if (lastAverage[1]> (maxWeight /2)) { weightAlarm =true; }}// Read the difference in weight from the last // average to this time across both load cells.// average value is stored in the lastAverage array.float readDelta(bool printDetails) { float aDelta =readChannel(0, true); if (printDetails) { Serial.print("\t"); } float bDelta =readChannel(1, true); return aDelta + bDelta;}// Read the weight from a channel. Stores the measured value in // the lastAverage array and retuns the delta of the measured value// from the previous lastAverage. This allows us to know if the weight// has changed.// channel 0 =A// channel 1 =Bfloat readChannel(int channel, bool printDetails) { // Gain:// Channel A supports 128 or 64. Default 128 // Channel B supports 32 // Select Channel B by using gain of 32. scales.set_gain(gain[channel]); // HX711 library only has one set of offset/scale factors // which won't work for use as we use both channels and they // have different gains, so each needs to have it's offset/scale set // before reading the value. scales.set_offset(offset[channel]); scales.set_scale(scaleFactor[channel]); // force read to switch to gain. scales.read(); scales.read(); float singleRead =scales.get_units(); float average =scales.get_units(10); float delta =average - lastAverage[channel]; if (printDetails) { Serial.print(singleRead, 1); Serial.print("\t"); Serial.print(average, 1); Serial.print("\t"); Serial.print(delta, 1); Serial.print("\t"); } lastAverage[channel] =average; return delta;}// print the header for the debug data pushed out when measuring.void printHeader() { Serial.print("BME280\t\t\t\t\t"); Serial.print("Channel A\t\t\t"); Serial.print("Channel B\t\t\t"); Serial.print("\t\t"); Serial.print("Totals \t\t\t"); Serial.println(""); Serial.print("Temp\t"); Serial.print("Pressure\t"); Serial.print("Humidity\t"); Serial.print("read\t"); Serial.print("average\t"); Serial.print("delta\t"); Serial.print("\t"); Serial.print("read\t"); Serial.print("average\t"); Serial.print("delta\t"); Serial.print("\t"); Serial.print("sum\t"); Serial.print("\t"); Serial.print("weight\t"); Serial.print("items\t"); Serial.print("change\t"); Serial.print("items added"); Serial.println("");}// Calibration - reads/prints selected channel values.void calibrate() { digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) scales.set_gain(gain[calibrate_channel]); scales.set_offset(offset[calibrate_channel]); scales.set_scale(scaleFactor[calibrate_channel]); // force read to switch to gain Serial.print("\t|CH:\t"); Serial.print(calibrate_channel,1); Serial.print("\traw:\t"); Serial.print(scales.read(),1); Serial.print("\t| raw:\t"); Serial.print(scales.read(),1); Serial.print("\t| units:\t"); Serial.print(scales.get_units(), 1); Serial.print("\t| gain:\t"); Serial.print(gain[calibrate_channel], 1); Serial.print("\t| factor:\t"); Serial.println(scaleFactor[calibrate_channel], 1); digitalWrite(LED_BUILTIN, LOW);}// check the serial port for input from a console to allow us to alter // the device mode etc.void checkSerial() { if(Serial.available()) { char instruction =Serial.read(); switch (instruction) { case '0':calibrate_channel =0; Serial.println("Channel 0 (A) Selected"); ломать; case '1':calibrate_channel =1; Serial.println("Channel 1 (B) Selected"); ломать; case 'm':// Measurement mode mode =0; Serial.println("Measurement Mode"); printHeader(); ломать; case 'c':// Calibration mode mode =1; Serial.println("Calibration Mode"); ломать; case 't':// Tare. Teset the scale to 0 Serial.println("Taring"); scales.power_up(); задержка (500); scales.tare(5); // Need to do this for each channel // and update our stored offset. Serial.println("Not properly Tared!"); ломать; case 'h':printHeader(); ломать; case 'z':zeroScales(); ломать; case 's':printSigfoxModelDetails(); ломать; default:Serial.println("Unknown instruction. Select:0, 1, m, c, t, h, z, or s"); Serial.println("m - measurement mode"); Serial.println("c - Calibration mode"); Serial.println("0 - Channel 0 (A) Calibration"); Serial.println("1 - Channel 1 (B) Calibration"); Serial.println("t - Tare (scale)"); Serial.println("z - Zero (Weight)"); Serial.println("h - print Header"); Serial.println("s - print Sigfox model details"); ломать; } }}// Measure (and record) the temperature and humidity levels// Sets alarms if out of rage (we can't use limits on Internet service side// as the messages may only be sent a few times a day and a brief (maybe hours)// out of range temperature/humidity could easily be missed between// message publishing.void measureTemperatureAndHumidity() { if (!bmeOk) { return; } currentTemperature =bme.readTemperature(); if (currentTemperature  maxTemperature) { temperatureAlarm =true; } currentHumidity =bme.readHumidity(); if (currentHumidity  maxHumidity) { humidityAlarm =true; }}// Print the values read from the BME280 sensorvoid printBmeValues() { //Serial.print("Temperature ="); Serial.print(bme.readTemperature(), 1); Serial.print("\t"); Serial.print(bme.readPressure() / 100.0F, 0); Serial.print("\t\t"); Serial.print(bme.readHumidity(),1); Serial.print("\t\t ");}// =============================================================// Sigfox handing// =============================================================// Determine if we should publish the Sigfox message.// We may also wish to publish if the stock level has// changed (or a significant weight level has changed)// but we would need to be careful of exceeding the // 140 messages per day for a noisy system.bool shouldPublish() { // Publish every 15 minutes // this doesn't really need to be this often // but whilst developing it helps keep an eye on the system. int messageIntervalMinutes =15; // On first run after reset // allow a 2 minute delay for the platform to be placed into // the box and stabalise before doing first publish // which is also expected to include a check for zeroing the platform. if (isFirstCycle) { messageIntervalMinutes =2; Serial.println("First cycle"); } // How long ago we last publish a Sigfox message long millisAgo =millis() - lastPublish; return millisAgo> (messageIntervalMinutes * 60 * 1000);}// Publish our measurements (weight, temperature, humidity etc)// to Sigfox.void publishMeasurements() { Serial.println("Sending via Sigfox..."); bool useDownlink =shouldUseDownlink(); if (useDownlink) { Serial.println("Using Sigfox downlink..."); } // stub for message which will be sent SigfoxMessage msg =buildMessage(); SigFox.begin(); SigFox.debug(); // Wait at least 30mS after first configuration (100mS before) delay(100); // Clears all pending interrupts SigFox.status(); delay(1); SigFox.beginPacket(); SigFox.write((uint8_t*)&msg, 12); // endPacket actually sends the data. uint8_t statusCode =SigFox.endPacket(useDownlink); printSigfoxStatus(statusCode); // Status =0 for a successful send, otherwise indicates // a failure. // Store when we last published a Sigfox message // to allow for timed message sending. if (statusCode ==0) { resetAlarms(); } // Update the last publish/downlink times // even if an error resonse was received to prevent // repeated publishing lastPublish =millis(); isFirstCycle =false; if (useDownlink) { parseDownlinkData(statusCode); lastDownlink =lastPublish; } // Store the status value lastSigfoxStatus =statusCode; SigFox.end();}void printSigfoxStatus(uint8_t statusCode) { Serial.print("Response status code :0x"); Serial.println(statusCode, HEX); if (statusCode !=0) { Serial.print("Sigfox Status:"); Serial1.println(SigFox.status(SIGFOX)); Serial1.println(); Serial.print("Atmel Status:"); Serial1.println(SigFox.status(ATMEL)); Serial1.println(); }}// Create the message to be publish to Sigfox.SigfoxMessage buildMessage() { SigfoxMessage message; message.status =getStatusFlags(); message.humidity =(int8_t )currentHumidity; message.temperature =(int8_t)currentTemperature; message.zeroWeight =(int16_t)zeroWeight; message.weight =(int16_t)currentWeight; message.itemCount =(int16_t)(stockLevel * 100); message.driftCorrection =0; // TODO message.filler =0; message.lastStatus =lastSigfoxStatus; return message;}// Get the status flags for the Sigfox message.byte getStatusFlags() { byte status =0; // B7 - First run, // B6 - HX711 fault // B5 - BME280 fault // B4 - Temperature alarm // B3 - Humidity alarm // B2 - weight alarm // B1 - Low stock // B0 - spare // Upper Nibble (Charging/Battery) // Battery flat if (isFirstCycle) { status |=0x80; // 1000 0000 } // HX711 fault. // we don't have a way to check this yet. if (!hx711Ok) { status |=0x40; // 0100 0000 } // BME280 fault if (!bmeOk) { status |=0x20; // 0010 0000 } // Over/Under temperature alarm if (temperatureAlarm> 0) { status |=0x10; // 0001 0000 } // Over/Under humidity alarm if (humidityAlarm) { status |=0x08; // 0000 1000 } // Over/under? weight alarm if (weightAlarm) { status |=0x04; // 0000 0100 } // if computed stock level low. if (lowStock) { status |=0x02; // 0000 0010 } return status;}// Determine if we are requesting a downlink message.bool shouldUseDownlink() { // When debugging uncomment this so as to not keep requesting // downlink //return false; // On first run we want to request a downlink // message to help with zero'ing and setup. if (isFirstCycle) { return true; } // How long ago we last did a downlink message. long millisAgo =millis() - lastDownlink; // try every 12 hours, this keeps us under the // maximum 4 per day. return millisAgo> (12 * 60 * 60 * 1000);}// Parse downlinked data.void parseDownlinkData(uint8_t statusMessage) { if (statusMessage> 0) { Serial.println("No transmission. Status:" + String(statusMessage)); возвращение; } // Max response size is 8 bytes // set-up a empty buffer to store this. (0x00 ==no action for us.) uint8_t response[] ={0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Expect... // Byte 0:Flags // B7:Zero scales // B6:Set Temperature range (ignore min/max temp if 0) // B5:Set Humidity range (ignore min/max humidity if 0) // B4:Set tolerance? // B3:Set ??? // B2:Update unit weight (ignore update if 0) // B1:// B0:// Byte 1:Min T // Byte 2:Max T // Byte 3:Min Humidity // byte 4:Max Humidity // byte 5:Read tolerence??? (+/- x) // byte 6 &7:Unit weight // Parse the response packet from Sigfox if (SigFox.parsePacket()) { Serial.println("Response from server:"); // Move the response into local buffer. int i =0; while (SigFox.available()) { Serial.print("0x"); int readValue =SigFox.read(); Serial.println(readValue, HEX); response[i] =(uint8_t)readValue; i++; } // byte 0 - flags. // 0b 1000 0000 if (response[0] &0x80 ==0x80) { zeroScales(); } // 0b 0100 0000 if (response[0] &0x40 ==0x40) { updateTemperatureAlarm(response[1], response[2]); } // 0b 0010 0000 if (response[0] &0x20 ==0x20) { updateHumidityAlarm(response[3], response[4]); } // 0b 0000 0100 if (response[0] &0x04 ==0x04) { // Little Endian format. (ff dd -> 0xddff uint16_t weight =response[7] <<8 &response[6]; updateUnitWeight(weight); } } else { Serial.println("No response from server"); } Serial.println();}void printSigfoxModelDetails() { if (!SigFox.begin()) { Serial.println("Shield error or not present!"); return; } // Output the ID and PAC needed to register the // device at the Sigfox backend. String version =SigFox.SigVersion(); String ID =SigFox.ID(); String PAC =SigFox.PAC(); // Display module informations Serial.println("MKRFox1200 Sigfox configuration"); Serial.println("SigFox FW version " + version); Serial.println("ID =" + ID); Serial.println("PAC =" + PAC); Serial.println(""); Serial.print("Module temperature:"); Serial.println(SigFox.internalTemperature()); Serial.println("Register your board on https://backend.sigfox.com/activate with provided ID and PAC"); delay(100); // Send the module to the deepest sleep SigFox.end();}// =============================================================// General helper methods// =============================================================// Reset the alarms after they have been published.void resetAlarms() { temperatureAlarm =false; humidityAlarm =false; weightAlarm =false;}void zeroScales() { zeroWeight =lastAverage[0] + lastAverage[1]; Serial.print("Zero'd:"); Serial.print(zeroWeight, 1); Serial.println();}void updateTemperatureAlarm(int8_t lower, int8_t upper) { Serial.print("Setting temperature alarm. Min:"); Serial.print(lower); Serial.print(", Max:"); Serial.println(upper); minTemperature =lower; maxTemperature =upper;}void updateHumidityAlarm(int8_t lower, int8_t upper) { Serial.print("Setting humidity alarm. Min:"); Serial.print(lower); Serial.print(", Max:"); Serial.println(upper); minHumidity =lower; maxHumidity =upper;}void updateUnitWeight(uint16_t weight) { Serial.print("Setting unit weight:"); Serial.println(weight); unitWeight =weight;}
Really Smart Box Github Repository
https://github.com/Tinamous/ReallySmartBox

Изготовленные на заказ детали и корпуса

Use this to cut the top and bottom acrylic sheets. cuttingguide_e7GNHf980M.svgThis sits between the lower acrylic sheet and load cell to raise it up a little and provide a edge to the platformsPrint 4 of these for each corner of the lower sheet if needed

Схема

Nothing to complex.

Производственный процесс

  1. Коробка сока
  2. Черный ящик
  3. Солнечный элемент
  4. Умный бармен
  5. Умный светофор
  6. Интеллектуальная корзина для мусора
  7. Робот-бокс для компакт-дисков Raspberry Pi
  8. UVC Box - УФ-стерилизатор для самостоятельного изготовления
  9. Smart Plant IoT
  10. Умные жалюзи