Logging av ølmengde på fat, hva kan AI få til her da?

Roadrune

Norbrygg-medlem
Overraskende mye skulle det vise seg!
IMG_5050.jpeg

Ok, litt bakgrunn: når jeg først startet med fat og tappekraner ønsket jeg en for for kontroll på hvor mye som var på fatene, jeg fikk tak i noen flowmetre som ble koblet via Arduino og en Rasberry PI samt en programvare jeg ikke husker navnet på lenger. Det fungerte relativt dårlig med mye feilmåling og jeg fikk det aldri bra.
Så lanserte Plaato vekter som fatene kunne stå på, vel og bra, men det fungerte heller aldri spesielt bra da de mistet nett hele tiden og i tillegg hadde en heller tvilsom strømkobling. Tilslutt la de også ned serveren og det gjorde at jeg ga opp hele Plaato, løsningen da ble en whiteboard på tappeskapet med info om øl, og ingen logging, men siden jeg likte konseptet med vekter kjøpte jeg inn lastceller (50kg) med hx711 forsterkere fra Aliexpress og fikk brent 10mm aluplater på jobb. Men jeg fant ikke noen god softwareløsning så derfor døde hele prosjektet ut.

Spol frem noen år, og kona forteller en nabo at tappeskapet var mye kulere når jeg hadde skjerm. Jeg spurte så ChatGPT om det var dukket opp noen brukbare løsninger, svaret var nja, men siden du bruker Home Assistent til mye tror jeg vi kan lage noe bedre, da var motivasjonen tent.

IMG_5049.jpeg

Vi startet med å koble alt opp slik at en kunne få fungerende hardware, i tillegg til det jeg handlet på Aliexpress brukte jeg en AtomLite fra M5stack siden jeg hadde noen liggende, men omtrent alt av ESP32 kan brukes til formålet. Chatgpt skrev koden og det ble lastet inn via Esphome slik at vektene dukket opp i Home Assistent. Kalibrering ble gjort med ett manualsett jeg hadde stående (så ble det brukt også :D )

IMG_4986.jpeg

Alt ble plassert i ett 3D printet kabinett (som gpt tegnet)IMG_4995.jpeg

Så begynte moroa; lage ett dashboard tilpasset en gammel Ipad som ikke ble brukt.

Jeg ønsket at ølene kunne hentes direkte fra Brewfather som har en HA integrasjon, men de entitetene vi trengte her manglet siden tanken med denne er først og fremst opp mot automasjon på gjæring. Jeg fikk derfor gpt til å skrive en ny integrasjon/tillegg til Brewfather som hentet det jeg ville ha: alt relevant fra de ølene som står enten som ferdig eller modning, den henter Navn, stil, abv, tappedato, FG, batchnr og farge som alle brukes under visningen. IMG_5048.jpeg

Logoen (også lagd av gpt) øverst, deretter klikkbar rullegardinmeny som kun inneholder de ølene som er under modning eller ferdig i BF, info om ølet under det igjen og nederst grafisk visning på restmengde øl der fargen er beregnet ut fra EBC (denne syns jeg er vel lys, så der blir det muligens litt endringer), i tillegg stiger det bobler opp i ølet kontinuerlig samt det skvulper litt på toppen.

Ps, dette innlegget har jeg skrevet på egenhånd :)
 

Vedlegg

  • IMG_4989.jpeg
    IMG_4989.jpeg
    1,7 MB · Sett: 20
Imponerende prosjekt, men er det ikke et problem å bare ha en vektcelle pr vekt? Må ikke fatet stå veldig nøyaktig over vektcellen for å få riktig måling?

Jeg har et lignende prosjekt som har gått på tomgang en god stund nå, fant ut, som deg, at vekt er veien å gå for å holde kontroll på innholdet i fat. Men jeg har kjøpt inn fire 50 kg celler pr fat. Høres ut som overkill, men det ble bestemt sammen med Claude eller ChatGPT (finner ikke chaten) for at det skulle bli mest mulig nøyaktig.

IMG_0408.jpeg
 
