Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Manufacturing Technology >> Processo de manufatura

Ultimate Kegerator

Componentes e suprimentos

SparkFun Arduino Pro Mini 328 - 3,3 V / 8 MHz
× 7
Amplificador de célula de carga SparkFun - HX711
× 1
Leviton 47603-12B 4x12 Telefone Distrobution Board
× 1
Raspberry Pi 2 Modelo B
× 1
AC 100-120V 200-220V 5V 6A 30W Swich Power Supply
× 1
Sensor de movimento infravermelho
× 1
10.6 cu. pés. Chest Freezer
× 1
Balança postal digital de peso inteligente
× 5
Teclado Ultra Slim Rii K12 de 2,4 GHz
× 1
Rasbperry Pi WiFi Dongle
× 1
Módulo de relé Arduino DC 5V
× 1
Sensor DHT11 de Temperatura e Umidade (4 pinos)
× 2
Sensor de temperatura DHT22
× 3
HomeBrewStuff Aço inoxidável Double Draft Torre de cerveja
× 2
Beer Tower Cooler
× 1
Sensor de pressão I2C 0-30 PSI (3,3V) ABPMANN030PG2A3
× 1

Ferramentas e máquinas necessárias

Ferramentas de trabalho para madeira
Ferramentas para personalizar a parte externa do freezer
Cortador de fio
Ferro de soldar (genérico)
Tubulação termorretrátil

Sobre este projeto


Isenção de responsabilidade: Em primeiro lugar, este projeto de forma alguma promove o uso ou abuso de álcool, cabe totalmente aos usuários quais bebidas irão compor o conteúdo deste kegerator.

Este projeto nasceu do desejo de gerenciar melhor o conteúdo de um kegerator. Um kegerator trabalha com o princípio básico de manter uma bebida fria, bem como mantê-la carbonatada em um determinado PSI. Além disso, apenas servindo-se de uma bebida gelada, você não tem ideia de quanto resta no barril. Seria uma pena receber gente para um jogo de futebol de domingo e ficar sem cerveja no meio do jogo.









Portanto, os objetivos deste projeto são:
  1. Mantenha uma temperatura consistente das bebidas, certifique-se de que as bebidas não fiquem muito quentes ou muito frias e congelem
  2. Certifique-se de que uma quantidade aceitável de carbonatação seja aplicada ao barril para manter o sabor ideal
  3. Acompanhe a quantidade de bebidas em cada barril e forneça feedback visual para garantir que haja muitas bebidas disponíveis para o grande jogo.
  4. Acompanhe a quantidade de CO2 restante no tanque usado para carbonatar as bebidas



Os componentes eletrônicos básicos e seu uso:
  1. Um freezer horizontal é usado para a unidade de resfriamento e para fornecer uma estrutura para criar uma bela peça de mobiliário
  2. Raspberry PI 2 executando o Windows 10 IoT core é usado como o cérebro da operação
  3. Pequenas balanças de postagem são usadas para medir o peso de cada barril, bem como do tanque de CO2, essas balanças de postagem têm os componentes eletrônicos removidos e um amplificador de célula de carga e um pequeno Arduino embutidos na balança. Essas escalas irão se comunicar com o Raspberry PI 2 via I2C (mais sobre isso mais tarde)
  4. Existem 5 sensores digitais de temperatura instalados na unidade, um na parte inferior do freezer torácico, um preso na parte inferior da parte superior, cada um instalado nas torres onde estão os puxadores da torneira (mais sobre isso mais tarde ) e um instalado na parte externa da unidade para medir a temperatura ambiente. Esses sensores de temperatura são conectados a um pequeno Arduino e também se comunicam com o Raspberry PI 2 via I2C
  5. Um sensor de pressão Honeywell é conectado às linhas de ar usadas para fornecer carbonatação aos barris. Embora o ajuste do PSI seja manual (por enquanto), ele fornecerá um medidor preciso de quanto CO2 é aplicado aos barris.
  6. Uma fonte de alimentação de 5 V é usada para fornecer energia ao Raspberry PI2. Uma versão maior (fornecendo até 6 amperes) foi escolhida para que também pudesse alimentar uma faixa de LED endereçável.
  7. Um relé simples é colocado em linha com a alimentação do compressor. Usando este relé, a energia pode ser aplicada e removida do compressor, o compressor, por sua vez, controlará a temperatura do kegerator (mais sobre isso mais tarde)

