Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Manufacturing Technology >> Proceso de manufactura

Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción

Componentes y suministros

SparkFun Arduino Pro Mini 328 - 3.3V/8MHz × 7
Amplificador de célula de carga SparkFun - HX711 × 1
Tablero de distribución telefónica Leviton 47603-12B 4x12 × 1
Raspberry Pi 2 Model B × 1
Fuente conmutada AC 100-120V 200-220V 5V 6A 30W × 1
Sensor de movimiento IR × 1
Congelador horizontal 10.6 pies cúbicos × 1
Báscula digital postal Smart Weight × 5
Teclado ultra slim Rii K12 2.4 GHz × 1
Dongle WiFi para Raspberry Pi × 1
Módulo relé DC 5V Arduino × 1
Sensor temperatura y humedad DHT11 (4 pines) × 2
Sensor temperatura DHT22 × 3
Torre de grifo doble acero inoxidable HomeBrewStuff × 2
Enfriador de torre de cerveza × 1
Sensor presión I2C 0-30 PSI (3.3V) ABPMANN030PG2A3 × 1

Herramientas y máquinas necesarias

Sobre el proyecto

Descargo de responsabilidad: En primer lugar, este proyecto no promueve de ninguna manera el uso o abuso del alcohol, depende completamente de los usuarios qué bebidas componen el contenido de este kegerator.

Este proyecto nació del deseo de gestionar mejor el contenido de un kegerator. Un kegerator funciona según el principio básico de mantener una bebida fría y carbonatada a una cierta presión (PSI). Además, simplemente sirviéndose una bebida fría no se sabe cuánto queda en el barril. Sería una pena tener invitados para un partido de fútbol el domingo y quedarse sin cerveza de raíz a mitad del partido.

Los objetivos de este proyecto son:

  1. Mantener una temperatura constante de las bebidas, asegurando que no se calienten demasiado ni se congelen
  2. Garantizar que se aplica una cantidad aceptable de carbonatación al barril para mantener el sabor óptimo
  3. Controlar la cantidad de bebida en cada barril y proporcionar retroalimentación visual para asegurar que hay suficiente bebida para el gran evento
  4. Controlar la cantidad de CO2 restante en el tanque usado para carbonatar las bebidas

Componentes electrónicos básicos y su uso:

  1. Un congelador horizontal se usa como unidad de enfriamiento y para proporcionar un marco para crear un mueble atractivo
  2. Raspberry Pi 2 ejecutando Windows 10 IoT Core actúa como cerebro del sistema
  3. Pequeñas básculas postales miden el peso de cada barril y del tanque de CO2; a estas básculas se les quita la electrónica original y se integra un amplificador de célula de carga y un pequeño Arduino. Estas básculas se comunican con la Raspberry Pi 2 vía I2C (más detalles después)
  4. Hay 5 sensores digitales de temperatura instalados en la unidad: uno en el fondo del congelador, uno en la parte inferior de la tapa, uno en cada torre donde están los grifos, y uno en el exterior para medir la temperatura ambiente. Estos sensores están conectados a un pequeño Arduino y también se comunican con la Raspberry Pi 2 vía I2C
  5. Un sensor de presión Honeywell está conectado a las líneas de aire que proveen carbonatación a los barriles. Aunque el ajuste de PSI es manual (por ahora), esto proporciona una medición precisa de cuánto CO2 se aplica a los barriles
  6. Una fuente de alimentación 5V alimenta la Raspberry Pi 2. Se eligió una versión mayor (hasta 6 amperios) para poder alimentar también una tira LED direccionable
  7. Un relé simple se coloca en línea con la alimentación del compresor. Usando este relé se puede aplicar y cortar la alimentación al compresor, que a su vez controla la temperatura del kegerator (más detalles después)

Conectividad en la nube

El Kegerator Definitivo contiene un servidor web para permitir configuración remota vía servicios REST y una vista estática simple del estado actual. Este sitio web puede alcanzarse en https://slsys.homeip.net:9501.

Además, el Kegerator Definitivo sube sus estadísticas vitales a un Event Hub de Windows Azure. No podrá usar el paquete Nuget estándar para hablar con el Event Hub, sino la librería fácil de implementar proporcionada por el MVP de Windows Embedded Paolo Patierno disponible en https://www.nuget.org/packages/AzureSBLite/

Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción

Para procesamiento definitivo por Stream Analytics

Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción

Planes eventuales para Stream Analytics:

  1. Monitorizar y notificar si las temperaturas se vuelven demasiado altas o bajas
  2. Monitorizar y notificar cuando el tanque de CO2 se queda muy bajo
  3. Monitorizar y notificar si se detecta una fuga en el tanque de CO2 (disminución gradual de peso)

Aquí hay algunas fotos adicionales del proceso de montaje:

Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción
Kegerator Definitivo DIY: Guía Completa de Componentes y Construcción

-twb

Código

Clase 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()); }
    }
  }
}
Clase 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()); }
    }
  }
}
Clase 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); }
    }
  }
}

Esquemas

Diagrama de componentes del sistema a alto nivel

Proceso de manufactura

  1. Sistema de Evitación de Obstáculos con IA y Arduino: Aprendizaje por Refuerzo Q-Learning
  2. Pantalla LED Interactiva con Giroscopio, Anillo NeoPixel y Arduino
  3. Mando USB para juegos DIY con Arduino – Construye tu propio gamepad de alto rendimiento
  4. Disfraz de nube DIY: Tira LED, Arduino Nano y componentes SparkFun
  5. Robot industrial seguidor de línea para transporte de materiales
  6. Pixie: Reloj de pulsera NeoPixel basado en Arduino – Elegante reloj LED DIY
  7. Botella de agua inteligente con Arduino: Monitorización en tiempo real y alertas
  8. Teatro de Sombras Navideño DIY: Espectáculo de Luces con Arduino y Tira LED Direccionable
  9. Sistema Portátil de Vigilancia Remota con Cámara y Robot Móvil
  10. Guante Inteligente de Gestos a Texto para Lenguaje de Signos
  11. Máquinas de Electroerosión por Hilo de Primera Categoría: Soluciones de Corte de Precisión desde Taiwán