Innholdsfortegnelse:
- Forfatter John Day [email protected].
- Public 2024-01-31 10:23.
- Sist endret 2025-01-23 15:02.
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