Imponerende prosjekt, men er det ikke et problem å bare ha en vektcelle pr vekt? Må ikke fatet stå veldig nøyaktig over vektcellen for å få riktig måling?

Jeg har et lignende prosjekt som har gått på tomgang en god stund nå, fant ut, som deg, at vekt er veien å gå for å holde kontroll på innholdet i fat. Men jeg har kjøpt inn fire 50 kg celler pr fat. Høres ut som overkill, men det ble bestemt sammen med Claude eller ChatGPT (finner ikke chaten) for at det skulle bli mest mulig nøyaktig.

Vis vedlegget 78999
Hvorfor 4 og ikke 3. Takler ikke 3 ujevnt underlag bedre?
 
Imponerende prosjekt, men er det ikke et problem å bare ha en vektcelle pr vekt? Må ikke fatet stå veldig nøyaktig over vektcellen for å få riktig måling?

Jeg har et lignende prosjekt som har gått på tomgang en god stund nå, fant ut, som deg, at vekt er veien å gå for å holde kontroll på innholdet i fat. Men jeg har kjøpt inn fire 50 kg celler pr fat. Høres ut som overkill, men det ble bestemt sammen med Claude eller ChatGPT (finner ikke chaten) for at det skulle bli mest mulig nøyaktig.

Vis vedlegget 78999
Jeg hadde samme bekymring, men så lenge det er skikkelig fastskrudd i solide plater ser det stabilt ut. 2 eller 3 pr ville nok vært bedre, men det ser ut som om 1 er godt nok.
 
Hvorfor 4 og ikke 3. Takler ikke 3 ujevnt underlag bedre?
Jo, jeg begynte å lure på det i dag, hvorfor jeg ikke gikk for tre i stedet. Fant chaten, den var med Gemini, det var derfor jeg ikke fant den tidligere. Nå trodde jeg at det var den som overtalte meg til å bruke fire 50 kg celler, men jeg ser at det var jeg som insisterte på det. Tvert i mot mente den at med samlet kapasitet på 200 kilo ble det litt vanskeligere å måle små variasjoner, men mente også at det var gjennomførbart med god koding.

Grunnen til at jeg gikk for 50 kilo-celler var at de er lave, og jeg har veldig lite plass over fatene. Med tre celler vil det bli mer nøyaktig måling, jeg får se hva jeg gjør.
 
Jeg hadde samme bekymring, men så lenge det er skikkelig fastskrudd i solide plater ser det stabilt ut. 2 eller 3 pr ville nok vært bedre, men det ser ut som om 1 er godt nok.
Setter du fatene på akkurat samme plass så går det vel greit. Det er vel viktig at du legger slangene for øl og co2 samme vei hver gang også, det vil jo påvirke hvordan platen trykker på vektcellen under. Mulig jeg overtenker, men…
 
Overraskende mye skulle det vise seg!
Vis vedlegget 78964

Ok, litt bakgrunn: når jeg først startet med fat og tappekraner ønsket jeg en for for kontroll på hvor mye som var på fatene, jeg fikk tak i noen flowmetre som ble koblet via Arduino og en Rasberry PI samt en programvare jeg ikke husker navnet på lenger. Det fungerte relativt dårlig med mye feilmåling og jeg fikk det aldri bra.
Så lanserte Plaato vekter som fatene kunne stå på, vel og bra, men det fungerte heller aldri spesielt bra da de mistet nett hele tiden og i tillegg hadde en heller tvilsom strømkobling. Tilslutt la de også ned serveren og det gjorde at jeg ga opp hele Plaato, løsningen da ble en whiteboard på tappeskapet med info om øl, og ingen logging, men siden jeg likte konseptet med vekter kjøpte jeg inn lastceller (50kg) med hx711 forsterkere fra Aliexpress og fikk brent 10mm aluplater på jobb. Men jeg fant ikke noen god softwareløsning så derfor døde hele prosjektet ut.

Spol frem noen år, og kona forteller en nabo at tappeskapet var mye kulere når jeg hadde skjerm. Jeg spurte så ChatGPT om det var dukket opp noen brukbare løsninger, svaret var nja, men siden du bruker Home Assistent til mye tror jeg vi kan lage noe bedre, da var motivasjonen tent.

