Assumptions
Room: 10 × 12 × 8 ft · air: 33 kJ/K. Device W = electrical input. Ideal thermostat at target; power is duty-averaged; latent cooling and standby watts omitted. Room heat load fitted to 90°F at 8:56 p.m. without cooling. COP and heat rejection capacity assumed; heat-pipe heat sink is outdoors.
Photo layout: one end fan → shared fin channel → open rear outlet. Two outer water blocks, with four TECs per side inferred. Fan model and installed airflow remain unverified.
Verification · not yet measured
Sourced concept. Default cooling is a test hypothesis, not verified performance.
Enter steady-state measurements at the water-block inlet and outlet.
Qhot = 69.67 × flow × (Tout − Tin). Net room cooling ≈ Qhot − V × I − cold fan watts. Hot-side pump, radiator and supply stay outdoors. Sensor error and heat leaks affect the result; one point does not validate the entire curve.| Component | No. Included | Qty | Dimensions / specifications | Per unit $ | Per package $ | Row total $ | Idiot index | One-time? |
|---|
Block dimensions: matching ASIN listing (Amazon page unavailable). Five 40 mm modules span 200 mm without gaps. Component switches gate cooling and pump heat in the calculator.
Sketches
Rough concept sketches
Reference mounting sketch for thermal cooling. Update this SVG as CAD constraints evolve.
Threejs Assembly
Assembly
Staff assembly instructions
Short internal build notes: fastener torque, jig alignment, and sub-assembly order for the shop floor.
Design


