Kegerator DIY ultime : Guide complet composants et construction
Composants et fournitures
| Composant | Quantité |
|---|---|
| SparkFun Arduino Pro Mini 328 - 3,3V/8MHz | 7 |
| Amplificateur cellule de charge SparkFun - HX711 | 1 |
| Tableau de distribution téléphonique Leviton 47603-12B 4x12 | 1 |
| Raspberry Pi 2 Model B | 1 |
| Alimentation commutée AC 100-120V/200-220V 5V 6A 30W | 1 |
| Capteur de mouvement IR | 1 |
| Congélateur coffre 10,6 pi³ (300 L) | 1 |
| Balance postale numérique Smart Weight | 5 |
| Clavier ultra-fin Rii K12 2,4 GHz | 1 |
| Dongle WiFi Raspberry Pi | 1 |
| Module relais DC 5V Arduino | 1 |
| Capteur température/humidité DHT11 (4 broches) | 2 |
| Capteur de température DHT22 | 3 |
| Tour à bière double inox HomeBrewStuff | 2 |
| Refroidisseur de tour à bière | td>1|
| Capteur de pression I2C 0-30 PSI (3,3V) ABPMANN030PG2A3 | 1 |
Outils et machines nécessaires
- Outils de menuiserie (pour personnaliser l'extérieur du congélateur)
- Coupe-fil
- Fer à souder (générique)
- Gaine thermorétractable
À propos du projet
Avertissement : Ce projet ne promeut en aucun cas l'usage ou l'abus d'alcool, le choix des boissons reste entièrement à la discrétion de l'utilisateur.
Ce projet est né du désir de mieux gérer le contenu d'un kegerator. Un kegerator fonctionne sur le principe de base de maintenir une boisson au frais tout en la maintenant carbonatée à une certaine pression (PSI). De plus, en se servant simplement un verre, on n'a aucune idée de la quantité restante dans le fût. Ce serait dommage d'avoir des invités pour un match de football le dimanche et de tomber à court de root beer (ou de bière) à la mi-temps.
Objectifs du projet :
- Maintenir une température constante des boissons, éviter qu'elles ne deviennent trop chaudes ou ne gèlent
- Assurer un niveau de carbonatation acceptable pour maintenir une saveur optimale
- Suivre la quantité de boisson dans chaque fût et fournir un retour visuel pour s'assurer qu'il y en a assez pour le grand match
- Surveiller la quantité de CO₂ restante dans la bouteille utilisée pour la carbonatation
Composants électroniques de base et leur utilisation :
- Un congélateur coffre sert d'unité de refroidissement et de structure pour créer un meuble esthétique
- Un Raspberry Pi 2 sous Windows 10 IoT Core fait office de cerveau du système
- De petites balances postales mesurent le poids de chaque fût ainsi que de la bouteille de CO₂. L'électronique de ces balances est retirée et remplacée par un amplificateur de cellule de charge (HX711) et un petit Arduino intégré. Ces balances communiquent avec le Raspberry Pi 2 via I2C
- Cinq capteurs de température numériques sont installés : un au fond du congélateur, un sous le couvercle, un dans chaque tour de tirage (là où sont les robinets) et un à l'extérieur pour mesurer la température ambiante. Ces capteurs sont connectés à un petit Arduino et communiquent aussi via I2C avec le Raspberry Pi 2
- Un capteur de pression Honeywell est raccordé aux conduites d'air qui carbonatent les fûts. Bien que le réglage du PSI soit manuel (pour l'instant), cela fournit une mesure précise de la pression de CO₂ appliquée
- Une alimentation 5V alimente le Raspberry Pi 2. Une version plus puissante (jusqu'à 6 A) a été choisie pour pouvoir aussi alimenter un ruban LED adressable
- Un relais simple est placé en série sur l'alimentation du compresseur. Ce relais permet d'activer ou couper l'alimentation du compresseur, qui contrôle alors la température du kegerator
Connectivité Cloud
L'Ultime Kegerator contient un serveur web permettant la configuration à distance via des services REST ainsi qu'une vue statique simple de l'état actuel. Ce site est accessible à https://slsys.homeip.net:9501.
De plus, le Kegerator envoie ses statistiques vitales vers un Windows Azure Event Hub. Vous ne pourrez pas utiliser le package NuGet standard pour communiquer avec l'Event Hub, mais la bibliothèque facile à implémenter fournie par le Windows Embedded MVP Paolo Patierno, disponible sur NuGet.

Pour un traitement ultérieur par Stream Analytics :

Projets futurs pour Stream Analytics :
- Surveiller et notifier si les températures deviennent trop élevées ou trop basses
- Surveiller et notifier lorsque la bouteille de CO₂ est trop basse
- Surveiller et notifier en cas de fuite détectée sur la bouteille de CO₂ (diminution progressive du poids)
Voici quelques photos supplémentaires du processus d'assemblage :











-twb
Code
Classe Keg (C#)
using LagoVista.Common.Commanding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
namespace LagoVista.IoT.Common.Kegerator.Models {
public class Keg : DeviceBase {
int _idx;
TimeSpan _updateInterval;
private Scales.Scale _scale;
public Keg(int idx, Scales.Scale scale, TimeSpan updateInterval) {
_idx = idx;
_updateInterval = UpdateInterval;
_scale = scale;
}
public override TimeSpan UpdateInterval {
get { return _updateInterval; }
}
public override void Refresh() {
LastUpdated = DateTime.Now;
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
ContentsWeight = Scale.Weight - ContainerWeightLb;
if (FullContentsWeightLb > 0)
PercentFull = Convert.ToInt32((ContentsWeight / (FullContentsWeightLb - ContainerWeightLb)) * 100);
else
PercentFull = 0;
PercentFull = Math.Min(PercentFull, 100);
if (GlassSizeOz > 0)
QtyRemaining = Convert.ToInt32((ContentsWeight * 16) / GlassSizeOz);
else
QtyRemaining = 0;
RaisePropertyChanged("PercentFullHeight");
RaisePropertyChanged("PercentFullDisplay");
});
}
public Scales.Scale Scale {
get { return _scale; }
}
#region Calculated Properties
private int _qtyRemaining;
public int QtyRemaining {
get { return _qtyRemaining; }
set { Set(ref _qtyRemaining, value); }
}
private DateTime? _installDate;
public DateTime? InstallDate {
get { return _installDate; }
set { Set(ref _installDate, value); }
}
private int _percentFull;
public int PercentFull {
get { return _percentFull; }
set { Set(ref _percentFull, value); }
}
public String PercentFullDisplay {
get { return String.Format("{0}%", Convert.ToInt32(PercentFull)); }
}
public double PercentFullHeight {
get { return Convert.ToDouble(_percentFull * 2); }
}
public int KegIndex {
get { return _idx; }
}
#endregion
#region Entered Properties
private bool _isEmpty;
public bool IsEmpty {
get { return _isEmpty; }
set { _isEmpty = value; RaisePropertyChanged(); }
}
private double _glassSize;
public double GlassSizeOz {
get { return _glassSize; }
set { Set(ref _glassSize, value); }
}
private DateTime? _bornDate;
public DateTime? BornDate {
get { return _bornDate; }
set { Set(ref _bornDate, value); }
}
double _containerWeight;
public double ContainerWeightLb {
get { return _containerWeight; }
set { Set(ref _containerWeight, value); }
}
double _contentsWeight;
public double ContentsWeight {
get { return _contentsWeight; }
set { Set(ref _contentsWeight, value); }
}
double _fullContentsWeight;
public double FullContentsWeightLb {
get { return _fullContentsWeight; }
set { Set(ref _fullContentsWeight, value); }
}
private String _contentsName;
public String ContentsName {
get { return _contentsName; }
set { Set(ref _contentsName, value); }
}
#endregion
public void Save() {
LagoVista.Common.PlatformSupport.Services.BindingHelper.RefreshBindings();
PutSetting(String.Format("KEG{0}_CONTENTS", _idx), ContentsName);
PutSetting(String.Format("KEG{0}_IS_EMPTY", _idx), IsEmpty.ToString());
PutSetting(String.Format("KEG{0}_CONTAINER_WEIGHT", _idx), String.Format("{0:0.00}", ContainerWeightLb));
PutSetting(String.Format("KEG{0}_GLASS_SIZE", _idx), String.Format("{0:0.00}", GlassSizeOz));
PutSetting(String.Format("KEG{0}_FULL_CONTENTS_WEIGHT", _idx), String.Format("{0:0.00}", FullContentsWeightLb));
if (BornDate.HasValue)
PutSetting(String.Format("KEG{0}_BORN_DATE", _idx), BornDate.Value.ToString());
else
RemoveSetting(String.Format("KEG{0}_BORN_DATE", _idx));
if (InstallDate.HasValue)
PutSetting(String.Format("KEG{0}_INSTALL_DATE", _idx), InstallDate.Value.ToString());
else
RemoveSetting(String.Format("KEG{0}_INSTALL_DATE", _idx));
}
public void Load() {
ContentsName = GetSetting(String.Format("KEG{0}_CONTENTS", _idx), "?");
ContainerWeightLb = Convert.ToDouble(GetSetting(String.Format("KEG{0}_CONTAINER_WEIGHT", _idx), "10.0"));
GlassSizeOz = Convert.ToDouble(GetSetting(String.Format("KEG{0}_GLASS_SIZE", _idx), "12.0"));
FullContentsWeightLb = Convert.ToDouble(GetSetting(String.Format("KEG{0}_FULL_CONTENTS_WEIGHT", _idx), "0.0"));
IsEmpty = Convert.ToBoolean(GetSetting(String.Format("KEG{0}_IS_EMPTY", _idx), "True"));
var bornDate = GetSetting("KEG{0}_BORN_DATE", String.Empty);
if (!String.IsNullOrEmpty(bornDate))
BornDate = DateTime.Parse(bornDate);
else
BornDate = null;
var installDate = GetSetting("KEG{0}_INSTALL_DATE", String.Empty);
if (!String.IsNullOrEmpty(installDate))
InstallDate = DateTime.Parse(installDate);
else
InstallDate = null;
}
public async void SaveFullWeight() {
FullContentsWeightLb = await Scale.GetAverageWeight();
Save();
}
public RelayCommand SaveFullWeightCommand {
get { return new RelayCommand(() => SaveFullWeight()); }
}
}
}
Classe Scale (C#)
using LagoVista.Common.Commanding;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.I2c;
namespace LagoVista.IoT.Common.Kegerator.Scales {
public class Scale : DeviceBase {
Windows.Devices.I2c.I2cDevice _scaleI2CChannel;
int _countOffset;
double? _calibrationFactor = null;
private TimeSpan _updateInterval;
byte _address;
public Scale(byte address) {
_address = address;
}
private void WriteValue(byte address, int value) {
if (!IsDemoMode) {
var offsetBuffer = new byte[5];
offsetBuffer[0] = address;
offsetBuffer[1] = (byte)(value >> 24);
offsetBuffer[2] = (byte)(value >> 16);
offsetBuffer[3] = (byte)(value >> 8);
offsetBuffer[4] = (byte)(value);
_scaleI2CChannel.Write(offsetBuffer);
}
}
public async Task Init(String i2cDeviceId, TimeSpan updateInterval) {
var settings = new I2cConnectionSettings(_address) {
BusSpeed = I2cBusSpeed.StandardMode,
SharingMode = I2cSharingMode.Shared
};
_updateInterval = updateInterval;
IsDemoMode = String.IsNullOrEmpty(i2cDeviceId);
if (!IsDemoMode) {
_scaleI2CChannel = await Windows.Devices.I2c.I2cDevice.FromIdAsync(i2cDeviceId, settings);
if (Windows.Storage.ApplicationData.Current.LocalSettings.Values.ContainsKey(String.Format("{0:X}.OFFSET", _address))) {
_countOffset = Convert.ToInt32(Windows.Storage.ApplicationData.Current.LocalSettings.Values[String.Format("{0:X}.OFFSET", _address)]);
try {
WriteValue((byte)'O', _countOffset);
} catch (Exception ex) {
Debug.WriteLine("Scale offline ");
}
}
if (Windows.Storage.ApplicationData.Current.LocalSettings.Values.ContainsKey(String.Format("{0:X}.CALIBRATION", _address))) {
_calibrationFactor = Convert.ToDouble(Windows.Storage.ApplicationData.Current.LocalSettings.Values[String.Format("{0:X}.CALIBRATION", _address)]);
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
Status = "Ready";
});
}
} else {
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
Status = "Ready";
});
}
}
int? _lastRaw = null;
private int GetRaw() {
try {
var inbuffer = new byte[4];
_scaleI2CChannel.Write(new byte[] { (byte)0x11 });
_scaleI2CChannel.Read(inbuffer);
var thisRaw = (int)(inbuffer[0] << 24 | inbuffer[1] << 16 | inbuffer[2] << 8 | inbuffer[3]);
if (_lastRaw.HasValue) {
if (Math.Abs(_lastRaw.Value - thisRaw) > 0xFFFF)
return _lastRaw.Value;
} else
_lastRaw = thisRaw;
return thisRaw;
} catch (Exception) {
return -1;
}
}
public override void Refresh() {
LastUpdated = DateTime.Now;
int rawResult = 0;
var isOnline = true;
try {
var inbuffer = new byte[4];
var statusBuffer = new byte[1];
if (!IsDemoMode) {
_scaleI2CChannel.Write(new byte[] { (byte)0x0A });
_scaleI2CChannel.Read(statusBuffer);
rawResult = GetRaw();
}
if (_calibrationFactor.HasValue) {
Weight = (rawResult - _countOffset) * _calibrationFactor.Value;
Debug.WriteLine(String.Format("0x{0:X} WEIGHT VALUE => {1:0.00} lbs", _address, Weight));
} else if (_countOffset > 0)
Debug.WriteLine(String.Format("0x{0:X} ZEROED VALUE => {1}", _address, rawResult - _countOffset));
else
Debug.WriteLine(String.Format("0x{0:X} RAW VALUE => 0x{1:X}", _address, rawResult));
} catch (Exception ex) {
rawResult = -1;
isOnline = false;
Debug.WriteLine(ex.Message);
}
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
Raw = rawResult;
IsOnline = isOnline;
if (!IsOnline) {
Status = "Offline";
WeightDisplay = "?";
} else {
if (_calibrationFactor.HasValue) {
Status = "Ready";
WeightDisplay = String.Format("{0}lb {1:00}oz", Math.Truncate(Weight), ((Weight % 1.0) * 16.0));
} else {
WeightDisplay = "?";
Status = "Not Calibrated";
}
}
RaisePropertyChanged("LastUpdateDisplay");
});
}
const int CALIBRATION_COUNT = 10;
public async void StoreOffset() {
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
Status = "Zeroing";
});
Debug.WriteLine("Staritng Zero Process");
long zeroSum = 0;
for (var idx = 0; idx < CALIBRATION_COUNT; ++idx) {
await Task.Delay(250);
zeroSum += GetRaw();
}
_countOffset = (int)(zeroSum / CALIBRATION_COUNT);
WriteValue((byte)'O', _countOffset);
Debug.WriteLine(String.Format("Finished Zero Process {0:X}", _countOffset));
if (Windows.Storage.ApplicationData.Current.LocalSettings.Values.ContainsKey(String.Format("{0:X}.OFFSET", _address)))
Windows.Storage.ApplicationData.Current.LocalSettings.Values.Remove(String.Format("{0:X}.OFFSET", _address));
Windows.Storage.ApplicationData.Current.LocalSettings.Values.Add(String.Format("{0:X}.OFFSET", _address), _countOffset);
Debug.WriteLine("Finished Zero Process");
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
Status = "Zeroed";
});
}
public async void Calibrate() {
Status = "Calibrating";
LagoVista.Common.PlatformSupport.Services.BindingHelper.RefreshBindings();
long countSum = 0;
for (var idx = 0; idx < CALIBRATION_COUNT; ++idx) {
await Task.Delay(250);
countSum += GetRaw() - _countOffset;
}
_calibrationFactor = CalibrationWeight / (countSum / CALIBRATION_COUNT);
if (Windows.Storage.ApplicationData.Current.LocalSettings.Values.ContainsKey(String.Format("{0:X}.CALIBRATION", _address)))
Windows.Storage.ApplicationData.Current.LocalSettings.Values.Remove(String.Format("{0:X}.CALIBRATION", _address));
Windows.Storage.ApplicationData.Current.LocalSettings.Values[String.Format("{0:X}.CALIBRATION", _address)] = _calibrationFactor;
}
public async Task<double> GetAverageWeight(int pointCount = 5) {
var weightSum = 0.0;
for (var idx = 0; idx < pointCount; ++idx) {
weightSum += Weight;
await Task.Delay(UpdateInterval);
}
return weightSum / pointCount;
}
private int _fullRaw;
public int FullRaw {
get { return _fullRaw; }
set { Set(ref _fullRaw, value); }
}
private double _weight;
public double Weight {
get { return _weight; }
set { _weight = value; }
}
private String _weightDisplay;
public String WeightDisplay {
get { return _weightDisplay; }
set { Set(ref _weightDisplay, value); }
}
private int _raw;
public int Raw {
get { return _raw; }
set { RaisePropertyChanged("RawDisplay"); Set(ref _raw, value); }
}
public String AddressDisplay {
get { return String.Format("0x{0:X}", _address); }
}
public String RawDisplay {
get { return String.Format("{0:X}", Raw); }
}
private int _tareRaw;
public int TareRaw {
get { return _tareRaw; }
set { Set(ref _tareRaw, value); }
}
private int _beerCount;
public int BeerCount {
get { return _beerCount; }
set { Set(ref _beerCount, value); }
}
private double _percentFull;
public double PercentFull {
get { return _percentFull; }
set { Set(ref _percentFull, value); }
}
public override TimeSpan UpdateInterval {
get { return _updateInterval; }
}
private double _calibrationWeight;
public double CalibrationWeight {
get { return _calibrationWeight; }
set { _calibrationWeight = value; }
}
public RelayCommand ZeroCommand {
get { return new RelayCommand(() => StoreOffset()); }
}
public RelayCommand CalibrationCommand {
get { return new RelayCommand(() => Calibrate()); }
}
}
}
Classe Kegerator (C#)
using LagoVista.Common.Commanding;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.I2c;
namespace LagoVista.IoT.Common.Kegerator {
public class Kegerator : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private Models.Keg _keg1;
private Models.Keg _keg2;
private Models.Keg _keg3;
private Models.Keg _keg4;
private CO2.CO2Tank _co2Tank;
private Kegerator() { }
public List<DeviceBase> _devices = new List<DeviceBase>();
private void RaisePropertyChanged([CallerMemberName] string propertyName = null) {
var eventHandler = this.PropertyChanged;
if (eventHandler != null) {
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool Set<T>(ref T storage, T value, string columnName = null, [CallerMemberName] string propertyName = null) {
if (object.Equals(storage, value)) return false;
storage = value;
this.RaisePropertyChanged(propertyName);
return true;
}
byte[] _scalesAddresses = { 0x43, 0x41, 0x40, 0x42 };
private const string I2C_CONTROLLER_NAME = "I2C1";
private Thermo.Temperatures _temperatures;
private Thermo.Controller _tempController;
private Scales.Scale _co2Scale;
private Dictionary<int, Scales.Scale> _kegScales;
private CO2.PressureSensor _pressureSensor;
private LED.LEDManager _ledManager;
private REST.KegeratorServices _kegServices;
private static Kegerator _kegerator = new Kegerator();
public static Kegerator Instance {
get { return _kegerator; }
}
private CloudServices.EventHubClient _eventHubClient;
System.Threading.Timer _timer;
private bool _initialized = false;
public async Task Init() {
if (!_initialized) {
_initialized = true;
var selector = I2cDevice.GetDeviceSelector(I2C_CONTROLLER_NAME);
var deviceInfo = (await DeviceInformation.FindAllAsync(selector)).FirstOrDefault();
var deviceId = deviceInfo == null ? (string)null : deviceInfo.Id;
_temperatures = new Thermo.Temperatures(0x48);
await _temperatures.Init(deviceId);
_devices.Add(_temperatures);
_tempController = new Thermo.Controller();
_tempController.Init(_temperatures);
_devices.Add(_tempController);
_pressureSensor = new CO2.PressureSensor();
await _pressureSensor.Init(deviceId, TimeSpan.FromSeconds(1));
_devices.Add(_pressureSensor);
_co2Scale = new Scales.Scale(0x44);
await _co2Scale.Init(deviceId, TimeSpan.FromSeconds(1));
_devices.Add(_co2Scale);
_co2Tank = new CO2.CO2Tank(_co2Scale, TimeSpan.FromSeconds(2));
_co2Tank.Load();
_devices.Add(_co2Tank);
_kegScales = new Dictionary<int, Scales.Scale>();
_eventHubClient = new CloudServices.EventHubClient(this, TimeSpan.FromSeconds(2));
_devices.Add(_eventHubClient);
for (var idx = 0; idx < 4; ++idx) {
var scale = new Scales.Scale(_scalesAddresses[idx]);
await scale.Init(deviceId, TimeSpan.FromMilliseconds(500));
_kegScales.Add(idx, scale);
_devices.Add(scale);
}
_keg1 = new Models.Keg(1, _kegScales[0], TimeSpan.FromMilliseconds(500));
_keg1.Load();
_devices.Add(_keg1);
_keg2 = new Models.Keg(2, _kegScales[1], TimeSpan.FromMilliseconds(500));
_keg2.Load();
_devices.Add(_keg2);
_keg3 = new Models.Keg(3, _kegScales[2], TimeSpan.FromMilliseconds(500));
_keg3.Load();
_devices.Add(_keg3);
_keg4 = new Models.Keg(4, _kegScales[3], TimeSpan.FromMilliseconds(500));
_keg4.Load();
_devices.Add(_keg4);
DateInitialized = DateTime.Now.ToString();
Web.WebServer.Instance.StartServer();
_kegServices = new REST.KegeratorServices() { Port = 9500 };
_kegServices.EventContent += _kegServices_EventContent;
_kegServices.StartServer();
_timer = new System.Threading.Timer((state) => {
Refresh();
}, null, 0, 250);
}
}
private void _kegServices_EventContent(object sender, string e) {
var parts = e.Split('/');
if (parts.Count() > 0) {
switch (parts[1]) {
case "zero": {
var scaleIndex = Convert.ToInt32(parts[2]);
_kegScales[scaleIndex].StoreOffset();
} break;
case "cal": {
var scaleIndex = Convert.ToInt32(parts[2]);
_kegScales[scaleIndex].CalibrationWeight = Convert.ToDouble(parts[3]);
_kegScales[scaleIndex].Calibrate();
} break;
}
}
}
public void Refresh() {
foreach (var device in _devices) {
if (DateTime.Now > (device.LastUpdated + device.UpdateInterval))
device.Refresh();
}
LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => {
CurrentTimeDisplay = DateTime.Now.ToString();
RaisePropertyChanged("CurrentTimeDisplay");
});
}
public Thermo.Temperatures Temperatures {
get { return _temperatures; }
}
public Thermo.Controller TemperatureController {
get { return _tempController; }
}
private String _statusMessage;
public String StatusMessage {
get { return _statusMessage; }
set { Set(ref _statusMessage, value); }
}
public List<Scales.Scale> KegScales {
get { return _kegScales.Values.ToList(); }
}
public void ToggleCompressor() {
if (_tempController.IsCompressorOn)
_tempController.CompressorOff();
else
_tempController.CompressorOn();
}
public String DateInitialized { get; set; }
public String CurrentTimeDisplay { get; set; }
public Scales.Scale CO2Scale {
get { return _co2Scale; }
}
public CO2.PressureSensor PressureSensor {
get { return _pressureSensor; }
}
public Models.Keg Keg1 {
get { return _keg1; }
}
public Models.Keg Keg2 {
get { return _keg2; }
}
public Models.Keg Keg3 {
get { return _keg3; }
}
public Models.Keg Keg4 {
get { return _keg4; }
}
public CO2.CO2Tank CO2Tank {
get { return _co2Tank; }
}
public RelayCommand ToggleCompressorCommand {
get { return new RelayCommand(ToggleCompressor); }
}
}
}
Schémas
Diagramme de composants système de haut niveau
Processus de fabrication
- Système d'Évitement d'Obstacles par Intelligence Artificielle (Q-Learning) sur Arduino
- Affichage LED Interactif Gyroscopique avec Anneau NeoPixel et Arduino
- Manette de jeu USB Arduino DIY – Construisez votre propre gamepad haute performance
- Costume nuage DIY : Bandeau LED, Arduino Nano et pièces SparkFun
- Robot suiveur de ligne industriel pour transport de matériaux
- Pixie : Montre-bracelet NeoPixel à base d'Arduino – Élégante, DIY, montre LED
- Bouteille d'eau intelligente Arduino : Suivi temps réel et alertes
- Théâtre d'Ombres DIY : Créez un Spectacle Lumineux avec Arduino et Bandeau LED Adressable
- Machines d'électroérosion à fil (EDM) haut de gamme : solutions de coupe de précision depuis Taïwan