Vis vedlegget 78963

Vi startet med å koble alt opp slik at en kunne få fungerende hardware, i tillegg til det jeg handlet på Aliexpress brukte jeg en AtomLite fra M5stack siden jeg hadde noen liggende, men omtrent alt av ESP32 kan brukes til formålet. Chatgpt skrev koden og det ble lastet inn via Esphome slik at vektene dukket opp i Home Assistent. Kalibrering ble gjort med ett manualsett jeg hadde stående (så ble det brukt også :D )

Vis vedlegget 78958

Alt ble plassert i ett 3D printet kabinett (som gpt tegnet)Vis vedlegget 78955

Så begynte moroa; lage ett dashboard tilpasset en gammel Ipad som ikke ble brukt.

Jeg ønsket at ølene kunne hentes direkte fra Brewfather som har en HA integrasjon, men de entitetene vi trengte her manglet siden tanken med denne er først og fremst opp mot automasjon på gjæring. Jeg fikk derfor gpt til å skrive en ny integrasjon/tillegg til Brewfather som hentet det jeg ville ha: alt relevant fra de ølene som står enten som ferdig eller modning, den henter Navn, stil, abv, tappedato, FG, batchnr og farge som alle brukes under visningen. Vis vedlegget 78962

Logoen (også lagd av gpt) øverst, deretter klikkbar rullegardinmeny som kun inneholder de ølene som er under modning eller ferdig i BF, info om ølet under det igjen og nederst grafisk visning på restmengde øl der fargen er beregnet ut fra EBC (denne syns jeg er vel lys, så der blir det muligens litt endringer), i tillegg stiger det bobler opp i ølet kontinuerlig samt det skvulper litt på toppen.

Ps, dette innlegget har jeg skrevet på egenhånd :)
Kunne du ha tilgjengeliggjort deler av koden?
Ønsker en skjerm, men dropper vekt og måling.
Hadde vært interessant med API’et du laget for høsting av data via Esphome
Er Dash’n html, eller native kode i HomeA?

Selv har jeg laget temp-logging fra esp til Brewfather. Det er meget enkelt via API’et. Deler gjerne den. Da kan du trolig også laste ned gjæringsdata i Dash’n din.

Laster også ned litt nettverks- og HW info til BF. Kjekt å ha tilgjengelig. Det meste genererer Arduino automatisk
1786982553760.png
Fra BF
1786982518356.png
 
Setter du fatene på akkurat samme plass så går det vel greit. Det er vel viktig at du legger slangene for øl og co2 samme vei hver gang også, det vil jo påvirke hvordan platen trykker på vektcellen under. Mulig jeg overtenker, men…
Jeg tror du overtenker. Noen gram fra eller til betyr null i praksis.
 
Kunne du ha tilgjengeliggjort deler av koden?
Ønsker en skjerm, men dropper vekt og måling.
Hadde vært interessant med API’et du laget for høsting av data via Esphome
Er Dash’n html, eller native kode i HomeA?

Selv har jeg laget temp-logging fra esp til Brewfather. Det er meget enkelt via API’et. Deler gjerne den. Da kan du trolig også laste ned gjæringsdata i Dash’n din.

Laster også ned litt nettverks- og HW info til BF. Kjekt å ha tilgjengelig. Det meste genererer Arduino automatisk
Vis vedlegget 79001
Fra BF
Vis vedlegget 79000
All kode er i HA format; .yaml (kan ikke nok om koding til å uttale meg mer, men tror muligens det er en form for Python?)
Jeg kan godt dele alt, men akkurat nå er jeg på ferie så det får bli litt ut i neste uke.
 
Her er koden for dashbordet:
Kode:
title: Klubbane Garasjebryggeri
kiosk_mode:
  hide_header: true
  hide_sidebar: true
  mobile_settings:
    hide_header: true
    hide_sidebar: true
