Really Smart Box: IoT-ingeschakelde slimme opslagoplossing
Onderdelen en benodigdheden
| Onderdeel | Aantal |
|---|---|
| Acrylaatplaat (bijv. 3mm perspex) | 1 |
| Arduino MKR FOX 1200 | 1 |
| SparkFun HX711 Belastingcel-versterker | 1 |
| 5kg belastingcel | 2 |
| Adafruit BME280 | 1 |
Benodigde tools en machines
| Tool |
|---|
| Lasersnijder |
| 3D-printer |
Apps en online diensten
| App/Dienst |
|---|
| Tinamous |
| Sigfox |
Over dit project
Het Really Smart Box-platform verandert een Really Useful Box (tm) in een intelligente, internetverbonden opslagdoos voor voorraadmonitoring. Gebaseerd op de Sigfox Arduino MKR FOX 1200 meet het het gewicht van de inhoud, samen met temperatuur en vochtigheid, en stuurt deze informatie via de lage-energie Sigfox-radio.
Use Case – 3D-printer filament opslag
Als je een 3D-printer hebt, weet je dat filament correct opgeslagen moet worden. Dit geldt ook voor veel andere materialen die binnen acceptabele temperatuur- en vochtigheidsgrenzen bewaard moeten worden (bijv. kit die onbruikbaar wordt bij vriestemperaturen).
Als een van de verantwoordelijken voor de 3D-printers in mijn lokale makerspace, moet ik ervoor zorgen dat we voldoende filament hebben en dat het droog blijft.
Met de Really Smart Box kan ik het gewicht van het filament monitoren (om te weten of de voorraad laag wordt) en de vochtigheid in de doos controleren (om te zien of het silicagel vervangen moet worden).
Use Case – Verbruiksartikelen voorraadbeheer
Een schoonmaakbedrijf wil misschien een voorraad luchtverfrissers, zeep of andere verbruiksartikelen bij een klant bewaren. De klant zal waarschijnlijk geen WiFi-toegang geven of een apparaat laten draaien wanneer het bedrijf niet aanwezig is. Toch moet het bedrijf weten wanneer nieuwe voorraad geleverd moet worden, wat overhead en papierwerk oplevert.
Het Really Smart Box-platform valt gewoon in een opslagdoos. Omdat het Sigfox gebruikt, hoeft het niet verbonden te zijn met het netwerk van de klant en draait het op batterijen. Omdat de inhoud zelden verandert, kan de Arduino de meeste tijd in een lage-energiestand staan, wat de batterijduur verlengt.
Het platform kan verteld worden wat het gewicht is van het opgeslagen artikeltype (bijv. luchtverfrissers) en daaruit berekenen hoeveel er in de doos zitten. Dit kan dan naar het schoonmaakbedrijf gestuurd worden om ze te waarschuwen wanneer er nieuwe voorraad nodig is.
Het platform maken
De constructie is simpel: de hoofdbestaat uit twee gelaserde acrylaatplaten (ik gebruikte 3mm, dikker is beter voor zware items zoals filament) met twee belastingcellen ertussen.
Ik heb de schroefgaten voor de belastingcellen handmatig ingetrokken voor een nette afwerking. Nog geen lasersnijder gevonden die kontersinken kan!
Het acrylaat kan op maat gesneden worden voor elke doos. Let op de positionering van de belastingcellen en de kabellengte (die vrij kort is). De bovenste plaat is iets kleiner dan de onderste om te voorkomen dat het aan de zijkanten hangt.
De onderste plaat heeft een uitsparing voor de elektronica, zodat de gesoldeerde poten door de plaat kunnen steken. Ik gebruikte een M3-tap in de hoeksecties van de uitsparing om de PCB direct te bevestigen. 3D-geprinte voertjes op de hoeken zorgen ervoor dat eventuele schroeven van de belastingcel die niet vlak zitten, de balans niet beïnvloeden.
Het gewicht wordt gemeten met twee 5kg belastingcellen. Een set van 4 wordt normaal in persoonlijke weegschalen gebruikt en had beter kunnen zijn, maar ik kon geen nette manier vinden om ze aan acrylaat te bevestigen én de benodigde ruimte voor de elektronica te bieden.
De belastingcellen hebben boven en onder padding nodig voor een beetje buiging. De daadwerkelijke rekstrekkensoren (het witte deel) zijn dikker dan het montageblok. Dit wordt opgelost onder de belastingcel met de 3D-geprinte eindplaten "Really Smart Box" die een kleine blok hebben om de cel op te tillen, en bovenaan gelaserde acrylaat-paddingblokken.
De belastingcellen zijn aangesloten op een HX711-versterker. Deze heeft twee kanalen (A en B) die geselecteerd kunnen worden – perfect voor dit gebruik.
Elke belastingcel bestaat uit rekstrekkensoren in een Wheatstone-brugconfiguratie. Dit creëert een ongebalanceerd paar potentiële delers. Bij belasting verandert de weerstand van de rekstrekkensoren, wat een verschil tussen de delers creëert. Dit wordt versterkt en gemeten door de HX711, die de analoge-naar-digitale conversie voor ons doet.
Ik gebruikte twee 5kg cellen. Er zijn verschillende belastingklassen (bijv. 1kg, 10kg) die hetzelfde werken maar met andere gevoeligheid.
Bij het plaatsen van de cellen: zorg dat de pijl op het einde van de cel naar beneden wijst (in de richting van de belasting). De cellen hebben M5-gaten aan één kant (vaast vast) en M4 aan de andere (belastingkant).
Rood/zwart draden zijn voeding (gedeeld tussen beide cellen). Groen en wit zijn de meetdraden van het midden van de delers, aangesloten op kanaal A en B van de HX711.
De HX711 ondersteunt 3 versterkingsfactoren, die ook voor kanaalselectie gebruikt worden. Versterking 128 en 64 zijn voor kanaal A, versterking 32 selecteert kanaal B. Dit betekent dat ons tweede kanaal minder gevoelig is, wat voor deze toepassing prima is.
De HX711 kan op elke digitale pin van de Arduino aangesloten worden. Ik gebruikte D0 (Data) en D1 (Clock), en de versterker op de 3v3-voeding van de Arduino.
Meer over belastingcellen en de HX711 vind je in de uitstekende tutorial van SparkFun.
Tot slot is een BME280 aangesloten op de I2C-bus voor temperatuur- en vochtigheidsmeting in de doos. Druk kan ook gemeten worden, maar is waarschijnlijk minder relevant en we hebben maar 12 bytes Sigfox-data, dus wordt niet verstuurd.
De elektronica zit op een ThingySticks Arduino Prototype-board. Ik voegde een batterijhouder toe (warmtegelijmd aan de onderste acrylaatplaat) en de antenne (vlak ontwerp, werkt perfect).
Belastingcel kalibratie
Voordat we het platform gebruiken, moeten de belastingcellen gekalibreerd worden. Elke cel is uniek (rekstrekkensoren op metalen blok met gaten voor buiging), dus we moeten elke cel kalibreren voor zijn respons op gewicht.
Eenmaal gekalibreerd passen we de vergelijking y = mx + c toe op de gemeten ADC-waarde (x) om het daadwerkelijke gewicht (y) te krijgen. We moeten c (offset) en m (helling) bepalen.
Ik haalde de bovenste plaat af en bevestigde een klein acrylaatvierkantje aan elke belastingcel om de bewaarde waarden te monitoren (er zit een routine in de firmware die gestart kan worden door een 'c' naar de seriële poort te sturen).
Eerst de meting van een leeg platform (geeft offset c). Dan een bekend gewicht op de cel leggen – het verschil in metingen geeft de helling.
Helling = (gemeten - offset) / gewicht (g).
Ik gebruikte een kleine luchtverfrisser (ca. 230g) en een filamentrol (ca. 1.5kg) om de waarden te controleren – beide gaven ongeveer dezelfde helling, geruststellend.
Naturellijk is de offset met het kleine acrylaatvierkantje anders dan met de volledige bovenplaat, en is er ook een kleine hellingsverschil bij gebruik van beide cellen. Een secundaire kalibratie is nodig. Voor nu wordt een eenpunt-nuloffset (tare) gebruikt, ingesteld in de firmware maar ook via USB-serieel of Sigfox-downlink na uitrol.
Sigfox-verbinding
Ik gebruikte eerst de USB-seriële poort om de output te monitoren voor debugging. Maar voor een uitgezette doos moet het volledig draadloos zijn.
Met Sigfox kunnen we 12 bytes data sturen, maximaal 140 keer per dag – meer dan genoeg. Hieronder de datastructuur in de Arduino:
typedef struct __attribute__ ((packed)) sigfox_message {
uint8_t status; // status flags
int8_t humidity; // vochtigheid::int:8
int8_t temperature; // temperatuur::int:8
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 werkelijke count)
int8_t driftCorrection;// Drift correctie
int8_t filler; // reserve
int8_t lastStatus; // Laatste sigfox status
} SigfoxMessage;
De eerste byte (status) is opgesplitst in bit-vlaggen:
// B7 - Eerste run
// B6 - HX711 fout
// B5 - BME280 fout
// B4 - Temperatuur alarm
// B3 - Vochtigheid alarm
// B2 - Gewicht alarm
// B1 - Lage voorraad
// B0 - reserve
Deze structuur kompakteert tot 12 bytes. Aan de Sigfox-kant moeten we uitpakken voor Tinamous. We gebruiken een Custom Payload Configuration:
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-endian
Let op: little-endian specificeren voor multi-byte waarden (Sigfox default is big-endian, Arduino is little-endian). De boolean-vlaggen in de eerste byte verplaatsen de byte-marker niet, daarom lezen we de status-byte ook nog eens om over de eerste byte heen te komen.
In de zitten temperatuur-, vochtigheids- en gewichtsalarmvlaggen. Omdat berichten zelden verstuurd worden (1-2x per dag), kunnen kortdurende afwijkingen gemist worden. Daarom worden ze op het apparaat gevlagd en verstuurd (en gereset na succesvolle verzending).
De item-count is 100x de werkelijke count. Dit toestaat waarden als 2.2 items (vanwege weegfout of andere items) zonder afronding. 2.95 zou anders naar 2 afgerond kunnen worden terwijl het 3 items suggereert. Ik wilde geen float gebruiken (meer ruimte), dus een 16-bit woord met factor.
Sigfox-communicatie inschakelen is simpel: Sigfox-bibliotheek toevoegen en de juiste functies aanroepen per Arduino-voorbeelden. Wel moet het apparaat geregistreerd worden bij Sigfox.
Stuur een 's' naar de seriële poort om Sigfox ID en PAC-code te printen. Deze gebruiken we voor activatie op de Sigfox-backend.
Apparaat geactiveerd en gelist:
Sigfox wijst apparaten toe aan Device Type groeperingen. Met de Device Type gedefinieerd, kunnen we een custom callback configureren om data naar onze onlinedienst te sturen. Ik gebruik Tinamous.
De Really Smart Box gebruiken
Eenmaal bekabeld, gemonteerd, firmware geflasht en batterijen geplaatst: platform in een Really Useful Box (tm) leggen en klaar.
Stroom zo laat mogelijk aanzetten: na 2 minuten verstuurt het de eerste Sigfox-bericht en vraagt downlink-data aan (kan een "Zero"-commando bevatten om het gewicht te nullijnen. Anders USB-serieel of wachten op volgende downlink-aanvraag (elke 12 uur).
Eenmaal draaiend publiceert het platform elke 15 minuten een Sigfox-bericht met gewicht, item-count, temperatuur, vochtigheid en alarmstatussen. Temperatuur, vochtigheid en gewicht worden elke minuut gemeten voor alarmdetectie.
Monitoren met Tinamous
Tinamous ondersteunt Sigfox custom callbacks door een "Sigfox Bot" toe te voegen. Zie mijn "Get Your Sigfox On" Hackster.io tutorial voor instructies.
Bij het toevoegen van een Sigfox Bot, als je de API-instellingen meeneemt, zoekt de bot je apparaten op en voegt ze toe. Je hoeft dit niet te doen – het apparaat wordt automatisch toegevoegd bij eerste data.
Je krijgt een Callback-configuratiescherm om de Sigfox Callbacks in te stellen.
Maak een custom callback bij Sigfox. De Really Smart Box gebruikt DATA → BIDIR callback (normale UPLINK + BIDIR).
Plak de custom payload uit de broncode in het veld "Custom Payload" en werk de veldensectie bij.
Lat en Lng in deze callback geven een benaderde locatie. Sigfox op Arduino ondersteunt verbeterde locatie, maar vereist een tweede callback. Als je Geo-locatie gebruikt, specificeer Lat/Lng niet in dit bericht, anders lijkt de box te verhuizen.
De callback moet ook voor Downlink ingeschakeld worden (standaard uit, zelfs bij BIDIR).
Voor Downlink callback ook een SERVICE → ACKNOWLEDGE callback specificeren om te weten of het apparaat de downlink ontving. Klik op de Sigfox Bot in Tinamous voor meer callback-configuraties (ACKNOWLEDGE en GEOLOC). Kopieer de autorisatie-header uit de eerste uplink/bidir callback (dit is een wachtwoord dat eenrichtings-geëncrypteerd is in Tinamous).
Met callbacks op hun plaats zou data nu naar Tinamous moeten stromen. Je kunt ook e-mail-callbacks bij Sigfox toevoegen voor bevestiging (maar kan snel vervelend worden).
Tinamous-apparaat configureren
Zodra het Sigfox-apparaat bij Tinamous binnenkomt (via API-lookup of callback), verschijnt het op de Apparaten-pagina. Velden worden automatisch toegevoegd zoals ze uit de callback komen – wacht tot data gepubliceerd wordt.
Ik stelde "Not Reporting After" in op 1 uur (publiceert nu elke 15 minuten) om te weten of het apparaat kapot is.
Ik wilde niet alle velden (veel vlaggen) op het grafiek/detailpagina zien, dus Tinamous toont alleen gewicht en item-count. Mensvriendelijke labels en eenheden toegepast.
Item Count is 100x de werkelijke count, dus kalibratie toegepast om het 100x te delen.
Downlink-data ingesteld die de Really Smart Box zal nullijnen en temperatuur/vochtigheidsgrenzen instellen bij volgende downlink-aanvraag (2 min na inschakelen, dan elke 12 uur).
Really Smart Box informatie bekijken
Nu de velden geconfigureerd zijn, kunnen we ze monitoren via de apparaat-detailpagina (ik had het platform nog niet genulld, dus denkt hij dat 0.5 eenheden aanwezig zijn – ik verving ook de acrylaat-top door een 5mm versie die zwaarder is maar filament beter aan kan).
Sigfox callback-interacties zijn zichtbaar in de Sigfox-sectie. Downlink-data wordt verstuurd en bevestigd, maar de Arduino rapporteert een fout. Meer daarover aan het einde.
Op het Locatie-tabblad zien we waar de box is – handig als je vergeet bij welke klant het staat of als het in een bus zit.
En natuurlijk een mooi dashboard: inhoudsgewicht, geschatte eenheden, en telling van apparaten die niet rapporteren.
Notificaties ontvangen met Tinamous
Ik stelde Tinamous in om e-mail en SMS te sturen bij lage voorraad. Werkbereik van 3–300 voor item-count. Buiten dit bereik = measurement out of range event.
Notificatie toevoegen in Tinamous voor wanneer dit gebeurt.
Velden leeg laten = notificaties voor elk veld buiten bereik. Apparaten leeg laten = alle apparaten (nu maar 1).
Repeat Notifications op "trigger once only, until reset (daily)" zetten, anders elke 15 minuten een notificatie!
E-mail en SMS selecteren, notificatie aanmaken:
Conclusie
Ik kan de Really Smart Box nu uitrollen en (hopelijk) vergeten. Bij lage voorraad krijg ik een notificatie en kan ik op het dashboard checken hoe het gaat. Met Sigfox geen zorgen over voeding (behalve occasionele batterijwissel) en geen WiFi-setup op locatie – implementatie is extreem simpel.
Ik plan om deze unit in een van onze filament-opslagdozen in Cambridge Makespace te plaatsen voor voorraadmonitoring.
Te verhelpen problemen
Dit is geen productie-klaar project, nog een paar issues:
Sigfox Downlink: Sigfox staat 4 downlink-berichten per dag toe. De Really Smart Box gebruikt deze voor hernullijnen, temperatuur/vochtigheidsgrenzen en item-gewicht. Maar de Arduino rapporteert statusfout 62, wat niet in de ATA8520-datasheet staat. De commando gebruikt een downlink-verzoek dat ook niet in de datasheet staat – meer onderzoek nodig.
Alleen debug: Zonder debug activeert de lage-energiestand van de Arduino, wat de USB-seriële poort doodt.
Lage-energiestand: Zie boven – USB-serieel valt uit, dus nog niet ingeschakeld.
Drift: Geen compensatie voor drift toegevoegd. Belastingcellen zullen waarschijnlijk driften bij constant gewicht.
Ruimte / niet-verticale metingen: De box kan in een bus liggen (mobiele schoonmaker, timmeraar, loodgieter). Een accelerometer toevoegen om metingen over te slaan wanneer de box niet stilstaat, en verticale oriëntatie controleren.
Code
Really Smart Box Arduino-code Arduino
Voeg bibliotheken toe voor Arduino MKR FOX 1200, HX711, AdaFruit BME280, Arduino Low Power. Programmeer via Arduino IDE.
// Really Smart Box
// Meet het gewicht van de inhoud van een really smart box
// Gemaakt van twee acrylaatplaten met 2 belastingcellen ertussen
// In een really useful box geplaatst.
// Ook een BME280 voor temperatuur en druk in de box.
// Auteur: Stephen Harrison
// Licentie: MIT
#include <Adafruit_BME280.h>
#include <HX711.h>
#include <SigFox.h>
#include <SigFox.h>
#include <ArduinoLowPower.h>
// BME280 op I2C
Adafruit_BME280 bme;
// HX711 belastingcel versterker
// 0: D0 - DOUT
// 1: D1 - CLK
// initiële versterking 128
HX711 scales(0, 1, 128);
// Arrays voor belastingcellen. Index 0 = Kanaal A, Index 1 = Kanaal B
float gain[] = {128, 32};
// Kalibratiefactoren
// y = mx + c (c = offset, m = scaleFactor)
// om gemeten waarde om te zetten in gewicht
float offset[] = {0, 54940};
float scaleFactor[] = {378.f, 260.9f};
// Sigfox datastructuur
typedef struct __attribute__ ((packed)) sigfox_message {
uint8_t status;
int8_t humidity;
int8_t temperature;
int16_t zeroWeight;
int16_t weight;
int16_t itemCount;
int8_t driftCorrection;
int8_t filler;
int8_t lastStatus;
} SigfoxMessage;
long lastPublish = 0;
long lastDownlink = 0;
uint8_t lastSigfoxStatus = 0;
bool isFirstCycle = true;
int mode = 0;
int calibrate_channel = 0;
float lastAverage[] = {0, 0};
float currentWeight = 0;
float unitWeight = 238;
float zeroWeight = 0;
bool bmeOk = true;
bool hx711Ok = true;
float minTemperature = 5.f;
float maxTemperature = 60.f;
float minHumidity = 0.f;
float maxHumidity = 60.f;
float maxWeight = 10000;
bool temperatureAlarm = false;
bool humidityAlarm = false;
bool weightAlarm = false;
float currentTemperature = 0;
float currentHumidity = 0;
float stockLevel = 0;
bool lowStock = false;
float minStock = 5;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
bmeOk = bme.begin(0x76);
if (!bmeOk) {
Serial.println("Geen geldige BME280 sensor gevonden!");
}
delay(5000);
Serial.println("Really Smart Box...");
printHeader();
}
int delayCounter = 0;
void loop() {
switch (mode) {
case 0: measureAndPublish(); delay(60 * 1000); break;
case 1: calibrate(); delay(1000); break;
}
checkSerial();
delay(100);
}
void measureAndPublish() {
digitalWrite(LED_BUILTIN, HIGH);
printBmeValues();
measureTemperatureAndHumidity();
measureWeight(true);
if (shouldPublish()) publishMeasurements();
digitalWrite(LED_BUILTIN, LOW);
}
void measureWeight(bool printDetails) {
scales.power_up();
delay(500);
float delta = readDelta(printDetails);
if (printDetails) { Serial.print("\t"); Serial.print(delta, 2); }
if (delta < 1.f && delta > -1.f) {
float lastWeight = currentWeight;
currentWeight = lastAverage[0] + lastAverage[1] - zeroWeight;
float itemsWeightDelta = currentWeight - lastWeight;
updateStockLevels();
if (printDetails) {
Serial.print("\t\t"); Serial.print(currentWeight, 2);
Serial.print("\t"); Serial.print(currentWeight / unitWeight, 2);
Serial.print("\t"); Serial.print(itemsWeightDelta, 2);
Serial.print("\t"); Serial.print(itemsWeightDelta / unitWeight, 2);
}
}
checkWeightLimits();
if (printDetails) Serial.println();
scales.power_down();
}
void updateStockLevels() {
stockLevel = currentWeight / unitWeight;
lowStock = stockLevel < minStock;
}
void checkWeightLimits() {
if (currentWeight > maxWeight) weightAlarm = true;
if (lastAverage[0] > (maxWeight / 2)) weightAlarm = true;
if (lastAverage[1] > (maxWeight / 2)) weightAlarm = true;
}
float readDelta(bool printDetails) {
float aDelta = readChannel(0, true);
if (printDetails) Serial.print("\t");
float bDelta = readChannel(1, true);
return aDelta + bDelta;
}
float readChannel(int channel, bool printDetails) {
scales.set_gain(gain[channel]);
scales.set_offset(offset[channel]);
scales.set_scale(scaleFactor[channel]);
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;
}
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("");
}
void calibrate() {
digitalWrite(LED_BUILTIN, HIGH);
scales.set_gain(gain[calibrate_channel]);
scales.set_offset(offset[calibrate_channel]);
scales.set_scale(scaleFactor[calibrate_channel]);
scales.read();
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);
}
void checkSerial() {
if (Serial.available()) {
char instruction = Serial.read();
switch (instruction) {
case '0': calibrate_channel = 0; Serial.println("Kanaal 0 (A) Geselecteerd"); break;
case '1': calibrate_channel = 1; Serial.println("Kanaal 1 (B) Geselecteerd"); break;
case 'm': mode = 0; Serial.println("Meetmodus"); printHeader(); break;
case 'c': mode = 1; Serial.println("Kalibratie modus"); break;
case 't': Serial.println("Taren"); scales.power_up(); delay(500); scales.tare(5); Serial.println("Niet correct getaard!"); break;
case 'h': printHeader(); break;
case 'z': zeroScales(); break;
case 's': printSigfoxModelDetails(); break;
default: Serial.println("Onbekende instructie. Kies: 0, 1, m, c, t, h, z, of s");
}
}
}
void measureTemperatureAndHumidity() {
if (!bmeOk) return;
currentTemperature = bme.readTemperature();
if (currentTemperature < minTemperature) temperatureAlarm = true;
if (currentTemperature > maxTemperature) temperatureAlarm = true;
currentHumidity = bme.readHumidity();
if (currentHumidity < minHumidity) humidityAlarm = true;
if (currentHumidity > maxHumidity) humidityAlarm = true;
}
void printBmeValues() {
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");
}
bool shouldPublish() {
int messageIntervalMinutes = 15;
if (isFirstCycle) {
messageIntervalMinutes = 2;
Serial.println("Eerste cyclus");
}
long millisAgo = millis() - lastPublish;
return millisAgo > (messageIntervalMinutes * 60 * 1000);
}
void publishMeasurements() {
Serial.println("Versturen via Sigfox...");
bool useDownlink = shouldUseDownlink();
if (useDownlink) Serial.println("Sigfox downlink gebruiken...");
SigfoxMessage msg = buildMessage();
SigFox.begin();
SigFox.debug();
delay(100);
SigFox.status(); delay(1);
SigFox.beginPacket();
SigFox.write((uint8_t*)&msg, 12);
uint8_t statusCode = SigFox.endPacket(useDownlink);
printSigfoxStatus(statusCode);
if (statusCode == 0) resetAlarms();
lastPublish = millis();
isFirstCycle = false;
if (useDownlink) { parseDownlinkData(statusCode); lastDownlink = lastPublish; }
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();
}
}
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;
message.filler = 0;
message.lastStatus = lastSigfoxStatus;
return message;
}
byte getStatusFlags() {
byte status = 0;
if (isFirstCycle) status |= 0x80;
if (!hx711Ok) status |= 0x40;
if (!bmeOk) status |= 0x20;
if (temperatureAlarm) status |= 0x10;
if (humidityAlarm) status |= 0x08;
if (weightAlarm) status |= 0x04;
if (lowStock) status |= 0x02;
return status;
}
bool shouldUseDownlink() {
if (isFirstCycle) return true;
long millisAgo = millis() - lastDownlink;
return millisAgo > (12 * 60 * 60 * 1000);
}
void parseDownlinkData(uint8_t statusMessage) {
if (statusMessage > 0) {
Serial.println("Geen transmissie. Status: " + String(statusMessage));
return;
}
uint8_t response[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
if (SigFox.parsePacket()) {
Serial.println("Response van server:");
int i = 0;
while (SigFox.available()) {
Serial.print("0x"); int readValue = SigFox.read(); Serial.println(readValue, HEX);
response[i] = (uint8_t)readValue; i++;
}
if (response[0] & 0x80) zeroScales();
if (response[0] & 0x40) updateTemperatureAlarm(response[1], response[2]);
if (response[0] & 0x20) updateHumidityAlarm(response[3], response[4]);
if (response[0] & 0x04) {
uint16_t weight = response[7] << 8 | response[6];
updateUnitWeight(weight);
}
} else {
Serial.println("Geen response van server");
}
Serial.println();
}
void printSigfoxModelDetails() {
if (!SigFox.begin()) { Serial.println("Shield fout of niet aanwezig!"); return; }
String version = SigFox.SigVersion();
String ID = SigFox.ID();
String PAC = SigFox.PAC();
Serial.println("MKRFox1200 Sigfox configuratie");
Serial.println("SigFox FW versie " + version);
Serial.println("ID = " + ID);
Serial.println("PAC = " + PAC);
Serial.println("");
Serial.print("Module temperatuur: "); Serial.println(SigFox.internalTemperature());
Serial.println("Registreer je board op https://backend.sigfox.com/activate met ID en PAC");
delay(100);
SigFox.end();
}
void resetAlarms() {
temperatureAlarm = false; humidityAlarm = false; weightAlarm = false;
}
void zeroScales() {
zeroWeight = lastAverage[0] + lastAverage[1];
Serial.print("Genuld: "); Serial.print(zeroWeight, 1); Serial.println();
}
void updateTemperatureAlarm(int8_t lower, int8_t upper) {
Serial.print("Temperatuur alarm instellen. Min: "); Serial.print(lower); Serial.print(", Max: "); Serial.println(upper);
minTemperature = lower; maxTemperature = upper;
}
void updateHumidityAlarm(int8_t lower, int8_t upper) {
Serial.print("Vochtigheid alarm instellen. Min: "); Serial.print(lower); Serial.print(", Max: "); Serial.println(upper);
minHumidity = lower; maxHumidity = upper;
}
void updateUnitWeight(uint16_t weight) {
Serial.print("Eenheidsgewicht instellen: "); Serial.println(weight);
unitWeight = weight;
}
Really Smart Box GitHub Repository
https://github.com/Tinamous/ReallySmartBox
Aangepaste onderdelen en behuizingen
Gebruik dit om de boven- en onderste acrylaatplaten te snijden.
cuttingguide_e7GNHf980M.svg
Dit zit tussen de onderste acrylaatplaat en belastingcel om het iets op te tillen en een rand te geven aan het platform. Print 4 van deze voor elke hoek van de onderste plaat indien nodig.
Schema's
Niets te complex.
Productieproces
- De Sapdoos Revolutie: Geschiedenis, Ontwerp en Milieueffect
- Black Box Vluchtrecorders: Van Vroege Prototypen tot Toekomstige Geïntegrerde Ontwerpen
- Zonnecellen: van vroege experimenten tot moderne fotovoltaïsche technieken – Technologie, productie en toekomstperspectieven
- Slimme Barman: Bouw Je Eigen Raspberry Pi-Cocktailstation
- Slimme Verkeerslicht: Adaptieve Straatverlichting Aangedreven door IoT
- Intelligente Slimme Vuilnisbak: Afvalophaal Optimaliseren met IoT-sensoren
- DIY Wall-E Geïnspireerde Raspberry Pi CD-Doos Robot
- Zelfbouw UVC-sterilisatiedoos – Goedkope, makkelijke UV-box voor medische en persoonlijke items
- Slimme Plant IoT: Verbonden kassen bouwen met Arduino & Azure
- Slimme Jaloezieën: Geautomatiseerd Lichtbeheer met Arduino & Zonne-energie
- Upgrade je huis: Slimme touch-schakelaar met temperatuurbewaking