Conectividade em nuvem

O Ultimate Kegerator contém um servidor web que permite a configuração remota por meio de serviços REST, bem como a visualização estática simples do status atual. Este site pode ser acessado em http://slsys.homeip.net:9501.

Além disso, o Ultimate Kegerator carrega suas estatísticas vitais para um hub de eventos do Windows Azure. Você não será capaz de usar o pacote Nuget padrão para falar com o hub de eventos, no entanto, você tem a biblioteca fácil de implementar fornecida por seu colega MVP do Windows Embedded Paolo Patierno disponível em

https://www.nuget.org/packages/AzureSBLite/

Para processamento final por Stream Analytics

Os planos eventuais para o Stream Analytics seriam:

1) Monitore e notifique se as temperaturas ficarem muito altas ou muito baixas

2) Monitore e notifique quando o tanque de CO2 ficar muito baixo

3) Monitore e notifique se houver um vazamento detectado no tanque de CO2 (diminuição gradual do peso)




Aqui estão algumas fotos adicionais do processo de montagem:




-twb

Código

  • Classe Barril
  • Classe de Escala
  • Classe Kegerator
Classe Barril C #
Visualização do código-fonte antes do código-fonte completo ser lançado no GitHub. Se você deseja acesso antecipado ou gostaria de ajudar a contribuir, entre em contato com o autor deste projeto
 usando LagoVista.Common.Commanding; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System .Threading.Tasks; usando 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, escala Scales.Scale, TimeSpan updateInterval) {_idx =idx; _updateInterval =UpdateInterval; _escala =escala; } public override TimeSpan UpdateInterval {get {return _updateInterval; }} substituição pública void Refresh () {LastUpdated =DateTime.Now; LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {ContentsWeight =Scale.Weight - ContainerWeightLb; if (FullContentsWeightLb> 0) PercentFull =Convert.ToInt32 ((ContentsWeight / (FullContentsWeight) 100); else PercentFull =0; PercentFull =Math.Min (PercentFull, 100); if (GlassSizeOz> 0) QtyRemaining =Convert.ToInt32 ((ContentsWeight * 16) / GlassSizeOz); else QtyRemaining =0; RaisePropertyChanged ("PercentFullHeight"); RaisePropertyChanged ("PercentFullHeight"); ("PercentFullDisplay");}); } public Scales.Scale Scale {get {return _scale; }} #region Propriedades calculadas 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 Propriedades inseridas private bool _isEmpty; public bool IsEmpty {get {return _isEmpty; } defina {_isEmpty =value; RaisePropertyChanged (); }} private double _glassSize; public double GlassSizeOz {get {return _glassSize; } set {Set (ref _glassSize, value); }} private DateTime? _data de nascimento; 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); senão BornDate =null; var installDate =GetSetting ("KEG {0} _INSTALL_DATE", String.Empty); if (! String.IsNullOrEmpty (installDate)) InstallDate =DateTime.Parse (installDate); senão InstallDate =null; } public async void SaveFullWeight () {FullContentsWeightLb =await Scale.GetAadiseWeight (); Salve (); } public RelayCommand SaveFullWeightCommand {get {return new RelayCommand (() => SaveFullWeight ()); }}}} 