button_card_templates:
  klubbane_keg:
    show_name: false
    show_icon: false
    show_state: false
    tap_action:
      action: none
    hold_action:
      action: none
    double_tap_action:
      action: none
    triggers_update:
      - sensor.keg_list
      - '[[[ return variables.selector; ]]]'
      - '[[[ return variables.liters; ]]]'
    custom_fields:
      content: |
        [[[
          const helper = states[variables.selector];
          const selected = helper?.state || 'Ingen øl valgt';
          const beers = states['sensor.keg_list']?.attributes?.beers || [];


          // input_select options are trimmed before they are stored. Brewfather
          // names can contain invisible leading/trailing or repeated spaces.
          // Compare normalized names so the selected beer still finds its data.
          const normalizeBeerName = value => String(value ?? '')
            .trim()
            .replace(/\s+/g, ' ')
            .toLocaleLowerCase('nb-NO');


          const selectedKey = normalizeBeerName(selected);
          const beer = beers.find(
            b => normalizeBeerName(b?.name) === selectedKey
          ) || {};
          const litersState = states[variables.liters];
          const liters = Number.parseFloat(litersState?.state);
          const validLiters = Number.isFinite(liters);
          const capacity = Number(variables.capacity || 19);
          const percent = validLiters
            ? Math.max(0, Math.min(100, liters / capacity * 100))
            : 0;


          const options = [...new Set(
            (helper?.attributes?.options || [])
              .filter(n => typeof n === 'string' && n.trim())
          )];


          const esc = value => String(value ?? '')
            .replaceAll('&','&amp;').replaceAll('<','&lt;')
            .replaceAll('>','&gt;').replaceAll('"','&quot;')
            .replaceAll("'",'&#039;');


          const fmtDate = value => {
            if (!value) return '–';
            const d = new Date(value);
            if (Number.isNaN(d.getTime())) return String(value);
            return new Intl.DateTimeFormat('nb-NO', {
              day:'2-digit', month:'2-digit', year:'numeric'
            }).format(d);
          };


          // Approximate standard beer-colour palette, indexed by SRM.
          const srmPalette = [
            '#f8f3a5','#f6e96b','#f4dc42','#efc72f','#e7ae22',
            '#dc9418','#cc7913','#b96010','#a54a0d','#91380b',
            '#7d2b09','#692207','#561b06','#451505','#361104',
            '#2a0d04','#210a04','#190803','#120602','#0c0402'
          ];
          let srm = Number.parseFloat(beer.color_srm);
          if (!Number.isFinite(srm)) {
            const ebc = Number.parseFloat(beer.color_ebc);
            srm = Number.isFinite(ebc) ? ebc / 1.97 : 7;
          }
          const colorIndex = Math.max(0, Math.min(srmPalette.length - 1, Math.round(srm) - 1));
          const beerColor = srmPalette[colorIndex];


          // Progress colour: red at empty, yellow halfway, green at full.
          const progressHue = Math.round(percent * 1.2);
          const progressColor = `hsl(${progressHue} 82% 45%)`;


          const optionHtml = options.map(name =>
            `<option value="${esc(name)}"${name === selected ? ' selected' : ''}>${esc(name)}</option>`
          ).join('');


          return `
            <div class="keg-card">
              <div class="fat-title"><i></i><span>FAT ${variables.keg_number}</span><i></i></div>


              <div class="selector-wrap">
                <ha-icon icon="mdi:beer"></ha-icon>
                <select class="beer-select"
                  onclick="event.stopPropagation()"
                  onmousedown="event.stopPropagation()"
                  ontouchstart="event.stopPropagation()"
                  onchange="event.stopPropagation(); document.querySelector('home-assistant').hass.callService(
                    'input_select','select_option',
                    {entity_id:'${variables.selector}', option:this.value}
                  );">
                  ${optionHtml}
                </select>
              </div>


              <div class="style">${esc(beer.style || '–')}</div>


              <div class="facts">
                <div>
                  <ha-icon icon="mdi:water-outline"></ha-icon>
                  <strong>${beer.abv !== undefined && beer.abv !== null ? esc(String(beer.abv).replace('.', ',')) + '%' : '–'}</strong>
                  <span>ABV</span>
                </div>
                <div>
                  <ha-icon icon="mdi:barrel"></ha-icon>
                  <strong>${beer.batch_number ? '#' + esc(beer.batch_number) : '–'}</strong>
                  <span>BRYGG NR.</span>
                </div>
              </div>


              <div class="tap-date">
                <ha-icon icon="mdi:calendar"></ha-icon>
                <span>TAPPEDATO</span>
                <strong>${fmtDate(beer.packaging_date)}</strong>
              </div>


              <div class="tank-area">
                <div class="tank">
                  <div class="tank-cap"></div>
                  <div class="tank-window">
                    <div class="liquid" style="height:${percent}%; --beer:${beerColor}">
                      <div class="wave"></div>
                      <i class="bubble b1"></i><i class="bubble b2"></i>
                      <i class="bubble b3"></i><i class="bubble b4"></i>
                    </div>
                    <div class="tank-copy">
                      <span>LITER IGJEN</span>
                      <strong>${validLiters ? liters.toFixed(1).replace('.', ',') : '–'} <small>L</small></strong>
                      <em>${validLiters ? Math.round(percent) + '%' : '–'}</em>
                    </div>
                  </div>
                  <div class="tank-base"></div>
                </div>
              </div>


              <div class="progress-shell">
                <div class="progress-fill"
                  style="width:${percent}%; background:${progressColor}"></div>
                <b>${validLiters ? Math.round(percent) + '%' : '–'}</b>
              </div>
              <div class="progress-labels"><span>0%</span><span>50%</span><span>100%</span></div>
            </div>`;
        ]]]
    styles:
      card:
        - height: 100%
        - padding: 0
        - border-radius: 14px
        - overflow: hidden
        - background: linear-gradient(180deg,#171b1c,#0d1112)
        - border: 1px solid rgba(185,190,188,.36)
        - box-shadow: 0 10px 25px rgba(0,0,0,.45), inset 0 1px rgba(255,255,255,.04)
      grid:
        - height: 100%
        - grid-template-areas: '"content"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr
      custom_fields:
        content:
          - height: 100%
          - pointer-events: auto
          - position: relative
          - z-index: 10
    extra_styles: >
      * { box-sizing:border-box; }


      .keg-card { height:100%; display:grid; grid-template-rows:34px 45px 35px
      70px 54px minmax(0,1fr) 18px 16px; gap:4px; padding:6px 10px 7px;
      color:#f3f0e9; font-family:Arial,sans-serif; }


      .fat-title { display:grid; grid-template-columns:1fr auto 1fr;
      align-items:center; gap:10px; color:#dc9a30; font-size:21px;
      font-weight:800; letter-spacing:.07em; }


      .fat-title i { height:1px;
      background:linear-gradient(90deg,transparent,#c48929); }


      .fat-title i:last-child {
      background:linear-gradient(90deg,#c48929,transparent); }




      .selector-wrap { display:grid; grid-template-columns:33px minmax(0,1fr);
      align-items:center; border:1px solid #c88620; border-radius:11px;
      background:#080b0c; overflow:hidden; position:relative; z-index:20;
      pointer-events:auto !important; touch-action:manipulation; }


      .selector-wrap ha-icon { color:#dc981e; justify-self:center; width:23px;
      height:23px; }


      .beer-select { width:100%; height:43px; padding:0 34px 0 5px; border:0;
      outline:0; color:#f5f3ed; background:#080b0c;
      font-size:clamp(16px,2.0vw,25px); font-weight:650; text-align:center;
      position:relative; z-index:30; pointer-events:auto !important;
      touch-action:manipulation; cursor:pointer; -webkit-appearance:menulist;
      appearance:auto; }


      .beer-select option { color:#fff; background:#101314; }




      .style { display:flex; align-items:center; justify-content:center;
      color:#d99527; font-size:clamp(13px,1.45vw,19px); border-bottom:1px solid
      rgba(255,255,255,.17); white-space:nowrap; overflow:hidden;
      text-overflow:ellipsis; }




      .facts { display:grid; grid-template-columns:1fr 1fr; border-bottom:1px
      solid rgba(255,255,255,.17); }


      .facts > div { display:grid; grid-template-columns:34px 1fr;
      grid-template-rows:1fr 20px; grid-template-areas:'icon value' 'icon
      label'; align-items:center; padding:4px 10px; }


      .facts > div + div { border-left:1px solid rgba(255,255,255,.17); }


      .facts ha-icon { grid-area:icon; color:#d79525; width:28px; height:28px; }


      .facts strong { grid-area:value; font-size:clamp(18px,2.2vw,29px);
      font-weight:700; }


      .facts span { grid-area:label; color:#d79525; font-size:11px;
      letter-spacing:.12em; }




      .tap-date { display:grid; grid-template-columns:38px 1fr;
      grid-template-rows:19px 1fr; grid-template-areas:'icon label' 'icon
      value'; align-items:center; padding:3px 12%; border-bottom:1px solid
      rgba(255,255,255,.17); }


      .tap-date ha-icon { grid-area:icon; color:#d79525; width:27px;
      height:27px; }


      .tap-date span { grid-area:label; color:rgba(245,245,245,.6);
      font-size:10px; letter-spacing:.13em; }


      .tap-date strong { grid-area:value; font-size:clamp(15px,1.65vw,21px);
      font-weight:550; }




      .tank-area { min-height:0; display:flex; align-items:stretch;
      justify-content:center; padding-top:2px; }


      .tank { position:relative; width:96%; height:100%; min-height:210px;
      display:grid; grid-template-rows:18px minmax(0,1fr) 12px;
      filter:drop-shadow(0 10px 10px rgba(0,0,0,.48)); }


      .tank-cap,.tank-base { z-index:3; margin:0 6%; border:2px solid #858989;
      background:linear-gradient(90deg,#323536,#d1d2cf 20%,#55595a 45%,#e1e0da
      70%,#373a3b); }


      .tank-cap { border-radius:15px 15px 4px 4px; }


      .tank-base { border-radius:4px 4px 13px 13px; }


      .tank-window { position:relative; overflow:hidden; border:5px solid
      transparent; border-left-color:#7c8080; border-right-color:#7c8080;
      background:radial-gradient(circle at 50% 45%,#151a1b,#050708 72%);
      box-shadow:inset 0 0 0 2px #1d2021,inset 0 0 22px #000;
      border-radius:10px; }


      .liquid { position:absolute; left:0; right:0; bottom:0;
      background:linear-gradient(90deg,color-mix(in srgb,var(--beer),#fff
      14%),var(--beer) 45%,color-mix(in srgb,var(--beer),#000 30%));
      transition:height 1.2s ease; box-shadow:inset 14px 0 20px
      rgba(255,255,255,.10),inset -16px 0 24px rgba(0,0,0,.3); }


      .wave { position:absolute; top:-8px; left:-8%; width:116%; height:18px;
      border-radius:48%; background:color-mix(in srgb,var(--beer),#fff 38%);
      box-shadow:0 2px 7px rgba(255,255,255,.45); animation:slosh 4s ease-in-out
      infinite alternate; }


      @keyframes slosh { from{transform:translateX(-2%) rotate(-1deg)}
      to{transform:translateX(2%) rotate(1deg)} }


      .bubble { position:absolute; bottom:4%; border:1px solid
      rgba(255,255,255,.58); border-radius:50%; opacity:0; animation:rise 5s
      infinite linear; }


      .b1{width:7px;height:7px;left:17%;animation-delay:-1s}.b2{width:10px;height:10px;left:42%;animation-delay:-3s}.b3{width:5px;height:5px;left:68%;animation-delay:-2s}.b4{width:8px;height:8px;left:82%;animation-delay:-4s}


      @keyframes rise { 0%{transform:translateY(0);opacity:0} 15%{opacity:.55}
      100%{transform:translateY(-170px);opacity:0} }


      .tank-copy { position:absolute; z-index:4; inset:0; display:flex;
      flex-direction:column; align-items:center; justify-content:flex-start;
      padding-top:15px; text-shadow:0 2px 5px #000; pointer-events:none; }


      .tank-copy span { color:#d79525; font-size:11px; letter-spacing:.12em; }


      .tank-copy strong { margin-top:4px; font-size:clamp(28px,3.4vw,42px);
      line-height:1; }


      .tank-copy small { font-size:.48em; }


      .tank-copy em { color:#d79525; margin-top:4px; font-size:18px;
      font-style:normal; }




      .progress-shell { position:relative; height:18px; overflow:hidden;
      border:1px solid rgba(220,220,210,.7); border-radius:5px;
      background:#090c0d; }


      .progress-fill { height:100%; transition:width 1.2s ease,background .8s
      ease; }


      .progress-shell b { position:absolute; right:5px; top:1px; color:#fff;
      font-size:12px; text-shadow:0 1px 3px #000; }


      .progress-labels { display:flex; justify-content:space-between;
      color:#f4f3ee; font-size:10px; }
views:
  - title: Tappekraner
    path: tappekraner
    type: panel
    cards:
      - type: vertical-stack
        card_mod:
          style: |
            ha-card {
              height: 100vh;
              overflow: hidden;
              background: #050607;
              border-radius: 0;
              box-shadow: none;
            }
        cards:
          - type: picture
            image: /local/klubbane_banner_2020.png
            tap_action:
              action: none
            hold_action:
              action: none
            card_mod:
              style: >
                ha-card { margin:0; border-radius:0; box-shadow:none;
                background:#050607; }


                img { display:block; width:100%; height:116px; object-fit:cover;
                }
          - type: grid
            columns: 3
            square: false
            card_mod:
              style: |
                ha-card {
                  height: calc(100vh - 116px);
                  padding: 5px;
                  margin: 0;
                  background: #050607;
                  box-sizing: border-box;
                }
                #root {
                  height: 100%;
                  gap: 7px !important;
                  align-items: stretch;
                }
            cards:
              - type: custom:button-card
                template: klubbane_keg
                entity: input_select.keg_1_beer
                variables:
                  keg_number: 1
                  selector: input_select.keg_1_beer
                  liters: sensor.keg_1_fg_kompensert_ol_igjen
                  capacity: 19
              - type: custom:button-card
                template: klubbane_keg
                entity: input_select.keg_2_beer
                variables:
                  keg_number: 2
                  selector: input_select.keg_2_beer
                  liters: sensor.keg_2_fg_kompensert_ol_igjen
                  capacity: 19
              - type: custom:button-card
                template: klubbane_keg
                entity: input_select.keg_3_beer
                variables:
                  keg_number: 3
                  selector: input_select.keg_3_beer
                  liters: sensor.keg_3_fg_kompensert_ol_igjen
                  capacity: 19

For API se vedlagt .zip med følgende kommentar fra ChatGPT (jeg har ikke kvalitetssikret filen, men den bør være riktig

Ja — nå har jeg den komplette pakken. Jeg brukte brewfather_kegs_v1.0.4 du lastet opp sammen med den siste fungerende V5 Persistent Fix-package og V10.3 Normalized Names-dashboardet.


Jeg har også sanert pakken for privat informasjon og fjernet __pycache__ og bryggerispesifikk branding.


Last ned komplett Brewfather Kegs – siste fungerende, sanert ZIP


Den inneholder:


  • brewfather_kegs/ — komplett v1.0.4-integrasjon
  • persistent keg-package
  • V10.3-dashboard med normalisert ølnavnsoppslag
  • installasjonsveiledning
  • README

Jeg har gjort vekt-sensorene generiske (sensor.keg_monitor_keg_1_weight osv.), så de tre entity-ID-ene må eventuelt endres til dine faktiske sensorer. Selve Brewfather-integrasjonen bruker fortsatt eksisterende Brewfather-integrasjon/credentials, slik originalpakken gjorde.


Dette er dermed den pakken jeg ville brukt som ren distribusjons-/backupversjon.
 

Vedlegg

  • Brewfather_Kegs_latest_working_sanitized.zip
    16,2 KB · Sett: 1
Tilbake
Topp