Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Manufacturing Technology >> Processus de fabrication

Kegerator ultime

Composants et fournitures

SparkFun Arduino Pro Mini 328 - 3.3V/8MHz
× 7
Amplificateur de cellule de charge SparkFun - HX711
× 1
Tableau de distribution téléphonique Leviton 47603-12B 4x12
× 1
Raspberry Pi 2 Modèle B
× 1
Alimentation Swich AC 100-120V 200-220V 5V 6A 30W
× 1
Capteur de mouvement IR
× 1
10.6 cu. pi. Congélateur coffre
× 1
Pèse-personne numérique Smart Weight
× 5
Clavier Rii K12 Ultra Slim 2,4 GHz
× 1
Dongle WiFi Rasbperry Pi
× 1
Module de relais Arduino DC 5V
× 1
Capteur de température et d'humidité DHT11 (4 broches)
× 2
Capteur de température DHT22
× 3
Tour à bière double pression HomeBrewStuff en acier inoxydable
× 2
Refroidisseur de tour à bière
× 1
Capteur de pression 0-30 PSI I2C (3.3V) ABPMANN030PG2A3
× 1

Outils et machines nécessaires

Outils de travail du bois
Outils pour personnaliser l'extérieur du congélateur
Coupe-fil
Fer à souder (générique)
Tube thermorétractable

À 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 :

  1. Maintenir une température constante des boissons, s'assurer que les boissons ne deviennent pas trop chaudes ou trop froides et congeler
  2. Assurez-vous qu'une quantité acceptable de gazéification est appliquée au fût pour maintenir une saveur optimale
  3. 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.
  4. 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 :

  1. Un congélateur coffre est utilisé pour l'unité de refroidissement et pour fournir un cadre pour créer un joli meuble
  2. Raspberry PI 2 exécutant Windows 10 IoT core est utilisé comme cerveau de fonctionnement
  3. 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)
  4. 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
  5. 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.
  6. 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.
  7. 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 projet
en 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 projeten utilisant LagoVista.Common.Commanding;en utilisant System;en utilisant System.Collections.Generic;en utilisant System.Diagnostics;en utilisant System.Linq;en utilisant System .Text;à l'aide de System.Threading.Tasks;à l'aide de Windows.Devices.I2c;espace de noms LagoVista.IoT.Common.Kegerator.Scales{ public class Scale :DeviceBase { Windows.Devices.I2c.I2cDevice _scaleI2CChannel; int _countOffset; double? _calibrationFactor =null; privé TimeSpan _updateInterval; octet _adresse ; public Scale (adresse d'octet) { _address =adresse ; } private void WriteValue(adresse d'octet, valeur int) { if (!IsDemoMode) { var offsetBuffer =new byte[5]; offsetBuffer[0] =adresse ; offsetBuffer[1] =(octet)(valeur>> 24); offsetBuffer[2] =(octet)(valeur>> 16); offsetBuffer[3] =(octet)(valeur>> 8); offsetBuffer[4] =(octet)(valeur); _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 =wait Windows.Devices.I2c.I2cDevice.FromIdAsync(i2cDeviceId, paramètres); 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)]); essayez { 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 ="Prêt"; }); } } else { LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => { Status ="Ready"; }); } } entier ? _lastRaw =null ; private int GetRaw() { try { var inbuffer =new byte[4]; _scaleI2CChannel.Write(new byte[] { (byte)0x11 }); _scaleI2CChannel.Read(inbuffer); /* Remarque sur l'échelle, c'est un long (64 bits) ici c'est un int (64 bits) */ 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; retourner thisRaw ; } catch (Exception) { return -1; } } public override void Refresh() { LastUpdated =DateTime.Now; int rawResult =0; var est en ligne =vrai ; try { var inbuffer =new byte[4]; var statusBuffer =nouvel octet[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} VALEUR DE POIDS => {1:0.00} lb", _address, Weight)); } else if (_countOffset> 0) Debug.WriteLine(String.Format("0x{0:X} VALEUR ZÉRO => {1}", _address, rawResult - _countOffset)); else Debug.WriteLine(String.Format("0x{0:X} VALEUR RAW => 0x{1:X}", _address, rawResult)); } catch (Exception ex) { rawResult =-1; est en ligne =faux ; 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 ="Non calibré"; } } RaisePropertyChanged("LastUpdateDisplay"); }); } const int CALIBRATION_COUNT =10; public async void StoreOffset() { LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke(() => { Status ="Zeroing"; }); Debug.WriteLine("Démarrage du processus zéro"); long zeroSum =0; for (var idx =0; idx { Status ="Zeroed"; }); } public async void Calibrate() { Status ="Calibration"; LagoVista.Common.PlatformSupport.Services.BindingHelper.RefreshBindings(); long countSum =0 ; for (var idx =0; idx GetAverageWeight(int pointCount =5) { var weightSum =0.0; for(var idx =0; idx StoreOffset()); } } public RelayCommand CalibrationCommand { get { return new RelayCommand(() => Calibrate()); } } }}
Classe 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 Diagram

Processus de fabrication

  1. Évitement d'obstacles à l'aide de l'intelligence artificielle
  2. Gyroscope amusant avec l'anneau NeoPixel
  3. Contrôleur de jeu Arduino
  4. Costume nuage
  5. Suiveur de ligne industrielle pour la fourniture de matériaux
  6. Pixie :une montre-bracelet NeoPixel basée sur Arduino
  7. Bouteille d'eau alimentée par Arduino
  8. Théâtre d'ombres des Fêtes
  9. Machine d'électroérosion à fil ultime à Taiwan