Classe de escala C #
Visualização do código-fonte antes do código-fonte completo ser lançado no GitHub. Se você deseja acesso antecipado ou gostaria de ajudar a contribuir, entre em contato com o autor deste projeto
 usando LagoVista.Common.Commanding; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System .Text; usando System.Threading.Tasks; usando Windows.Devices.I2c; namespace LagoVista.IoT.Common.Kegerator.Scales {public class Scale:DeviceBase {Windows.Devices.I2c.I2cDevice _scaleI2CChannel; int _countOffset; Duplo? _calibrationFactor =null; private TimeSpan _updateInterval; byte _address; escala pública (endereço de byte) {_address =endereço; } private void WriteValue (endereço de byte, valor int) {if (! IsDemoMode) {var offsetBuffer =new byte [5]; offsetBuffer [0] =endereço; offsetBuffer [1] =(byte) (valor>> 24); offsetBuffer [2] =(byte) (valor>> 16); offsetBuffer [3] =(byte) (valor>> 8); offsetBuffer [4] =(byte) (valor); _scaleI2CChannel.Write (offsetBuffer); }} public assíncrono 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, configurações); 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)]); tente {WriteValue ((byte) 'O', _countOffset); } catch (Exceção ex) {Debug.WriteLine ("Escala 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 ="Pronto";}); }} else {LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {Status ="Pronto";}); }} int? _lastRaw =null; private int GetRaw () {try {var inbuffer =new byte [4]; _scaleI2CChannel.Write (novo byte [] {(byte) 0x11}); _scaleI2CChannel.Read (inbuffer); / * Nota na escala, este é um longo (64 bits) aqui é um 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; } catch (exceção) {return -1; }} substituição pública void Refresh () {LastUpdated =DateTime.Now; int rawResult =0; var isOnline =true; tente {var inbuffer =novo byte [4]; var statusBuffer =novo byte [1]; if (! IsDemoMode) {_scaleI2CChannel.Write (novo 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, Peso)); } 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 (exceção 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 ="Não calibrado";}} RaisePropertyChanged ("LastUpdateDisplay");}); } const int CALIBRATION_COUNT =10; public async void StoreOffset () {LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {Status ="Zeroing";}); Debug.WriteLine ("Staritng Zero Process"); longo zeroSum =0; para (var idx =0; idx  {Status ="Zerado";}); } public async void Calibrate () {Status ="Calibrando"; LagoVista.Common.PlatformSupport.Services.BindingHelper.RefreshBindings (); long countSum =0; para (var idx =0; idx  GetAadiseWeight (int pointCount =5) {var weightSum =0.0; para (var idx =0; idx  StoreOffset ()); }} public RelayCommand CalibrationCommand {get {return new RelayCommand (() => Calibrate ()); }}}} 
Classe Kegerator C #
Visualização do código-fonte antes do código-fonte completo ser lançado no GitHub. Se você deseja acesso antecipado ou gostaria de ajudar a contribuir, entre em contato com o autor deste projeto
 usando LagoVista.Common.Commanding; using System; using System.Collections.Generic; 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; namespace LagoVista.IoT.Common.Kegerator {classe pública Kegerator :INotifyPropertyChanged {evento público PropertyChangedEventHandler PropertyChanged; Modelos privados.Keg _keg1; Modelos privados.Keg _keg2; Modelos privados.Keg _keg3; Modelos privados.Keg _keg4; CO2.CO2Tank privado _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)); }} private bool Set  (ref T storage, T value, string columnName =null, [CallerMemberName] string propertyName =null) {if (object.Equals (storage, value)) return false; armazenamento =valor; this.RaisePropertyChanged (propertyName); return true; } byte [] _scalesAddresses ={0x43, 0x41, 0x40, 0x42}; string const privada I2C_CONTROLLER_NAME ="I2C1"; private Thermo.Temperatures _temperatures; private Thermo.Controller _tempController; private Scales.Scale _co2Scale; dicionário privado  _kegScales; private CO2.PressureSensor _pressureSensor; private LED.LEDManager _ledManager; private REST.KegeratorServices _kegServices; Kegerator estático privado _kegerator =new Kegerator (); instância pública estática do Kegerator {get {return _kegerator; }} private CloudServices.EventHubClient _eventHubClient; System.Threading.Timer _timer; bool privado _inicializado =falso; Tarefa assíncrona pública Init () {if (! _initialized) {_initialized =true; var selector =I2cDevice.GetDeviceSelector (I2C_CONTROLLER_NAME); / * Encontre a string do seletor para o controlador de barramento I2C * / var deviceInfo =(await DeviceInformation.FindAllAsync (selector)). FirstOrDefault (); / * Encontre o dispositivo controlador de barramento I2C com nossa string de seletor * / var deviceId =deviceInfo ==null? (string) null:deviceInfo.Id; _temperaturas =novas Thermo.Temperatures (0x48); esperar _temperatures.Init (deviceId); _devices.Add (_temperatures); _tempController =novo Thermo.Controller (); _tempController.Init (_temperatures); _devices.Add (_tempController); _pressureSensor =novo CO2.PressureSensor (); esperar _pressureSensor.Init (deviceId, TimeSpan.FromSeconds (1)); _devices.Add (_pressureSensor); _co2Scale =new Scales.Scale (0x44); esperar _co2Scale.Init (deviceId, TimeSpan.FromSeconds (1)); _devices.Add (_co2Scale); _co2Tank =novo CO2.CO2Tank (_co2Scale, TimeSpan.FromSeconds (2)); _co2Tank.Load (); _devices.Add (_co2Tank); _kegScales =novo Dicionário  (); _eventHubClient =new CloudServices.EventHubClient (this, TimeSpan.FromSeconds (2)); _devices.Add (_eventHubClient); para (var idx =0; idx <4; ++ idx) {var scale =new Scales.Scale (_scalesAddresses [idx]); esperar scale.Init (deviceId, TimeSpan.FromMilliseconds (500)); _kegScales.Add (idx, escala); _dispositivos.Adicionar (escala); } _keg1 =novos Modelos.Keg (1, _kegScales [0], TimeSpan.FromMilliseconds (500)); _keg1.Load (); _devices.Add (_keg1); _keg2 =novos Modelos.Keg (2, _kegScales [1], TimeSpan.FromMilliseconds (500)); _keg2.Load (); _devices.Add (_keg2); _keg3 =novos Modelos.Keg (3, _kegScales [2], TimeSpan.FromMilliseconds (500)); _keg3.Load (); _devices.Add (_keg3); _keg4 =novos 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 () {Port =9500}; _kegServices.EventContent + =_kegServices_EventContent; _kegServices.StartServer (); _timer =novo System.Threading.Timer ((state) => {Refresh ();}, null, 0, 250); }} private void _kegServices_EventContent (remetente do objeto, string e) {var parts =e.Split ('/'); if (parts.Count ()> 0) {switch (parts [1]) {case "zero":{var scaleIndex =Convert.ToInt32 (parts [2]); _kegScales [scaleIndex] .StoreOffset (); } pausa; case "cal":{var scaleIndex =Convert.ToInt32 (partes [2]); _kegScales [scaleIndex] .CalibrationWeight =Convert.ToDouble (partes [3]); _kegScales [scaleIndex] .Calibrate (); } pausa; }}} 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; }} controlador de temperatura Thermo.Controller público {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; definir; } public String CurrentTimeDisplay {get; definir; } public Scales.Scale CO2Scale {get {return _co2Scale; }} public CO2.PressureSensor PressureSensor {get {return _pressureSensor; }} modelos públicos.Keg Keg1 {get {return _keg1; }} modelos públicos.Keg Keg2 {get {return _keg2; }} modelos públicos.Keg Keg3 {get {return _keg3; }} modelos públicos.Keg Keg4 {get {return _keg4; }} public CO2.CO2Tank CO2Tank {get {return _co2Tank; }} public RelayCommand ToggleCompressorCommand {get {return new RelayCommand (ToggleCompressor); }}}} 

Esquemas

Diagrama de componentes do sistema de alto nível

Processo de manufatura

  1. Evitar Obstáculos usando Inteligência Artificial
  2. Diversão do giroscópio com anel NeoPixel
  3. Controlador de jogo Arduino
  4. Traje da nuvem
  5. Seguidor de linha industrial para fornecimento de materiais
  6. Pixie:Um relógio de pulso NeoPixel baseado em Arduino
  7. Garrafa de água alimentada por Arduino
  8. Teatro Holiday Shadow
  9. Câmera de vigilância remota móvel
  10. Ultimate Wire EDM Machine em Taiwan