Innholdsfortegnelse:
- Trinn 1: CROUTON
- Trinn 2: ASSIMILER CROUTON
- Trinn 3: ENHETSMONTERING
- Trinn 4: FIRMWARE
- Trinn 5: ENHETSKORT
- Trinn 6: WEEKVIEW CARD
- Trinn 7: ENDPOINT -TILPASNING
- Trinn 8: VIDEOER
Video: IOT123 - ASSIMILATE SENSOR HUB: ICOS10 CORS WEBCOMPONENTS: 8 Steps
2024 Forfatter: John Day | [email protected]. Sist endret: 2024-01-31 10:23
ASSIMILATE SENSOR/ACTOR Slaves legger inn metadata som brukes til å definere visualiseringene i Crouton. Denne bygningen er litt forskjellig fra tidligere; det er ingen maskinvareendringer. Fastvaren støtter nå hosting tilpassede (rikere) redaktører som kan integreres i den siste versjonen av AssimilateCrouton. Mer oppmerksomhet vil bli gitt til å forklare fastvaren og MQTT -dashbordet i denne artikkelen.
En av fordelene med å betjene WebComponents fra enheten de kontrollerer, er at den mer avanserte kontrollen over enheten er begrenset til nettverket enheten er koblet til: WiFi -tilgangspunktet ditt. Selv om det er en likhet med beskyttelse når du bruker en MQTT -server med autentisering, i offentlige nettverk hvis du forlater nettleseren din for øyeblikket (AssimilateCrouton -nettstedet), kan noen hoppe inn og kontrollere automatiseringsenhetene dine. Denne CORS WebComponent -funksjonen gjør det mulig å bare vise avlesninger (temp, lysnivåer, fuktighet) offentlig og kommandofunksjoner (på/av, planlegging) bare tilgjengelig fra enhetsnettverket.
På enheten støttes alle webserverfunksjonene med autentisering og hosting i SPIFFS fortsatt, men det er lagt spesielt fokus på CORS (Cross Origin Resource Sharing) støtte for Polymer WebComponents (Crouton bruker Polymer 1.4.0).
I AssimilateCrouton (gaffelen til Crouton brukt til Assimilate IOT Network) inkluderer endringene
- støtte for et enhetskort (assim-device) som blant annet viser og skjuler, for en bruker, individuelle kort for en enhet
- info -egenskap på alle kort som viser en skål med nyttig kontekstuell informasjon for et kort
- støtte for CORS -webkomponenter, i dette tilfellet hostet på webserveren på enheten (ESP8266).
Trinn 1: CROUTON
Crouton er et dashbord som lar deg visualisere og kontrollere IOT -enhetene dine med minimalt oppsett. I hovedsak er det det enkleste dashbordet å sette opp for alle IOT -maskinvareentusiaster som bare bruker MQTT og JSON.
ASSIMILATE SLAVES (sensorer og aktører) har innebygde metadata og egenskaper som masteren bruker til å bygge opp deviceInfo json -pakken som Crouton bruker til å bygge dashbordet. Mellommannen mellom ASSIMILATE NODES og Crouton er en MQTT -megler som er nettstikkvennlig: Mygg brukes til demoen.
Siden ASSIMILATE MASTER ber om egenskaper, formaterer det svarverdiene i det nødvendige formatet for Crouton -oppdateringer. AssimilateCrouton -gaffelen legger til noen funksjoner som gjør at du kan desentralisere forretningsreglene som kjører enheten, det vil si at IOT -enheten ikke trenger noen innebygde forretningsregler, den er bare en rørledning for MQTT/I2C -kommunikasjon til de smartere (ATTINY -kontrollerte) slaveaktørene og sensorene.
Trinn 2: ASSIMILER CROUTON
ENDRINGER I CROUTON
Endringer fra den gaffelversjonen inkluderer:
- hvis et endepunkt har en baneiendom definert, vil WebComponent for kortet gjøre en HTMLImport for en CORS -ressurs (webserveren på ESP8266 i denne builden).
- eventuelle ressurser oppstrøms for (avhengigheter av) en CORS WebComponent refereres som om de blir betjent fra Crouton -nettstedet; når de ikke klarer å laste en unntaksbehandler, avviser banene og laster hvis fra nettstedet.
- en gjeldende lokal tid vises øverst til høyre, nyttig for planlegging av bekreftelse.
POLYMERAVHENGIGHETER OG KORS
Bladene til et tre av Polymer -avhengighet kan lagres i CORS. Fordi rotavhengighetene kan brukes flere ganger i en app, kan de ikke refereres til fra to steder (nettstedet og enheten) fordi Polymer Module Loader behandler dem som 2 separate ressurser og flere registreringsfeil raskt flyndrer et program.
Av denne grunn er WebComponent for et kort (HTML -fil i 1.4.0) og den tilhørende CSS -filen de eneste filene som er lagret på enheten. De andre avhengighetene refereres som om WebComponent er plassert i "html" -mappen på det opprinnelige nettstedet, noe som gjør det enkelt å utvikle WebComponents fra den mappen til den er klar til å lastes opp til SPIFFS på ESP8266. AssimilateCrouton vil finne ut hvordan du får de riktige filene.
UTPLASSERING
edfungus skaperen av den originale Crouton skrev kilden i Pug/Less og hadde en NPM/Grunt verktøykjede. Jeg gjengjorde Pug/Less som HTML/css og redigerte/distribuerte bare de gjengitte filene. Dette brøt NPM/Grunt -verktøykjeden. Å fikse dette er dekket i FREMTID -delen.
Du kan teste dashbordet lokalt på DEV -boksen din:
- Fra kommandolinjen i rotmappen
- npm start
- lite-serveren er spunnet opp for https:// localhost: 10001
Distribuer til en statisk webserver:
- kopier alle mapper unntatt node_modules
- kopier index.html (og muligens web.config)
FRAMTID
Et av hovedmålene er å oppgradere til Polymer3 og jobbe fra Polymer CLI. Å legge til avanserte redaktører og rammeverk for IOT -utviklere til å utvikle sine egne er høyt prioritert. Etter hvert vil det avanserte automatiserte systemet bli kjørt helt fra frittliggende MQTT -klienter som AssimilateCrouton.
Et eksempel på deviceInfo-pakken som brukes for AssimilateCrouton:
{ |
"enhetsinformasjon": { |
"endPoints": { |
"CC_enhet": { |
"device_name": "ash_mezz_A3", |
"card-type": "assim-enhet", |
"ssid": "Corelines_2", |
"ip_addr": "192.168.8.104", |
"endepunkter": [ |
{ |
"title": "Grow Lights", |
"card-type": "crouton-simple-toggle", |
"endepunkt": "bytte" |
}, |
{ |
"title": "Planterlys", |
"card-type": "crouton-assim-weekview", |
"endpoint": "CC_bryter" |
} |
] |
}, |
"CC_switch": { |
"card-type": "assim-weekview", |
"info": "Slå lysene på eller av i løpet av 15 minutter", |
"path": "https://192.168.8.104/cors", |
"title": "Planterlys", |
"interval_mins": 15, |
"verdier": { |
"verdi": "" |
} |
}, |
"bytte om": { |
"title": "Grow Lights", |
"card-type": "crouton-simple-toggle", |
"info": "Slå på eller av lys på ad hoc -basis", |
"labels": { |
"false": "AV", |
"true": "ON" |
}, |
"ikoner": { |
"false": "sun-o", |
"true": "sun-o" |
}, |
"verdier": { |
"verdi": 0 |
} |
} |
}, |
"status": "bra", |
"name": "ash_mezz_A3", |
"description": "Office at Ashmore, Mezzanine, Area A2", |
"color": "#4D90FE" |
} |
} |
se rawdeviceInfo.json hostet av ❤ av GitHub
Trinn 3: ENHETSMONTERING
Siden det ikke er noen maskinvareendringer, er her koblingene til relevant informasjon:
- Skallmontering
- Materialer og verktøy
- MCU -forberedelse
- MCU Boligforberedelse
- Bygge Slaves Low-side Switch/RESET Daughter-board
- Montering av hovedkomponentene
Trinn 4: FIRMWARE
HOVED ENDRINGER DETTE BYGGET
For at AssimilateCrouton -applikasjonen skulle kunne bruke CORS -ressurser fra enheten, måtte responsoverskrifter konfigureres på en bestemt måte. Dette ble implementert i denne versjonen av fastvaren (static_server.ino => server_file_read ()).
Også hovedavhengighetsgrafen for Polymer måtte være fra en enkelt opprinnelse. En strategi ble brukt til å legge til en onerror -handler (corsLinkOnError) til SPIFFS CORS -filene for å laste inn ressursene fra AssimilateCrouton -nettstedet når de ikke blir funnet på enheten.
Det er lagt til to nye konvensjoner i SPIFFS -filsystemet for å tilpasse endepunktene som opprettes i deviceInfo - som AssimilateCrouton bruker til å lage dashbordkortene:
- /config/user_card_base.json Endpoint -definisjon med kjøretidsvariabler som først byttes:,,. Det er vanligvis her assim-enhetskortet blir lagt til. Dette kommuniserer ikke tilbake med enheten.
- /config/user_card_#.json Sluttpunktsdefinisjon med kjøretidsvariabler som først byttes:,,. Det er vanligvis her de rike redaktørene som assim-weekview-kortet blir lagt til koblet til I2C-slaven (skuespiller/sensor) som er relatert til #.
SKETSEN/BIBLIOTEKETE
På dette stadiet har prosjektet blitt pakket som et eksempel for AssimilateBus Arduino -biblioteket. Dette er hovedsakelig for å gjøre alle nødvendige filer lett tilgjengelige fra Arduino IDE. Hovedkodeartefaktene er:
- mqtt_crouton_esp8266_cors_webcomponents.ino - hovedinngangspunktet.
- assimilate_bus.h/assimilate_bus.cpp - biblioteket som håndterer I2C -kommunikasjonen med Slave Sensor/Actors
- VizJson.h/VizJson.cpp - biblioteket som formaterer/bygger JSON publisert via MQTT
- config.h/config.cpp - biblioteket som leser/bokser/skriver konfigurasjonsfiler på SPIFFS
- static_i2c_callbacks.ino - I2C -tilbakeringingene for en eiendom som mottas og slaveforespørselssyklusen er fullført static_mqtt.ino - MQTT -funksjonene
- static_server.ino - webserverfunksjonene
- static_utility.ino - hjelperfunksjoner
De statiske INO -funksjonene ble brukt (i stedet for biblioteker) av en rekke årsaker, men hovedsakelig for at funksjonene Webserver og MQTT kunne spille godt sammen.
SPIFFS -RESSURSENE
Detaljerte forklaringer på SPIFFS -filene finner du her.
- favicon.ico - ressurs brukt av Ace Editor
-
konfigur
- device.json - konfigurasjonen for enheten (Wifi, MQTT …)
- slave_metas _#. json - generert ved kjøretid for hvert slaveadressenummer (#)
- user_card _#. json - tilpasset endepunkt som skal integreres i deviceInfo for hvert slaveadressenummer (#)
- user_card_base.json - tilpasset endepunkt som skal integreres i deviceInfo for enheten
- user_meta _#. json - egendefinerte metadata overstyrer slavene for hvert slaveadressenummer (#)
- user_props.json - egendefinerte eiendomsnavn for å overstyre de i metadataene til slavene
-
cors
- card -webcomponent.css - stilark for forskjellige tilpassede kort
- card -webcomponent.html - webkomponent for forskjellige tilpassede kort
-
redaktør
- assimilate -logo-p.webp" />
- edit.htm.gz - gzip av Ace Editor HTML
- edit.htm.src - original HTML av Esseditoren
- favicon -32x32-p.webp" />
OPPLADERING AV FIRMWARE
- Kodelageret finner du her (øyeblikksbilde).
- En ZIP av biblioteket finner du her (øyeblikksbilde).
- Instruksjoner for "Import av et ZIP -bibliotek" her.
- Når biblioteket er installert, kan du åpne eksempelet "mqtt_crouton_esp8266_cors_webcomponents".
- Instruksjoner for hvordan du konfigurerer Arduino for Wemos D1 Mini her.
- Avhengigheter: ArduinoJson, TimeLib, PubSubClient, NeoTimer (se vedlegg hvis du bryter endringer i depotene).
Laster opp til SPIFFS
Når koden er lastet inn i Arduino IDE, åpner du device.json i data/config -mappen:
- Endre verdien av wifi_ssid med WiFi SSID.
- Endre verdien av wifi_key med WiFi -nøkkelen.
- Endre verdien av mqtt_device_name med din foretrukne enhetsidentifikasjon (ingen sammenkobling nødvendig).
- Endre verdien av mqtt_device_description med din foretrukne enhetsbeskrivelse (i Crouton).
- Lagre device.json.
- Last opp datafilene til SPIFFS.
Hovedinngangspunktet for AssimilateBus-eksemplet:
/* |
* |
*FORRETNINGSREGLENE FOR ENHETEN DIN FORVENTES Å KONTROLLERES VIA MQTT - IKKE HARDT INNBAKET I DENNE FIRMWARE |
* |
* Annet enn oppsett og loop i denne filen |
* De viktige bevegelige delene er |
* on_bus_received og on_bus_complete i static_i2c_callbacks.ino |
* og |
* mqtt_publish og mqtt_callback i static_mqtt.ino |
* |
*/ |
#include "types.h" |
#inkluder "VizJson.h" |
#include "assimilate_bus.h" |
#include "debug.h" |
#include "config.h" |
#inkludere |
#inkludere // sett MQTT_MAX_PACKET_SIZE til ~ 3000 (eller dine behov for deviceInfo json) |
#inkludere |
#inkludere |
#inkludere |
#inkludere |
#inkludere |
// --------------------------------- MINNE-ERKLARINGER |
// ------------------------------------------------ - definerer |
#defineDBG_OUTPUT_FLAG2 // 0, 1, 2 MINIMUM, RELEASE, FULL |
#define_mqtt_pub_topic "outbox" // CROUTON CONVENTIONS |
#define_mqtt_sub_topic "innboks" |
// ------------------------------------------------ - klasseobjekter |
Debug _debug (DBG_OUTPUT_FLAG); |
AssimilateBus _assimilate_bus; |
VizJson _viz_json; |
Config _config_data; |
WiFiClient _esp_client; |
PubSubClient _client (_esp_client); |
WiFiUDP Udp; |
ESP8266WebServer _server (80); |
Neotimer _timer_property_request = Neotimer (5000); |
// ------------------------------------------------ - datastrukturer / variabel |
RuntimeDeviceData _runtime_device_data; |
PropertyDto _dto_props [50]; // maks 10 slaver x maks 5 eiendommer |
// ------------------------------------------------ - kontrollflyt |
volatilebool _sent_device_info = false; |
byte _dto_props_index = 0; |
bool _fatal_error = false; |
// --------------------------------- FUNKSJONSOMRÅDE DEKLARASJONER |
// ------------------------------------------------ - static_i2c_callbacks.ino |
voidon_bus_received (byte slave_address, byte prop_index, rolle rolle, char navn [16], char verdi [16]); |
voidon_bus_complete (); |
// ------------------------------------------------ - static_mqtt.ino |
voidmqtt_callback (char* topic, byte* nyttelast, usignert lengde); |
voidmqtt_loop (); |
int8_tmqtt_get_topic_index (char* topic); |
voidmqtt_init (constchar* wifi_ssid, constchar* wifi_password, constchar* mqtt_broker, int mqtt_port); |
voidmqtt_create_subscriptions (); |
voidmqtt_publish (char *root_topic, char *deviceName, char *endepunkt, constchar *nyttelast); |
boolmqtt_ensure_connect (); |
voidmqtt_subscribe (char *root_topic, char *deviceName, char *endpoint); |
voidi2c_set_and_get (byte adresse, byte kode, constchar *param); |
// ------------------------------------------------ - statisk_server.ino |
String server_content_type_get (strengfilnavn); |
boolserver_path_in_auth_exclusion (strengbane); |
boolserver_auth_read (strengbane); |
boolserver_file_read (strengbane); |
voidserver_file_upload (); |
voidserver_file_delete (); |
voidserver_file_create (); |
voidserver_file_list (); |
voidserver_init (); |
voidtime_services_init (char *ntp_server_name, byte time_zone); |
time_tget_ntp_time (); |
voidsend_ntp_packet (IPAddress & adresse); |
char *time_stamp_get (); |
// ------------------------------------------------ - static_utility.ino |
String spiffs_file_list_build (Stringbane); |
voidreport_deserialize_error (); |
voidreport_spiffs_error (); |
boolcheck_fatal_error (); |
boolget_json_card_type (byte slave_address, byte prop_index, char *card_type); |
boolget_struct_card_type (byte slave_address, byte prop_index, char *card_type); |
boolget_json_is_series (byte slave_address, byte prop_index); |
voidstr_replace (char *src, constchar *oldchars, char *newchars); |
byte get_prop_dto_idx (byte slave_address, byte prop_index); |
//---------------------------------HOVED |
voidsetup () { |
DBG_OUTPUT_PORT.begynner (115200); |
SetupDeviceData device_data; |
Serial.println (); Serial.println (); // margin for konsollsøppel |
forsinkelse (5000); |
hvis (DBG_OUTPUT_FLAG == 2) DBG_OUTPUT_PORT.setDebugOutput (true); |
_debug.out_fla (F ("setup"), true, 2); |
// få nødvendig konfigurasjon |
hvis (SPIFFS.begin ()) { |
_debug.out_str (spiffs_file_list_build ("/"), true, 2); |
hvis (! _config_data.get_device_data (device_data, _runtime_device_data)) { |
report_deserialize_error (); |
komme tilbake; |
} |
}ellers{ |
report_spiffs_error (); |
komme tilbake; |
} |
// bruk tidtakerverdi angitt i device.json |
_timer_property_request.set (device_data.sensor_interval); |
mqtt_init (device_data.wifi_ssid, device_data.wifi_key, device_data.mqtt_broker, device_data.mqtt_port); |
time_services_init (device_data.ntp_server_name, device_data.time_zone); |
server_init (); |
// kick off metadata collection |
_assimilate_bus.get_metadata (); |
_assimilate_bus.print_metadata_details (); |
mqtt_ensure_connect (); |
// trenger sensoregenskap (navn) for å fullføre metadatasamlingen |
_assimilate_bus.get_properties (on_bus_received, on_bus_complete); |
_timer_property_request.reset (); // kan forfalle merkbar tid til dette punktet, så start det på nytt |
} |
voidloop () { |
if (! check_fatal_error ()) return; |
mqtt_loop (); |
_server.handleClient (); |
hvis (_timer_property_request.repeat ()) { |
_assimilate_bus.get_properties (on_bus_received, on_bus_complete); |
} |
} |
vis rawmqtt_crouton_esp8266_cors_webcomponents.ino hostet av ❤ av GitHub
Trinn 5: ENHETSKORT
Enhetskortet (korttype: assim-device) ligger på nettstedet, og det er ikke nødvendig å betjene det fra enheten (CORS).
Standardsidelistene:
- MQTT -emnene for lesing og skriving til enheten
- Tilgangspunktet enheten er koblet til
- En lenke til SPIFFS -filredigereren som er plassert på enheten ved hjelp av ACE EDITOR
- Et øyeikon som viser siden Vis/skjul kort.
Siden Vis/skjul kort viser:
- Hvert kort som et eget element
- Fet blå skrift når den vises
- Svart normal skrift når den er skjult
- Et ikon som viser typen kort.
Kortet kan skjules ved å klikke på skjul-knappen på kortene, eller ved å klikke på et blått skrift-element i listen. Kortene kan vises ved å klikke på et element med svart normal skrift i listen.
Løst relatert til denne funksjonen er infoskålene. Hvis noen av endepunktene i deviceInfo har en info -egenskap tildelt, vises en info -knapp ved siden av skjul -knappen på kortet. Når du klikker, blir den kontekstuelle informasjonen som er definert i endepunktet "ristet" i vinduet.
Hvis enhetskortet ikke er definert, vises ikke skjulknappene på kortene. Dette er fordi en gang de er skjult, er det ingen måte å vise dem på igjen.
Se ENDPOINT CUSTOMIZATION for å finne ut hvordan assim-enhetskortet kan legges til via SPIFFS-filene på ESP8266.
AssimilateCrouton WebComponent
VIS HJEM IKON |
ENHETSKJEMA |
div> |
VIS GJEM LISTE |
mal> |
papirliste> |
div> |
crouton-kort> |
mal> |
dom-modul> |
se rawassim-device.html hostet av ❤ av GitHub
Trinn 6: WEEKVIEW CARD
Weekview-kortet (korttype: assim-weekview) ligger på enheten (mappen cors). Den injiseres i deviceInfo -pakken som er publisert for AssimilateCrouton, ved å legge til en fil config/user_card _#. Json i SPIFFS (i dette tilfellet user_card_9.json).
OVERSIKT
Ukedagene presenteres som lister over tidsluker. Detaljene til tidsluken settes med egenskapen "interval_mins" i config/user_card _#. Json. Det må være en brøkdel av en time eller flere ganger en time f.eks. 10, 15, 20, 30, 60, 120, 360. Klikk på en tidsluke for å sikre at en på-tilstand blir kommandert for den tilknyttede enheten på den tiden. Hvis tidsluken er nå, sendes en kommando (publiseres) umiddelbart for enheten. Normalt kontrolleres/publiseres staten hvert minutt. Valgene lagres i LocalStorage, så tidene lastes på nytt med en oppdatering av nettleseren.
BRUK SAKER
I sin nåværende tilstand er ukevisningen egnet for enheter som kan bruke en Toggle -bryter for å visualisere tilstanden, dvs. at de enten er på eller av, og etter at de er satt, forblir de i den tilstanden. Lys, vifter og varmtvannsberedere er gode kandidater.
BEGRENSNINGER/HULLER
- Intervallet_miner må være en av verdiene nevnt ovenfor
- Ukevisningen støtter ikke øyeblikkelige handlinger som også er planlagt, for eksempel å trykke kort på (5 sekunder) to ganger om dagen.
FRAMTID
- Det forventes at øyeblikkelige handlinger vil bli støttet.
- Synkronisert lagring på tvers av enheter, for valg av plan, vurderes.
Trinn 7: ENDPOINT -TILPASNING
Som nevnt i FIRMWARE, er det to nye konvensjoner lagt til SPIFFS -filsystemet for å tilpasse endepunktene. JSON -filene er fragmenter som blir lagt til egenskapene endepunkter i deviceInfo -pakken som er lagt ut på MQTT -megleren som blir dashborddefinisjonen.
Nøklene til endepunktene genereres i fastvaren:
- CC_device (Custom Card) for user_card_base.json
- CC_SLAVE_ENDPOINT NAME for user_card _#. Json (# er slaveadresse)
Som nevnt tidligere er det variabler som blir erstattet av verdier ved kjøretid:
- mqtt_device_name
- wifi_ssid
- lokal_ip
user_card_base.json
Et eksempel:
user_card _#. json
Et eksempel:
Trinn 8: VIDEOER
Anbefalt:
Docker Pi -serien av Sensor Hub Board Om IOT: 13 trinn
Docker Pi -serien av Sensor Hub Board Om IOT: Hei alle sammen. I dag er nesten alt relatert til IOT. Ingen tvil om det, vårt DockerPi -seriekort støtter også IOT. I dag vil jeg introdusere DockerPi -serien til SensorHub hvordan å søke om IOT til deg. Jeg kjører dette elementet som er basert på
IOT123 - MULIG SENSORHUB: ICOS10 GENERISK SKAL (IDC) MONTERING: 6 trinn
IOT123 - ASSIMILATE SENSOR HUB: ICOS10 GENERIC SHELL (IDC) MONTERING: IKKE Dette er en forbedret (krets robusthet) versjon av ASSIMILATE SENSOR HUB: ICOS10 GENERIC SHELL (HOOKUP WIRE) Assembly. Den monteres raskere og har en krets av høyere kvalitet, men koster mer (~ $ 10 ekstra hvis du støtter 10 sensorer). Den viktigste fe
IOT123 - I2C 2CH RELAY BRICK: 5 Steps (med bilder)
IOT123 - I2C 2CH RELAY BRICK: IOT123 BRICKS er DIY modulære enheter som kan moses sammen med andre IOT123 BRICKS, for å legge funksjonalitet til en node eller bærbar. De er basert på de tommers firkantede, tosidige protoboardene med sammenhengende gjennomgående hull. En rekke av disse BRICK
IOT123 - SOLAR TRACKER - TILT/PAN, PANEL FRAME, LDR MOUNTS RIG: 9 Steps (with Pictures)
IOT123 - SOLAR TRACKER - TILT/PAN, PANEL FRAME, LDR MOUNTS RIG: De fleste DIY -designene for toakse solsporere "der ute" er basert på 9G Micro Servo som virkelig er undervurdert for å skyve rundt et par solceller, mikrokontrolleren, batteriet og huset. Du kan designe rundt
USB Power Fan Cooled, Built in USB Hub, Laptop Bag Part 1: 6 Steps
USB -vifte avkjølt, innebygd USB -hub, bærbar veske Del 1: Laptop -vesker er dyre. de billige er totalt dritt. De knapt anstendige starter på omtrent $ 69,99, og jeg har vanskelig for å bruke så mye penger når det ikke er akkurat det jeg vil i utgangspunktet, så jeg bestemte meg for å gjøre det selv og se hva jeg