User assembly experience
Photoreal references for customer-facing setup. Preserve this geometry and placement for future design iterations.
Assumptions
Feasibility estimate only. Target is the inner-liner cooling zone, not body temperature. Leather/air-gap insulation and wearer heat are assumed. Pumpable water/glycol surrogate: 1,030 kg/m³, 3,500 J/kg·K. Actual gel flow, skin contact, humidity and condensation are unverified.
Cool-jacket · initial BOM
| Component | No. Included | Qty | Basis | Budget $ | One-time? |
|---|---|---|---|---|---|
| Leather jacket shell | 1 | 1 | Assumed 0.6 m² coverage; not a specified garment | 80 | |
| 12 V heat-pipe chiller | 1 | 1 | 180 W / 40 L variant; COP unverified | 90 | |
| Flexible liner, tubing and coolant | 1 lot | 1 | Pumpable coolant surrogate, not a validated gel | 40 | |
| Small circulation pump + reservoir | 1 set | 1 | Assumed 0.5 L/min through installed loop | 25 | |
| External DC supply | 1 | 1 | Bench-powered feasibility; no battery assumed | 30 | |
| Insulation, connectors and clamps | 1 lot | 1 | Assembly allowance | 15 |
Sketches
Rough concept sketches
Blank by default. Use this area for rough SVG sketches that guide the CAD model.
Assembly
Staff assembly instructions
Short internal build notes: fastener torque, jig alignment, and sub-assembly order for the shop floor.
Design
User assembly experience
Short customer-facing setup notes: unboxing order, no-tool steps, and what the finished assembly should look like.
Battery-powered load-cell wiring
Use the kit’s included HX711; it turns the four half-bridge sensor signals into the digital reading the XIAO can measure. Connect the XIAO at 3.3 V, then use D2 for HX711 DOUT and D3 for SCK. The XIAO’s USB-C port both programs the board and charges the protected single-cell LiPo through its onboard charger.
Four 50 kg half-bridge sensors wired as one full Wheatstone bridge into a single HX711 — not two independent half-bridge channels. Verify the kit’s E+/E−/A+/A− wire-color diagram before assembly.| Expected value | Measured | % error |
|---|
UX
App
Waiting for a stable reading
Estimated consumed: 0 mLOwala · 24 oz
Simulated contents: 0 mL
Cutaway illustration · actual bottle is opaqueHardware
Network
Arduino concept
Download .inoBoard: Arduino Uno · Serial Monitor: 115200 baud
Library Manager: HX711 by Bogdan Necula, Adafruit ADXL345, Adafruit Unified Sensor, and Adafruit BusIO.
Send t to tare with no load. Set COUNTS_PER_GRAM using a known mass; until calibrated, weight logs as NA. Acceleration logs in m/s².
#include <Wire.h>
#include <HX711.h> // HX711 by Bogdan Necula / bogde
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
// Uno: D2 -> HX711 DAT, D3 -> CLK, A4 -> SDA, A5 -> SCL.
// Use the Adafruit 5V-compatible ADXL345 breakout. Common ground.
HX711 scale;
Adafruit_ADXL345_Unified accel(12345);
// Replace with measured (loaded_counts - tare_counts) / known_grams.
// Zero disables weight conversion: raw readings still log.
const float COUNTS_PER_GRAM = 0.0f;
long tareCounts = 0;
long rawCounts = 0;
bool haveLoad = false, haveTare = false, accelOK = false;
unsigned long lastLoad = 0, lastLog = 0;
void setup() {
Serial.begin(115200);
scale.begin(2, 3);
accelOK = accel.begin();
if (accelOK) accel.setRange(ADXL345_RANGE_4_G);
else Serial.println(F("ADXL345 missing; check wiring"));
Serial.println(F("Send t with no load to tare. Calibrate COUNTS_PER_GRAM for grams."));
Serial.println(F("ms,raw,weight_g,ax_m_s2,ay_m_s2,az_m_s2"));
}
void loop() {
if (scale.is_ready()) {
rawCounts = scale.read();
haveLoad = true;
lastLoad = millis();
}
while (Serial.available()) {
char c = Serial.read();
if (c == 't') {
if (haveLoad && millis() - lastLoad < 1000) {
tareCounts = rawCounts;
haveTare = true;
Serial.print(F("tare_counts=")); Serial.println(tareCounts);
} else Serial.println(F("No fresh HX711 reading to tare"));
}
}
if (millis() - lastLog < 200) return;
lastLog = millis();
bool fresh = haveLoad && millis() - lastLoad < 1000;
Serial.print(lastLog); Serial.print(',');
if (fresh) Serial.print(rawCounts); else Serial.print(F("NA"));
Serial.print(',');
if (fresh && haveTare && COUNTS_PER_GRAM != 0.0f)
Serial.print((rawCounts - tareCounts) / COUNTS_PER_GRAM, 1);
else Serial.print(F("NA"));
if (accelOK) {
sensors_event_t event;
accel.getEvent(&event);
Serial.print(','); Serial.print(event.acceleration.x, 3);
Serial.print(','); Serial.print(event.acceleration.y, 3);
Serial.print(','); Serial.println(event.acceleration.z, 3);
} else Serial.println(F(",NA,NA,NA"));
}
Bottle API
Local mock · same server · JSON · memory resets on restartGET /api/bottle/statusServer status
No parameters. Returns mock: boolean, storage: string, and records: integer.
Sample response
{
"mock": true,
"storage": "memory; resets on server restart",
"records": 2
}GET /api/bottle/measurementsList recorded measurements
Optional query parameter · string. Returns { measurements: Measurement[] }.
Sample response
{
"measurements": [
{
"runId": "demo",
"time_s": 2,
"weight_g": 700,
"water_ml": 300,
"consumed_ml": 0,
"receivedAt": "2026-09-10T12:00:00.000Z"
}
]
}Measurement: runId (string), time_s / weight_g / consumed_ml (numbers), optional water_ml (number), receivedAt (ISO timestamp). POST also returns classification and mock.
POST /api/bottle/measurementsRecord a stable weight
Request schema
| Field | Type | Required | Constraint |
|---|---|---|---|
| runId | string | Yes | 1–80 letters, digits, underscores or hyphens |
| time_s | number | Yes | ≥ 0; later than previous reading in this run |
| weight_g | number | Yes | 0–5000 g |
| water_ml | number | No | 0–1500 mL; calibrated water remaining |
201 Created · 400 Invalid payload · 409 Timestamp conflict · 413 Body over 4 KB
Sample response
{
"runId": "manual-test",
"time_s": 0,
"weight_g": 700,
"water_ml": 300,
"consumed_ml": 0,
"receivedAt": "2026-09-10T12:00:00.000Z",
"classification": "baseline",
"mock": true
}Measurement: runId (string), time_s / weight_g / consumed_ml (numbers), optional water_ml (number), receivedAt (ISO timestamp). POST also returns classification and mock.
Latest request · includes Performance actions
No request yet.
| Component | No. Included | Qty | Specification | Per unit $ | Per package $ | Row total $ | Idiot index | One-time? |
|---|
Sketches
Rough concept sketches
Blank by default. Use this area for rough SVG sketches that guide the CAD model.
Assembly
Staff assembly instructions
Short internal build notes: fastener torque, jig alignment, and sub-assembly order for the shop floor.
Design
User assembly experience
Short customer-facing setup notes: unboxing order, no-tool steps, and what the finished assembly should look like.