Caja realmente inteligente
Componentes y suministros
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 |
Herramientas y máquinas necesarias
| ||||
|
Aplicaciones y servicios en línea
| ||||
|
Acerca de este proyecto
La plataforma Really Smart Box convierte una Really Useful Box (tm) en una caja de almacenamiento inteligente conectada a Internet para el control de existencias. Basado en Sigfox Arduino MKR FOX 1200, detecta el peso de las cosas almacenadas en la caja, junto con la temperatura y la humedad y utiliza la radio Sigfox de baja potencia para transmitir esta información.
Caso de uso:almacenamiento de filamentos de impresora 3D:
Si posee una impresora 3D, lo más probable es que le importe cómo se almacena el filamento, esto es cierto no solo para el filamento de la impresora, sino que muchas otras cosas deben almacenarse dentro de rangos aceptables de temperatura y humedad (por ejemplo, el sellador de pintores puede volverse inutilizable si se expone a temperaturas bajo cero).
Como una de las personas responsables del mantenimiento de las impresoras 3D en mi espacio de fabricación local, necesito asegurarme de que tenemos suficiente stock de filamento y que se mantiene seco.
Con Really Smart Box puedo controlar el peso del filamento y así saber si estamos bajando, junto con el control del nivel de humedad en la caja para saber si es necesario reemplazar el gel de sílice.
Caso de uso:control de existencias de consumibles:
Es posible que un contratista de limpieza desee mantener un stock de ambientador, jabón de manos u otros consumibles en el sitio de un cliente, es probable que el cliente no le permita al contratista acceso WiFi o que encienda un dispositivo como este cuando no esté presente, sin embargo, la empresa contratista debe hacerlo. saber cuándo enviar nuevas existencias, lo que agrega gastos generales al tiempo de limpieza y al papeleo adicional que a nadie le gusta.
La plataforma Really Smart Box simplemente se coloca en una caja de almacenamiento, ya que usa Sigfox, no necesita conectarse a la red del cliente y tiene poca energía, por lo que funciona con un juego de baterías. Como el contenido de la caja cambia con poca frecuencia, el Arduino se puede mantener en un estado de bajo consumo de energía durante la mayor parte del tiempo, lo que ayuda a extender la vida útil de la batería.
A la plataforma se le puede decir el peso del tipo de artículo almacenado en la caja (es decir, los ambientadores) y, por lo tanto, calcular cuántos hay en la caja. Esto luego se puede enviar a la empresa contratista de limpieza para alertarlos cuando necesiten más carga para entregar al sitio del cliente.
Haciendo la plataforma:
La plataforma es de construcción simple, el cuerpo principal está hecho de dos piezas de acrílico cortado con láser (usé 3 mm, más grueso sería mejor para artículos pesados como filamento de impresora) con un par de celdas de carga entre ellos.
Avellané manualmente los orificios de los tornillos de la celda de carga para dar un mejor acabado, ¡todavía tengo que encontrar una cortadora láser que haga avellanados!
El acrílico se puede cortar en cualquier tamaño para que coincida con la caja que desee, sin embargo, observe la posición de las celdas de carga y la longitud del cable en estas, ya que es bastante corto. La lámina acrílica superior es ligeramente más pequeña que la inferior para garantizar que no se enganche en los lados de la caja.
La hoja inferior tiene un corte para permitir que los componentes electrónicos se monten en el acrílico sin espaciadores y que las patas soldadas del dispositivo se asoman. Utilicé un grifo M3 en las secciones de las esquinas del recorte para atornillar directamente la PCB. También se colocaron pies impresos en 3D en las esquinas para asegurar que los tornillos de la celda de carga que no estuvieran alineados con el acrílico no afectaran el equilibrio.
El peso se detecta mediante dos células de carga de 5 kg. Un juego de 4 se usa normalmente en básculas de baño y esto podría ser mejor, sin embargo, no pude encontrar una buena manera de fijarlos al acrílico y proporcionar el espacio requerido para los componentes electrónicos.
Las celdas de carga necesitan algo de relleno en la parte superior e inferior para permitir un poco de flexión y los sensores de galgas extensométricas reales (la broca blanca en la imagen) de la celda de carga son más gruesos que el bloque de montaje. Esto se logra debajo de la celda de carga con las dos placas finales impresas en 3D "Really Smart Box" que tienen un pequeño bloque para levantar la celda de carga, en la parte superior de la celda de carga hay algunos bloques acolchados acrílicos cortados con láser.
Las celdas de carga están conectadas a un amplificador de celda de carga HX711. Tiene dos canales (A y B) que se pueden seleccionar, lo que es perfecto para este uso.
Cada celda de carga está construida con galgas extensométricas en una configuración de puente de Wheatstone, esto crea un par desequilibrado de divisores de potencial, cuando la celda de carga se coloca bajo carga, la resistencia de las galgas extensométricas cambia y, por lo tanto, se crea una diferencia entre los dos divisores de potencial. , esto es amplificado y medido por el HX711 que realiza la conversión de analógico a digital por nosotros.
He usado dos celdas de carga de 5 kg para este proyecto, puede obtener diferentes clasificaciones (por ejemplo, 1 kg y 10 kg) que funcionan exactamente igual pero con diferente sensibilidad.
Al colocar las celdas de carga, asegúrese de que la flecha en el extremo de la celda apunte hacia abajo (en la dirección de la carga). Las celdas tienen orificios roscados M5 en un extremo (generalmente el extremo fijo) y M4 en el otro (el lado en el que coloca la carga.
Los cables rojo / negro son energía, esto alimenta la parte superior e inferior de los divisores de potencial y se comparte entre las dos celdas de carga. El verde y el blanco son los cables de detección del medio de los divisores de potencial, que están conectados a los canales A y B del HX711.
El HX711 admite 3 factores de ganancia, sin embargo, estos también se utilizan para la selección de canales. Las ganancias de 128 y 64 están disponibles en el canal A, mientras que al seleccionar una ganancia de 32 se selecciona el canal B. esto significa que nuestro segundo canal no será tan sensible como el canal principal, esto está bien para esta aplicación.
El HX711 se puede conectar a cualquier pin digital en el Arduino, he usado D0 (Datos) y D1 (Reloj), luego el amplificador solo necesita estar conectado al suministro 3v3 del Arduino.
Puede leer más sobre las celdas de carga y el HX711 en el excelente tutorial de celdas de carga de SparkFuns.
Finalmente, se conecta un BME280 al bus I2C y se usa para detectar la temperatura y la humedad dentro de la caja, esto también se puede usar para detectar la presión, sin embargo, esto probablemente sea de poco interés y solo tenemos 12 bytes de datos de sigfox para jugar, por lo que es no reportado.
La electrónica está montada en una placa ThingySticks Arduino Prototype, agregué un soporte de batería (adhesivo termofusible pegado a la lámina acrílica inferior) y conecté la antena, que tiene un bonito diseño plano, por lo que funcionó perfectamente para la plataforma.
Calibración de la celda de carga:
Antes de que podamos utilizar la plataforma, es necesario calibrar las células de carga. Cada celda de carga es única, se fabrica conectando un medidor de tensión a un bloque de metal y se perforan agujeros en él para proporcionar una flexión suficiente que pueda detectarse sin que se rompa, por lo que necesitamos calibrar cada celda de carga para su respuesta al peso. .
Una vez calibrado, aplicamos la ecuación y =mx + c al valor ADC medido (x) para obtener el peso real (y). Entonces, necesitamos encontrar c (el desplazamiento) y m (la pendiente) para nuestra celda de carga.
Quité la parte superior de la plataforma principal y adjunté un pequeño cuadrado acrílico a cada celda de carga, y monitoreé los valores medidos (hay una ruta en el firmware para hacer esto que se puede iniciar enviando una "c" al puerto serie.
Inicialmente se midió la lectura para una plataforma vacía, esto da el valor de compensación (c), luego colocando una carga de peso conocido en la celda, la diferencia en las lecturas nos da la pendiente.
Pendiente =(medida - compensación) / peso (g).
Usé una pequeña lata de ambientador (aproximadamente 230 g) y un carrete de filamento de impresora (aproximadamente 1,5 kg) para verificar los valores, ambos dieron aproximadamente la misma pendiente, lo cual fue reconfortante.
Naturalmente, el desplazamiento medido con la almohadilla acrílica pequeña es diferente al experimentado con la hoja superior completa, de la misma manera, también hay una pequeña diferencia de pendiente cuando se usan ambas celdas de carga, por lo que se necesita una calibración secundaria. Por ahora, se usa una compensación de un punto cero (tara), esto se establece en el firmware, pero también se puede configurar mediante la conexión en serie USB o mediante un mensaje de enlace descendente Sigfox una vez implementado.
Conexión Sigfox:
Con el cable Really Smart Box conectado, inicialmente usé el puerto serie USB para monitorear la salida para ayudar a depurar y ajustar el sistema. De esta manera puede ver las celdas de carga individuales, los cambios y el ruido. Sin embargo, esto no funcionará para una caja implementada, ya que debe ser totalmente inalámbrica.
Con Sigfox podemos enviar 12 bytes de datos hasta 140 veces al día a nuestro servicio en línea, esto es más que suficiente para Really Smart Box. La siguiente estructura de datos se usa en Arduino describe cómo usamos los 12 bytes.
typedef struct __attribute__ ((empaquetado)) sigfox_message {uint8_t status; // indicadores de estado int8_t humedad; // humedad ::int:8 - algunos sensores (HTU21D) leen -ve humedad) int8_t temperatura; // temperatura ::int:8 (sin decimales). int16_t zeroWeight; // peso cero ::int:16:little-endian int16_t peso; // peso ::int:16:little-endian int16_t itemCount; // itemCount ::int:16:little-endian (recuento real de artículos 100x para permitir 2.01 (ya que el peso no coincidirá exactamente) int8_t driftCorrection; // Corrección de deriva para cambios en el peso cero aplicado a las básculas. int8_t filler; // No hay nada que ver aquí, sigue adelante .... int8_t lastStatus; // Último estado de sigfox} SigfoxMessage;
El primer byte (estado) se divide en indicadores de bits para indicar los problemas:
// status ::uint:8 -> Split to 8 bits // B7 - Primera ejecución // B6 - Fallo HX711 // B5 - Fallo BME280 // B4 - Alarma de temperatura // B3 - Alarma de humedad // B2 - Alarma de peso // B1 - Stock bajo // B0 - Repuesto
Esta estructura se compacta hasta 12 bytes, sin embargo, debemos descomprimirla en el extremo de Sigfox para ingresar a Tinamous. Usamos una configuración de carga útil personalizada para eso y es mejor resolver esto mientras se define la estructura de datos. El nuestro es:
firstRun ::bool:7 hx711Fault ::bool:6 bmeFault ::bool:5 temperatureAlarm ::bool:4 humedadAlarm ::bool:3 weightAlarm ::bool:2 lowStock ::bool:1 b0::bool:0 status ::int:8 humedad ::int:8 temperatura ::int:8 zeroWeight ::int:16:little-endian weight ::int:16:little-endian itemCount ::int:16:little -endian
La carga útil personalizada divide nuestros 12 bytes a medida que se analizan.
Tenga en cuenta que necesitamos especificar la naturaleza little-endian de cualquier cosa mayor a 1 byte ya que Sigfox por defecto es big-endian y Arduino usa little-endian (es decir, el byte menos significativo es el primero en palabras de varios bytes).
También tenga en cuenta que dividir las banderas booleanas en el primer byte no progresa el marcador de bytes como lo hace con todas las demás lecturas, por lo que el byte de estado que contiene todas las banderas también se lee para omitir el primer byte.
Incluidas en las banderas están las banderas de alarma de rango de temperatura, humedad y peso, podríamos usar un servicio en línea (es decir, Tinamous) para monitorear la temperatura, la humedad y el peso fuera del rango, sin embargo, estos pueden ser de corta duración (unas pocas horas) y nuestra caja pueden enviarse con poca frecuencia (una o dos veces al día), la posible condición ambiental dañina resultante podría pasarse por alto fácilmente, por lo que se marcan en el dispositivo y se envían (y se restablecen después de un envío exitoso).
En realidad, el recuento de elementos se establece en 100 veces el recuento real de elementos. Quería permitir valores como 2.2 elementos (debido a un error de peso u otros elementos en la caja) sin forzar un redondeo, del mismo modo 2.95 podría redondearse a 2 si no tenemos cuidado y sería más sugerente de 3 elementos en la caja y un pequeño error. Tampoco quería usar un flotador que requeriría más espacio, así que usé una palabra de 16 bits y apliqué un factor para permitir una conversión fácil (también está firmado para permitir un error cero, lo que podría resultar en un nivel de stock de -1 o -2, etc.).
Se necesita hacer muy poco para habilitar la comunicación Sigfox. Dentro de Arduino, se agrega la biblioteca Sigfox y se llaman las funciones apropiadas para sacar los datos según los ejemplos de Arduino para la biblioteca Sigfox, sin embargo, necesitamos registrar nuestro dispositivo con Sigfox.
El envío de una "s" al puerto serie de Really Smart Box imprime el ID de Sigfox y el código PAC, estos se utilizan para activar el dispositivo en el backend de Sigfox. Luego nos dirigimos al servicio de activación de backend de Sigfox y seguimos el asistente, primero seleccionando nuestro dispositivo, luego el país / proveedor y luego algunos detalles.
Y finalmente nuestro dispositivo está activado y listado:
Sigfox asigna dispositivos a la agrupación de tipos de dispositivos, lo cual es sensato, ya que normalmente puede tener muchos (cientos, miles, etc.) del mismo tipo de dispositivo en los que desea actuar como grupo. Con el tipo de dispositivo definido, podemos configurar una devolución de llamada personalizada para enviar los datos que hemos recibido a nuestro servicio en línea. Estoy usando Tinamous para esto (pista:vea el nombre de mi perfil; podría estar sesgado en mi selección).
Uso de Really Smart Box:
Una vez conectada, atornillada, el firmware flasheado y las baterías instaladas, la plataforma simplemente se coloca en una Caja realmente útil (tm) y está lista para funcionar.
La energía debe aplicarse lo más tarde posible, ya que una vez que el dispositivo se enciende, enviará el primer mensaje de Sigfox después de 2 minutos y solicitará datos de enlace descendente con él. Estos datos pueden incluir un comando "Cero" para poner a cero el peso de la plataforma. De lo contrario, se necesita una conexión en serie USB o se espera la próxima solicitud de enlace descendente; estas se realizan cada 12 horas.
Una vez en funcionamiento, la plataforma publicará mensajes Sigfox cada 15 minutos para enviar el peso, el recuento de artículos, la temperatura, la humedad y los estados de alarma. La temperatura, la humedad y el peso se miden cada minuto para garantizar que no estén fuera de rango y que las alarmas se marquen para la próxima transmisión si se han activado.
Supervisión con Tinamous:
Tinamous es compatible con las devoluciones de llamada personalizadas de Sigfox al agregar un "Sigfox Bot" a nuestra cuenta. Para obtener instrucciones sobre cómo hacerlo, consulte mi tutorial de Hackster.io "Consiga su Sigfox en".
Cuando agregue un Sigfox Bot a su cuenta de Tinamous, si incluye la configuración de la API, el Sigfox Bot buscará sus dispositivos y los agregará a su cuenta de Tinamous; sin embargo, no es necesario que haga esto ya que el dispositivo se agregará automáticamente cuando los datos se publica.
Cuando haya agregado el Bot, se nos presentará una pantalla de configuración de devolución de llamada para ayudar a configurar las devoluciones de llamada de Sigfox.
Luego puede crear una devolución de llamada personalizada en Sigfox, tenga en cuenta que Really Smart Box usa DATA -> devolución de llamada BIDIR que maneja la devolución de llamada UPLINK normal y las devoluciones de llamada BIDIR (enlace ascendente y descendente).
Aquí es donde la carga útil personalizada de antes es útil, péguela desde el código fuente en la carga útil personalizada y actualice la sección de campos para reflejar esto.
Lat y Lng se especifican en esta devolución de llamada que proporciona una ubicación aproximada, sin embargo, Sigfox en Arduino admite una configuración de ubicación mejorada, pero esto necesita una segunda devolución de llamada. Si usa la función de ubicación geográfica, no especifique la latitud / longitud en este mensaje, ya que la caja realmente inteligente parecerá moverse entre ubicaciones.
Una vez que esto está configurado, también debe habilitarse para el enlace descendente, esto está deshabilitado de manera predeterminada a pesar de que se configuró el BIDIR.
Tenga en cuenta que la captura de pantalla debajo de la opción de enlace descendente está "marcada", esto debe hacerse manualmente y es posible que no esté disponible si el tipo de dispositivo no se ha configurado en "CALLBACK" para los datos del enlace descendente (Tipo de dispositivo -> Editar -> Datos de enlace descendente) .
Con una devolución de llamada de enlace descendente también queremos especificar una devolución de llamada de SERVICIO -> RECONOCIMIENTO para saber que nuestro dispositivo ha recibido los datos de enlace descendente. Al hacer clic en Sigfox Bot en Tinamous, se muestran otras configuraciones de devolución de llamada, siga las instrucciones para las devoluciones de llamada ACKNOWLEDGE y GEOLOC.
Tenga en cuenta que debe copiar el encabezado de autorización de la primera devolución de llamada de enlace ascendente / bidir, ya que esta es una contraseña cifrada unidireccionalmente en Tinamous y ya no está disponible para su visualización.
Con nuestras devoluciones de llamada en su lugar, los datos publicados por nuestro dispositivo ahora deberían enviarse a Tinamous. También podríamos agregar devoluciones de llamada por correo electrónico en Sigfox que pueden ayudar a confirmar que los datos están llegando (pero también pueden volverse muy ruidosos muy rápidamente).
Configuración del dispositivo Tinamous:
Una vez que se haya visto el dispositivo Sigfox en Tinamous (ya sea a través de la búsqueda de API o una devolución de llamada), se mostrará en la página de Dispositivos, desde aquí podemos editar las propiedades. Los campos se agregan automáticamente a medida que provienen de la devolución de llamada de Sigfox, por lo que es mejor esperar hasta que el dispositivo publique datos.
Establecí el tiempo de "No informar después de" en 1 hora (actualmente se publica cada 15 minutos) para poder saber si el dispositivo está roto y, opcionalmente, recibir una notificación sobre esto.
No quería ver todos los campos enviados por el dispositivo en la página de gráficos / detalles (hay muchos si incluye todas las banderas), por lo que Tinamous está configurado solo para mostrar el peso y el recuento de artículos. Aquí también se aplicaron etiquetas y unidades amigables para los humanos.
El campo Recuento de elementos es 100 veces el recuento real de elementos, por lo que se aplica una calibración a ese campo para reducirlo 100 veces.
Se establecen algunos datos de enlace descendente que harán que Really Smart Box se ponga a cero y aplique límites de rango de temperatura y humedad la próxima vez que solicite un mensaje de enlace descendente (2 minutos después del encendido, luego una vez cada 12 horas).
Visualización de la información de la caja realmente inteligente:
Ahora que los campos del dispositivo están configurados, podemos monitorearlos a través de la página de detalles del dispositivo. versión que es más pesada pero maneja mejor el filamento de la impresora).
También podemos ver las interacciones de devolución de llamada de Sigfox desde la sección Sigfox. Observe aquí que los datos del enlace descendente se envían y reciben, sin embargo, Arduino informa un error. Más sobre eso al final.
En la pestaña Ubicación también podemos ver dónde está nuestro Really Smart Box, lo que puede ser útil si olvidas en qué cliente está o si está en una camioneta.
Y, naturalmente, queremos una buena vista del tablero de nuestro Really Smart Box, el de abajo muestra el peso del contenido de la caja, las unidades estimadas en él y un recuento de dispositivos que no informan para que podamos saber si alguno está roto.
Recibir notificaciones con Tinamous:
A continuación, configuro Tinamous para enviar un correo electrónico y sms cuando el recuento de artículos es bajo. Hice esto especificando un rango de trabajo de 3 a 300 para el campo de recuento de elementos. Si el valor está fuera de este rango, se aumentará una medición incluso fuera del rango.
Al agregar una notificación a Tinamous, podemos recibir una notificación cuando eso suceda.
Podríamos especificar solo el campo que nos interesa, pero dejarlo vacío nos da notificaciones para cualquier campo que esté fuera de rango.
Lo mismo ocurre con los dispositivos, déjelo en blanco para todos los dispositivos (es decir, ¡el único que tenemos en este momento!)
Configure las notificaciones repetidas para que se activen solo una vez, hasta que se restablezca (todos los días); de lo contrario, las notificaciones cada 15 minutos se vuelven molestas muy rápidamente.
Luego selecciono cómo ser notificado, lo configuro para correo electrónico y sms y luego creo la notificación:
Conclusión:
Ahora puedo implementar Really Smart Box y (con suerte) olvidarme de él. Cuando el nivel de existencias sea bajo, se me notificará y puedo verificar en el tablero para ver cómo está. Al usar Sigfox, no tengo que preocuparme por encender el dispositivo más que un cambio ocasional de batería y no se requiere configuración de WiFi en el sitio, lo que hace que la implementación sea extremadamente simple.
Estoy planeando implementar esta unidad en una de nuestras cajas de almacenamiento de filamentos en Cambridge Makespace para monitorear los niveles de existencias de filamentos.
Problemas para resolver:
No hace falta decir que este no es un proyecto terminado de calidad de producción, algunos problemas aún deben resolverse:
Enlace descendente Sigfox:
Sigfox permite enviar 4 mensajes al día en respuesta a un mensaje de enlace ascendente. Really Smart Box los usa para permitir la puesta a cero de las básculas, estableciendo rangos de temperatura y humedad superior e inferior y el peso del artículo. Sin embargo, mientras intenta que esto funcione, a pesar de que el mensaje de enlace descendente parece que se está enviando y se está respondiendo (como se muestra en el backend de Sigfox), Arduino informa un error de estado de 62, que no se asigna a ninguna marca de error. condiciones enumeradas para el chip ATA8520, al indagar en los controladores, el comando utiliza una solicitud de enlace descendente que tampoco figura en la hoja de datos, por lo que es necesario realizar más investigaciones.
Solo depurar:
Ejecutar las comunicaciones Sigfox con la depuración deshabilitada hace que la configuración de bajo consumo de Arduino se active y mata el puerto serie USB.
Modo de bajo consumo:
Como se describe para la configuración de depuración de Sigfox, el uso de la biblioteca de bajo consumo de Arduino hace que la serie USB se apague, por lo que esto no está habilitado en este momento.
Deriva:
No se ha agregado compensación por la desviación, sin duda las celdas de carga se desviarán cuando se mantengan bajo carga constante.
Ruido / mediciones no verticales:
Es posible que Really Smart Box esté en la parte trasera de una camioneta (por ejemplo, limpiador de móviles, carpintero, plomero, etc.). Sería bueno agregar un acelerómetro a la plataforma y omitir los ciclos de medición cuando la caja no está estable, de la misma manera si la caja no está vertical, el peso no pasará por las celdas de carga como se esperaba.
Código
- Código Arduino de caja realmente inteligente
Código Arduino de caja realmente inteligente Arduino
Agregue bibliotecas para Arduino MKR FOX 1200, HX711, AdaFruit BME280, Arduino Low Power. Utilice el IDE de Arduino para programar de forma normal.// Really Smart Box // Mide el peso del contenido de una caja realmente inteligente // Realizada por dos láminas de acrílico con 2 celdas de carga entre ellas // colocadas en un lugar realmente caja inteligente.// También incluye un BME280 para medir la temperatura y la presión dentro de la caja.// Autor:Stephen Harrison // Licencia:MIT # incluye#include #include #include #include // ---------------------------------- ---- // BME280 en el puerto I2C. Adafruit_BME280 bme; // -------------------------------------- // Amplificador de celda de carga HX711.// 0 :D0 - DOUT // 1:D1 - CLK // ganancia inicial de 128 escalas HX711 (0, 1, 128); // Matrices para celdas de carga. Índice 0 ==Canal A, Índice 1 ==Canal B. Ganancia de flotación [] ={128,32}; // Factores de calibración.// usamos y =mx + c (c =offset, m =scaleFactor) ./ / para convertir el valor medido en un peso.// Ajústelo a la compensación informada por las celdas de carga.// sin peso en ellas.float offset [] ={0,54940}; // Establezca esto en el factor calculado cuando se coloca un peso en la balanza. // Establezca el desplazamiento primero, vuelva a parpadear el arduiono para que esto surta efecto // coloque un peso en la balanza y divida el valor bruto medido por el weight.// usando scaleFactor =valor medido / weight.float scaleFactor [] ={378.f, 260.9f}; // ---------------------- ---------------- // Sigfox // Esta es la estructura de datos que publicamos en Sigfox.// Divida los bits como banderas bool del primer byte de estado pero el byte aún necesita 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. retraso (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); descanso; case 1:calibrate(); retraso (1000); descanso; } // 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(); retraso (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"); descanso; case '1':calibrate_channel =1; Serial.println("Channel 1 (B) Selected"); descanso; case 'm':// Measurement mode mode =0; Serial.println("Measurement Mode"); printHeader(); descanso; case 'c':// Calibration mode mode =1; Serial.println("Calibration Mode"); descanso; case 't':// Tare. Teset the scale to 0 Serial.println("Taring"); scales.power_up(); retraso (500); scales.tare(5); // Need to do this for each channel // and update our stored offset. Serial.println("Not properly Tared!"); descanso; case 'h':printHeader(); descanso; case 'z':zeroScales(); descanso; case 's':printSigfoxModelDetails(); descanso; 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"); descanso; } }}// 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)); regreso; } // 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/ReallySmartBoxPiezas y carcasas personalizadas
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 neededEsquemas
Nothing to complex.Proceso de manufactura