Kegerator definitivo
Componentes y suministros
| × | 7 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 3 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 |
Herramientas y máquinas necesarias
| ||||
| ||||
| ||||
|
Acerca de este proyecto
Renuncia de responsabilidad: En primer lugar, este proyecto de ninguna manera promueve el uso o mal uso del alcohol, depende completamente de los usuarios qué bebidas compondrán el contenido de este kegerator.
Este proyecto nació del deseo de gestionar mejor los contenidos de un kegerator. Un barril de cerveza trabaja con el principio básico de mantener fría una bebida, así como mantener las bebidas carbonatadas a un cierto PSI. Además, con solo servirse una bebida fría, no tiene idea de cuánto queda en el barril. Sería una lástima tener gente para un partido de fútbol dominical y quedarse sin cerveza de raíz a la mitad del juego.
Entonces, los objetivos de este proyecto son:
- Mantenga una temperatura constante de las bebidas, asegúrese de que las bebidas no se calienten o enfríen demasiado y se congelen
- Asegúrese de que se aplique una cantidad aceptable de carbonatación al barril para mantener un sabor óptimo
- Lleve un registro de la cantidad de bebidas en cada barril y brinde información visual para asegurarse de que haya muchas bebidas disponibles para el gran juego.
- Lleve un registro de la cantidad de CO2 que queda en el tanque utilizado para carbonatar las bebidas
Los componentes electrónicos básicos y su uso:
- Se utiliza un congelador horizontal para la unidad de enfriamiento y para proporcionar un marco para crear un bonito mueble
- Raspberry PI 2 con Windows 10 IoT core se utiliza como el cerebro de la operación
- Se utilizan balanzas de franqueo pequeñas para medir el peso de cada barril, así como el tanque de CO2, estas balanzas de franqueo tienen la electrónica removida y un amplificador de celda de carga y un pequeño Arduino incorporado a la balanza. Estas básculas se comunicarán con Raspberry PI 2 a través de I2C (más sobre esto más adelante)
- Hay 5 sensores de temperatura digitales que están instalados en la unidad, uno en la parte inferior del congelador horizontal, uno en la parte inferior de la parte superior, uno instalado en las torres donde están las manijas del grifo (más sobre esto más adelante ) y uno instalado en el exterior de la unidad para medir la temperatura ambiente. Estos sensores de temperatura están conectados a un pequeño Arduino y también se comunican con la Raspberry PI 2 a través de I2C
- Un sensor de presión Honeywell está conectado a las líneas de aire que se utilizan para proporcionar carbonatación a los barriles. Aunque el ajuste de PSI es manual (por ahora), esto proporcionará un indicador preciso de la cantidad de CO2 que se aplica a los barriles.
- Se utiliza una fuente de alimentación de 5 V para proporcionar energía a la Raspberry PI2. Se eligió una versión más grande (que proporciona hasta 6 amperios) para que también pueda alimentar una tira de LED direccionable.
- Se coloca un relé simple en línea con la alimentación del compresor. Usando este relé, la energía se puede aplicar y quitar del compresor, el compresor a su vez controlará la temperatura del kegerator (más sobre esto más adelante)
Conectividad en la nube
El Ultimate Kegerator contiene un servidor web que permite la configuración remota a través de servicios REST, así como una vista estática simple del estado actual. Se puede acceder a este sitio web en http://slsys.homeip.net:9501.
Además, Ultimate Kegerator carga sus estadísticas vitales en un centro de eventos de Windows Azure. No podrá usar el paquete estándar de Nuget para hablar con el centro de eventos, sin embargo, tiene la biblioteca fácil de implementar proporcionada por su compañero de Windows Embedded MVP Paolo Patierno disponible en
https://www.nuget.org/packages/AzureSBLite/
Para un procesamiento definitivo por Stream Analytics
Los planes eventuales para Stream Analytics serían:
1) Supervise y notifique si las temperaturas son demasiado cálidas o demasiado frías
2) Supervise y notifique cuando el tanque de CO2 esté demasiado bajo
3) Monitorear y notificar si se detecta una fuga en el tanque de CO2 (disminución gradual de peso)
Aquí hay algunas imágenes adicionales del proceso de ensamblaje:
-twb
Código
- Clase de barril
- Clase de escala
- Clase de Kegerator
Clase de barril C #
Vista previa del código fuente antes de que se publique la fuente completa en GitHub. Si desea acceso anticipado o desea ayudar a contribuir, comuníquese con el autor de este proyectousando LagoVista.Common.Commands; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System .Threading.Tasks; usando Windows.UI.Xaml; espacio de nombres LagoVista.IoT.Common.Kegerator.Models {Barril de clase pública:DeviceBase {int _idx; TimeSpan _updateInterval; Private Scales.Scale _scale; Barril público (int idx, Scales.Scale scale, TimeSpan updateInterval) {_idx =idx; _updateInterval =UpdateInterval; _escala =escala; } Public override TimeSpan UpdateInterval {get {return _updateInterval; }} anulación pública void Refresh () {LastUpdated =DateTime.Now; LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {ContentsWeight =Scale.Weight - ContainerWeightLb; if (FullContentsWeightLb> 0) PercentFull =Convert.ToInt32 ((ContentsWeight / (FullContentsWeightLb - ContainerWeightLb)) * else PercentFull =0; PercentFull =Math.Min (PercentFull, 100); if (GlassSizeOz> 0) QtyRemaining =Convert.ToInt32 ((ContentsWeight * 16) / GlassSizeOz); else QtyRemaining =0; RaisePropertyChanged ("PercentChangedHeight"); RaiseProperty ("PercentFullDisplay");}); } escalas públicas. Escala Escala {get {return _scale; }} #region Propiedades calculadas private int _qtyRemaining; public int QtyRemaining {get {return _qtyRemaining; } set {Set (ref _qtyRemaining, value); }} fecha y hora privada? _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 Propiedades ingresadas private bool _isEmpty; public bool IsEmpty {get {return _isEmpty; } establecer {_isEmpty =valor; RaisePropertyChanged (); }} private double _glassSize; public double GlassSizeOz {get {return _glassSize; } set {Set (ref _glassSize, value); }} fecha y hora privada? _fecha de nacimiento; 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 ()); más RemoveSetting (String.Format ("KEG {0} _BORN_DATE", _idx)); if (InstallDate.HasValue) PutSetting (String.Format ("KEG {0} _INSTALL_DATE", _idx), InstallDate.Value.ToString ()); más 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); más BornDate =null; var installDate =GetSetting ("KEG {0} _INSTALL_DATE", String.Empty); if (! String.IsNullOrEmpty (installDate)) InstallDate =DateTime.Parse (installDate); más InstallDate =null; } public async void SaveFullWeight () {FullContentsWeightLb =await Scale.GetAverageWeight (); Ahorrar(); } Public RelayCommand SaveFullWeightCommand {get {return new RelayCommand (() => SaveFullWeight ()); }}}}
Clase de escala C #
Vista previa del código fuente antes de que se publique la fuente completa en GitHub. Si desea acceso anticipado o desea ayudar a contribuir, comuníquese con el autor de este proyectousando LagoVista.Common.Commands; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System .Text; usando System.Threading.Tasks; usando Windows.Devices.I2c; espacio de nombres LagoVista.IoT.Common.Kegerator.Scales {escala de clase pública:DeviceBase {Windows.Devices.I2c.I2cDevice _scaleI2CChannel; int _countOffset; ¿doble? _calibrationFactor =null; Private TimeSpan _updateInterval; byte _address; Public Scale (dirección de bytes) {_address =address; } private void WriteValue (dirección de byte, valor int) {if (! IsDemoMode) {var offsetBuffer =new byte [5]; offsetBuffer [0] =dirección; offsetBuffer [1] =(byte) (valor>> 24); offsetBuffer [2] =(byte) (valor>> 16); offsetBuffer [3] =(byte) (valor>> 8); offsetBuffer [4] =(byte) (valor); _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, configuración); 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)]); intente {WriteValue ((byte) 'O', _countOffset); } catch (Exception ex) {Debug.WriteLine ("Escalar sin conexión"); }} 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 (() => {Estado ="Listo";}); }} else {LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {Status ="Ready";}); } } ¿En t? _lastRaw =null; private int GetRaw () {try {var inbuffer =new byte [4]; _scaleI2CChannel.Write (nuevo byte [] {(byte) 0x11}); _scaleI2CChannel.Read (inbuffer); / * Nota en la escala, esto es largo (64 bits) aquí es 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; return thisRaw; } captura (excepción) {retorno -1; }} anulación pública void Refresh () {LastUpdated =DateTime.Now; int rawResult =0; var isOnline =true; intente {var inbuffer =new byte [4]; var statusBuffer =nuevo byte [1]; if (! IsDemoMode) {_scaleI2CChannel.Write (nuevo byte [] {(byte) 0x0A}); _scaleI2CChannel.Read (statusBuffer); rawResult =GetRaw (); } if (_calibrationFactor.HasValue) {Weight =(rawResult - _countOffset) * _calibrationFactor.Value; Debug.WriteLine (String.Format ("0x {0:X} VALOR DE PESO => {1:0.00} lbs", _address, Weight)); } else if (_countOffset> 0) Debug.WriteLine (String.Format ("0x {0:X} VALOR CERO => {1}", _address, rawResult - _countOffset)); más Debug.WriteLine (String.Format ("0x {0:X} VALOR SIN PROCESAR => 0x {1:X}", _address, rawResult)); } captura (Excepción ex) {rawResult =-1; isOnline =falso; Debug.WriteLine (por ejemplo, mensaje); } 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 ="?"; Estado ="No calibrado";}} RaisePropertyChanged ("LastUpdateDisplay");}); } const int CALIBRATION_COUNT =10; public async void StoreOffset () {LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {Status ="Zeroing";}); Debug.WriteLine ("Proceso cero de Staritng"); long zeroSum =0; for (var idx =0; idx{Status ="Zeroed";}); } public async void Calibrate () {Estado ="Calibrando"; LagoVista.Common.PlatformSupport.Services.BindingHelper.RefreshBindings (); long countSum =0; for (var idx =0; idx GetAverageWeight (int pointCount =5) {var weightSum =0.0; para (var idx =0; idx StoreOffset ()); }} Public RelayCommand CalibrationCommand {get {return new RelayCommand (() => Calibrate ()); }}}}
Clase Kegerator C #
Vista previa del código fuente antes de que se publique la fuente completa en GitHub. Si desea acceso anticipado o desea ayudar a contribuir, comuníquese con el autor de este proyectousando LagoVista.Common.Commands; using System; using System.Collections.ObjectModel; using System.Collections.ObjectModel; using System.ComponentModel; usando System.Linq; usando System.Runtime.CompilerServices; usando System.Text; usando System.Threading.Tasks; usando Windows.Devices.Enumeration; usando Windows.Devices.I2c; espacio de nombres LagoVista.IoT.Common.Kegerator {clase pública Kegerator :INotifyPropertyChanged {evento público PropertyChangedEventHandler PropertyChanged; privados Models.Keg _keg1; privados Models.Keg _keg2; privados Models.Keg _keg3; privados Models.Keg _keg4; privado 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 (esto, nuevo PropertyChangedEventArgs (propertyName)); }} private bool Set (ref T almacenamiento, T valor, string columnName =null, [CallerMemberName] string propertyName =null) {if (object.Equals (storage, value)) return false; almacenamiento =valor; this.RaisePropertyChanged (propertyName); devuelve verdadero; } byte [] _scalesAddresses ={0x43, 0x41, 0x40, 0x42}; cadena const privada I2C_CONTROLLER_NAME ="I2C1"; Termo.Temperaturas privadas _temperaturas; Thermo.Controller privado _tempController; Scales.Scale privado _co2Scale; Diccionario privado _kegScales; privado CO2.PressureSensor _pressureSensor; privado LED.LEDManager _ledManager; privado REST.KegeratorServices _kegServices; Kegerator estático privado _kegerator =new Kegerator (); Instancia pública estática de Kegerator {get {return _kegerator; }} CloudServices.EventHubClient privado _eventHubClient; System.Threading.Timer _timer; private bool _initialized =false; Public async Task Init () {if (! _initialized) {_initialized =true; var selector =I2cDevice.GetDeviceSelector (I2C_CONTROLLER_NAME); / * Encuentra la cadena de selección para el controlador de bus I2C * / var deviceInfo =(espera DeviceInformation.FindAllAsync (selector)). FirstOrDefault (); / * Encuentra el dispositivo controlador de bus I2C con nuestra cadena de selección * / var deviceId =deviceInfo ==null? (cadena) nulo:deviceInfo.Id; _temperaturas =nuevo Thermo.Temperatures (0x48); aguardar _temperaturas.Init (deviceId); _devices.Add (_temperatures); _tempController =nuevo Thermo.Controller (); _tempController.Init (_temperatures); _devices.Add (_tempController); _pressureSensor =nuevo CO2.PressureSensor (); aguardar _pressureSensor.Init (deviceId, TimeSpan.FromSeconds (1)); _devices.Add (_pressureSensor); _co2Scale =new Scales.Scale (0x44); aguardar _co2Scale.Init (deviceId, TimeSpan.FromSeconds (1)); _devices.Add (_co2Scale); _co2Tank =nuevo CO2.CO2Tank (_co2Scale, TimeSpan.FromSeconds (2)); _co2Tank.Load (); _devices.Add (_co2Tank); _kegScales =nuevo Diccionario (); _eventHubClient =new CloudServices.EventHubClient (esto, TimeSpan.FromSeconds (2)); _devices.Add (_eventHubClient); for (var idx =0; idx <4; ++ idx) {var scale =new Scales.Scale (_scalesAddresses [idx]); aguardar scale.Init (deviceId, TimeSpan.FromMilliseconds (500)); _kegScales.Add (idx, escala); _devices.Add (escala); } _keg1 =new Models.Keg (1, _kegScales [0], TimeSpan.FromMilliseconds (500)); _keg1.Load (); _devices.Add (_keg1); _keg2 =nuevos modelos.Keg (2, _kegScales [1], TimeSpan.FromMilliseconds (500)); _keg2.Load (); _devices.Add (_keg2); _keg3 =nuevos modelos.Keg (3, _kegScales [2], TimeSpan.FromMilliseconds (500)); _keg3.Load (); _devices.Add (_keg3); _keg4 =nuevos modelos.Keg (4, _kegScales [3], TimeSpan.FromMilliseconds (500)); _keg4.Load (); _devices.Add (_keg4); DateInitialized =DateTime.Now.ToString (); Web.WebServer.Instance.StartServer (); _kegServices =new REST.KegeratorServices () {Puerto =9500}; _kegServices.EventContent + =_kegServices_EventContent; _kegServices.StartServer (); _timer =nuevo System.Threading.Timer ((estado) => {Refresh ();}, null, 0, 250); }} private void _kegServices_EventContent (remitente del objeto, cadena e) {var parts =e.Split ('/'); if (parts.Count ()> 0) {switch (parts [1]) {case "zero":{var scaleIndex =Convert.ToInt32 (parts [2]); _kegScales [scaleIndex] .StoreOffset (); } descanso; case "cal":{var scaleIndex =Convert.ToInt32 (partes [2]); _kegScales [scaleIndex] .CalibrationWeight =Convert.ToDouble (partes [3]); _kegScales [scaleIndex] .Calibrate (); } descanso; }}} public void Refresh () {foreach (var dispositivo en _devices) {if (DateTime.Now> (device.LastUpdated + device.UpdateInterval)) device.Refresh (); } LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {CurrentTimeDisplay =DateTime.Now.ToString (); RaisePropertyChanged ("CurrentTimeDisplay");}); } temperaturas públicas Thermo.Temperatures Temperaturas {get {return _temperatures; }} Public Thermo.Controller TemperatureController {get {return _tempController; }} private String _statusMessage; Public String StatusMessage {get {return _statusMessage; } set {Set (ref _statusMessage, value); }} Lista pública KegScales {get {return _kegScales.Values.ToList (); }} public void ToggleCompressor () {if (_tempController.IsCompressorOn) _tempController.CompressorOff (); else _tempController.CompressorOn (); } public String DateInitialized {get; colocar; } public String CurrentTimeDisplay {get; colocar; } public Scales.Scale CO2Scale {get {return _co2Scale; }} público 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; }} CO2.CO2Tank público CO2Tank {get {return _co2Tank; }} public RelayCommand ToggleCompressorCommand {get {return new RelayCommand (ToggleCompressor); }}}}
Esquemas
Diagrama de componentes del sistema de alto nivelProceso de manufactura
- Evitación de obstáculos utilizando inteligencia artificial
- Diversión con giroscopio con NeoPixel Ring
- Controlador de juego Arduino
- Traje de nube
- Seguidor de línea industrial para suministro de materiales
- Pixie:un reloj de pulsera NeoPixel basado en Arduino
- Botella de agua con tecnología Arduino
- Teatro de sombras navideño
- Cámara de vigilancia remota móvil
- Guante de mano inteligente
- Última máquina de electroerosión por hilo en Taiwán