Kegerator ultime
Composants et fournitures
| × | 7 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 3 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 |
Outils et machines nécessaires
| ||||
| ||||
| ||||
|
À propos de ce projet
Avertissement : Tout d'abord, ce projet ne promeut en aucun cas l'usage ou l'abus d'alcool, il appartient entièrement aux utilisateurs de déterminer quelles boissons composeront le contenu de ce kegerator.
Ce projet est né de la volonté de mieux gérer le contenu d'un kegerator. Un kegerator fonctionne sur le principe de base de garder une boisson froide ainsi que de maintenir les boissons gazeuses à un certain PSI. De plus, rien qu'en vous versant une boisson froide, vous n'avez aucune idée de la quantité qui reste dans le fût. Ce serait dommage d'avoir des gens pour un match de football le dimanche et de manquer de root beer à mi-parcours.
Les objectifs de ce projet sont donc :
- Maintenir une température constante des boissons, s'assurer que les boissons ne deviennent pas trop chaudes ou trop froides et congeler
- Assurez-vous qu'une quantité acceptable de gazéification est appliquée au fût pour maintenir une saveur optimale
- Gardez une trace de la quantité de boissons dans chaque fût et fournissez des commentaires visuels pour vous assurer que de nombreuses boissons sont disponibles pour le grand match.
- Gardez une trace de la quantité de CO2 restant dans le réservoir utilisé pour gazéifier les boissons
Les composants électroniques de base et leur utilisation :
- Un congélateur coffre est utilisé pour l'unité de refroidissement et pour fournir un cadre pour créer un joli meuble
- Raspberry PI 2 exécutant Windows 10 IoT core est utilisé comme cerveau de fonctionnement
- De petites balances postales sont utilisées pour mesurer le poids de chaque fût ainsi que le réservoir de CO2, ces balances postales ont l'électronique retirée et un amplificateur de cellule de charge et un petit Arduino intégré à la balance. Ces balances communiqueront avec le Raspberry PI 2 via I2C (plus à ce sujet plus tard)
- Il y a 5 capteurs de température numériques qui sont installés sur l'unité, un au bas du congélateur coffre, un attaché au dessous du dessus, un chacun installé dans les tours où se trouvent les poignées du robinet (plus de détails plus tard ) et un installé à l'extérieur de l'unité pour mesurer la température ambiante. Ces capteurs de température sont connectés à un petit Arduino et communiquent également avec le Raspberry PI 2 via I2C
- Un capteur de pression Honeywell est fixé aux conduites d'air utilisées pour fournir la carbonatation aux fûts. Bien que le réglage du PSI soit manuel (pour l'instant), cela fournira une mesure précise de la quantité de CO2 appliquée aux fûts.
- Une alimentation 5V est utilisée pour alimenter le Raspberry PI2. Une version plus grande (fournissant jusqu'à 6 ampères) a été choisie afin qu'elle puisse également alimenter une bande LED adressable.
- Un simple relais est placé en ligne sur l'alimentation du compresseur. En utilisant ce relais, l'alimentation peut être appliquée et retirée du compresseur, le compresseur contrôlera alors à son tour la température du kegerator (nous en parlerons plus tard)
Connectivité Cloud
L'Ultimate Kegerator contient un serveur Web pour permettre la configuration à distance via les services REST ainsi qu'une simple vue statique de l'état actuel. Ce site Web est accessible à l'adresse http://slsys.homeip.net:9501 .
De plus, Ultimate Kegerator télécharge ses statistiques vitales sur un hub d'événements Windows Azure. Vous ne pourrez pas utiliser le package Nuget standard pour parler au centre d'événements, mais vous disposez de la bibliothèque facile à mettre en œuvre fournie par un autre MVP Windows Embedded Paolo Patierno disponible sur
https://www.nuget.org/packages/AzureSBLite/
Pour un traitement ultime par Stream Analytics
Les plans éventuels pour Stream Analytics seraient de :
1) Surveillez et notifiez si les températures deviennent trop chaudes ou trop froides
2) Surveiller et notifier lorsque le réservoir de CO2 devient trop bas
3) Surveiller et notifier s'il y a une fuite détectée dans le réservoir de CO2 (diminution progressive du poids)
Voici quelques photos supplémentaires du processus d'assemblage :
-twb
Code
- Classe Keg
- Classe d'échelle
- Classe Kegerator
Classe KegC#
Aperçu du code source avant la publication du code source complet sur GitHub. Si vous souhaitez un accès anticipé ou si vous souhaitez contribuer, veuillez contacter l'auteur de ce projeten utilisant LagoVista.Common.Commanding;en utilisant System;en utilisant System.Collections.Generic;en utilisant System.Linq;en utilisant System.Text;en utilisant System .Threading.Tasks;utilisation de Windows.UI.Xaml;espace de noms 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; _échelle =échelle ; } 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 Propriétés calculées private int _qtyRemaining; public int QtyRemaining { get { return _qtyRemaining; } set { Set(ref _qtyRemaining, value); } } DateHeure privée ? _installDate ; DateHeure publique ? InstallDate { get { return _installDate; } set { Set(ref _installDate, value); } } private int _percentFull; public int PercentFull { get { return _percentFull; } set { Set(ref _percentFull, valeur); } } 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 Propriétés saisies private bool _isEmpty; public bool IsEmpty { get { return _isEmpty; } set { _isEmpty =valeur; RaisePropertyChanged(); } } double _glassSize privé; public double GlassSizeOz { get { return _glassSize; } set { Set(ref _glassSize, value); } } DateHeure privée ? _date de naissance; DateHeure publique ? 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); } } chaîne privée _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); sinon BornDate =null ; var installDate =GetSetting("KEG{0}_INSTALL_DATE", String.Empty); if (!String.IsNullOrEmpty(installDate)) InstallDate =DateTime.Parse(installDate); sinon Date d'installation =null ; } public asynchrone void SaveFullWeight() { FullContentsWeightLb =wait Scale.GetAverageWeight(); Sauvegarder(); } public RelayCommand SaveFullWeightCommand { get { return new RelayCommand(() => SaveFullWeight()); } } }}
Classe d'échelleC#
Aperçu du code source avant la publication du code source complet sur GitHub. Si vous souhaitez un accès anticipé ou si vous souhaitez contribuer, veuillez contacter l'auteur de ce projetClasse KegeratorC#
Aperçu du code source avant la publication du code source complet sur GitHub. Si vous souhaitez un accès anticipé ou si vous souhaitez contribuer, veuillez contacter l'auteur de ce projetà l'aide de LagoVista.Common.Commanding;à l'aide de System;à l'aide de System.Collections.Generic;à l'aide de System.Collections.ObjectModel;à l'aide de System.ComponentModel; utilisant System.Linq;utilisant System.Runtime.CompilerServices;utilisant System.Text;utilisant System.Threading.Tasks;utilisant Windows.Devices.Enumeration;utilisant Windows.Devices.I2c;espace de noms LagoVista.IoT.Common.Kegerator{ public class Kegerator :INotifyPropertyChanged { événement public PropertyChangedEventHandler PropertyChanged; modèles privés.Keg _keg1; modèles privés.Keg _keg2; modèles privés.Keg _keg3; modèles privés.Keg _keg4; privé CO2.CO2Tank _co2Tank; private Kegerator() { } public List_devices =new List (); private void RaisePropertyChanged([CallerMemberName] string propertyName =null) { var eventHandler =this.PropertyChanged; if (eventHandler !=null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } bool privé Set (ref T stockage, valeur T, chaîne columnName =null, [CallerMemberName] chaîne propertyName =null) { if (object.Equals(storage, value)) return false; stockage =valeur ; this.RaisePropertyChanged(propertyName); renvoie vrai ; } octet[] _scalesAddresses ={ 0x43, 0x41, 0x40, 0x42 } ; chaîne const privée I2C_CONTROLLER_NAME ="I2C1"; privé Thermo.Temperatures _temperatures; Thermo.Controller _tempController privé; échelles privées.Scale _co2Scale; Private Dictionary _kegScales; CO2.PressureSensor privé _pressureSensor ; privé LED.LEDManager _ledManager ; privé REST.KegeratorServices _kegServices; Private static Kegerator _kegerator =new Kegerator(); Instance de Kegerator statique publique { get { return _kegerator; } } privé 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); /* Recherche la chaîne de sélection pour le contrôleur de bus I2C */ var deviceInfo =(wait DeviceInformation.FindAllAsync(selector)).FirstOrDefault(); /* Trouvez le périphérique contrôleur de bus I2C avec notre chaîne de sélection */ var deviceId =deviceInfo ==null ? (chaîne)null :deviceInfo.Id; _temperatures =new Thermo.Temperatures (0x48); wait _temperatures.Init(deviceId); _devices.Add(_temperatures); _tempController =new Thermo.Controller(); _tempController.Init(_temperatures); _devices.Add(_tempController); _pressureSensor =new CO2.PressureSensor(); wait _pressureSensor.Init(deviceId, TimeSpan.FromSeconds(1)); _devices.Add(_pressureSensor); _co2Scale =new Scales.Scale(0x44); wait _co2Scale.Init(deviceId, TimeSpan.FromSeconds(1)); _devices.Add(_co2Scale); _co2Tank =nouveau CO2.CO2Tank(_co2Scale, TimeSpan.FromSeconds(2)); _co2Tank.Load(); _devices.Add(_co2Tank); _kegScales =new Dictionary (); _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]); wait scale.Init(deviceId, TimeSpan.FromMilliseconds(500)); _kegScales.Add(idx, échelle); _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 =nouveau 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(); } Pause; case "cal":{ var scaleIndex =Convert.ToInt32(parts[2]); _kegScales[scaleIndex].CalibrationWeight =Convert.ToDouble(parts[3]); _kegScales[scaleIndex].Calibrate(); } Pause; } } } 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 Températures { get { return _temperatures; } } public Thermo.Controller TemperatureController { get { return _tempController; } } chaîne privée _statusMessage; public String StatusMessage { get { return _statusMessage; } set { Set(ref _statusMessage, valeur); } } public List 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
High Level System Component DiagramProcessus de fabrication
- Évitement d'obstacles à l'aide de l'intelligence artificielle
- Gyroscope amusant avec l'anneau NeoPixel
- Contrôleur de jeu Arduino
- Costume nuage
- Suiveur de ligne industrielle pour la fourniture de matériaux
- Pixie :une montre-bracelet NeoPixel basée sur Arduino
- Bouteille d'eau alimentée par Arduino
- Théâtre d'ombres des Fêtes
- Machine d'électroérosion à fil ultime à Taiwan