Окончательный кегератор
Компоненты и расходные материалы
![]() |
| × | 7 | |||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 2 | |||
![]() |
| × | 3 | |||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 |
Необходимые инструменты и машины
| ||||
| ||||
![]() |
| |||
|
Об этом проекте
Заявление об ограничении ответственности: Во-первых, этот проект никоим образом не продвигает употребление или злоупотребление алкоголем, это полностью зависит от пользователей, какие напитки будут входить в состав этого кегератора.
Этот проект родился из желания лучше управлять содержимым кегератора. Кегератор работает по основному принципу:поддержание напитка холодным, а также поддержание газированных напитков при определенном PSI. Вдобавок, просто наливая себе холодный напиток, вы даже не представляете, сколько осталось в бочонке. Было бы обидно, если бы ребята пришли на воскресный футбольный матч и на полпути закончились пивовары.
Итак, цели этого проекта:
- Поддерживайте постоянную температуру напитков, следите, чтобы они не становились слишком горячими или слишком холодными и не замерзали.
- Убедитесь, что в бочонок добавлено приемлемое количество карбонизации для поддержания оптимального вкуса.
- Следите за количеством напитков в каждой бочке и обеспечивайте визуальную обратную связь, чтобы обеспечить наличие большого количества напитков для большой игры.
- Следите за количеством CO2, оставшимся в баке, используемом для газирования напитков.
Основные компоненты электроники и их использование:
- Морозильный ларь используется для охлаждающего устройства и служит каркасом для создания красивого предмета мебели.
- Raspberry PI 2 под управлением Windows 10 IoT core используется как мозг операции.
- Небольшие почтовые весы используются для измерения веса каждого бочонка, а также баллона с CO2. У этих почтовых весов удалена электроника, а в весы встроены усилитель весовой ячейки и небольшой Arduino. Эти весы будут взаимодействовать с Raspberry PI 2 через I2C (подробнее об этом позже).
- На устройстве установлено 5 цифровых температурных датчиков:один в нижней части морозильного ларя, один прикреплен к нижней части верхней части, по одному установлен в башнях, где находятся ручки кранов (подробнее об этом позже ) и один установлен снаружи устройства для измерения температуры окружающей среды. Эти датчики температуры подключены к небольшому Arduino, а также обмениваются данными с Raspberry PI 2 через I2C.
- Датчик давления Honeywell прикреплен к воздушным линиям, которые используются для карбонизации бочонков. Хотя настройка PSI выполняется вручную (на данный момент), это позволит точно определить, сколько CO2 подается в кеги.
- Для питания Raspberry PI2 используется источник питания 5 В. Была выбрана версия большего размера (обеспечивающая до 6 ампер), чтобы она также могла питать адресную светодиодную ленту.
- Простое реле подключается к источнику питания компрессора. С помощью этого реле можно подавать и отключать питание компрессора, затем компрессор, в свою очередь, будет контролировать температуру кегератора (подробнее об этом позже).
Подключение к облаку
Ultimate Kegerator содержит веб-сервер для удаленной настройки через службы REST, а также простой статический просмотр текущего состояния. Этот веб-сайт находится по адресу http://slsys.homeip.net:9501.
Кроме того, Ultimate Kegerator загружает важную статистику в концентратор событий Windows Azure. Вы не сможете использовать стандартный пакет Nuget для связи с концентратором событий, однако вы можете легко реализовать библиотеку, предоставленную другим специалистом по Windows Embedded MVP Паоло Патьерно, доступную по адресу
https://www.nuget.org/packages/AzureSBLite/

Для максимальной обработки в Stream Analytics

Возможные планы Stream Analytics:
1) Отслеживайте и уведомляйте, если температура становится слишком высокой или слишком низкой
2) Контролируйте и уведомляйте, когда в баллоне с CO2 становится слишком мало
3) Отслеживайте и уведомляйте, если в баллоне с CO2 обнаружена утечка (постепенное уменьшение веса)
Вот несколько дополнительных изображений процесса сборки:











-twb
Код
- Класс кег
- Масштабный класс
- Класс кегератора
Класс Keg C #
Предварительный просмотр исходного кода до того, как полный исходный код будет выпущен на GitHub. Если вам нужен ранний доступ или вы хотите помочь, пожалуйста, свяжитесь с автором этого проектаиспользуя LagoVista.Common.Commanding; используя System; используя System.Collections.Generic; используя System.Linq; используя System.Text; используя System .Threading.Tasks; использование Windows.UI.Xaml; пространство имен LagoVista.IoT.Common.Kegerator.Models {открытый класс Keg:DeviceBase {int _idx; TimeSpan _updateInterval; частные Scales.Scale _scale; общедоступный бочонок (int idx, Scales.Scale scale, TimeSpan updateInterval) {_idx =idx; _updateInterval =UpdateInterval; _scale =масштаб; } общедоступное переопределение TimeSpan UpdateInterval {получить {return _updateInterval; }} общедоступное переопределение void Refresh () {LastUpdated =DateTime.Now; LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {ContentsWeight =Scale.Weight - ContainerWeightLb; if (FullContentsWeightLb> 0) PercentFull =Convert.ToInt32 ((ContentsWeight / (FullContentsWeightL)); else PercentFull =0; PercentFull =Math.Min (PercentFull, 100); if (GlassSizeOz> 0) QtyRemaining =Convert.ToInt32 ((ContentsWeight * 16) / GlassSizeOz); else QtyRemaining =0; RaisePropertyChanged ("PercentFullhangedHenged (" PercentFullhangedHenged) ("PercentFullDisplay");}); } общедоступные Scales.Scale Scale {получить {return _scale; }} #region Расчетные свойства private int _qtyRemaining; public int QtyRemaining {получить {вернуть _qtyRemaining; } set {Set (ref _qtyRemaining, значение); }} частный DateTime? _installDate; общедоступное DateTime? InstallDate {получить {return _installDate; } set {Set (ref _installDate, значение); }} private int _percentFull; public int PercentFull {получить {return _percentFull; } set {Set (ref _percentFull, значение); }} общедоступная строка PercentFullDisplay {получить {return String.Format ("{0}%", Convert.ToInt32 (PercentFull)); }} общедоступный двойной PercentFullHeight {получить {return Convert.ToDouble (_percentFull * 2); }} public int KegIndex {получить {return _idx; }} #endregion #region Введенные свойства private bool _isEmpty; public bool IsEmpty {получить {return _isEmpty; } установить {_isEmpty =значение; RaisePropertyChanged (); }} private double _glassSize; общедоступный двойной GlassSizeOz {получить {return _glassSize; } set {Set (ref _glassSize, значение); }} частный DateTime? _bornDate; общедоступное DateTime? BornDate {получить {return _bornDate; } set {Set (ref _bornDate, значение); }} double _containerWeight; общедоступный двойной ContainerWeightLb {получить {return _containerWeight; } set {Set (ref _containerWeight, значение); }} double _contentsWeight; общедоступный двойной ContentsWeight {получить {return _contentsWeight; } set {Set (ref _contentsWeight, значение); }} double _fullContentsWeight; общедоступный двойной FullContentsWeightLb {получить {return _fullContentsWeight; } set {Set (ref _fullContentsWeight, значение); }} private String _contentsName; общедоступная строка ContentsName {получить {return _contentsName; } set {Set (ref _contentsName, значение); }} #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")); varbornDate =GetSetting ("KEG {0} _BORN_DATE", String.Empty); если (! String.IsNullOrEmpty (bornDate)) BornDate =DateTime.Parse (bornDate); иначе BornDate =null; var installDate =GetSetting ("KEG {0} _INSTALL_DATE", String.Empty); если (! String.IsNullOrEmpty (installDate)) InstallDate =DateTime.Parse (installDate); иначе InstallDate =null; } public async void SaveFullWeight () {FullContentsWeightLb =ожидание Scale.GetAverageWeight (); Сохранять(); } общедоступная команда RelayCommand SaveFullWeightCommand {получить {вернуть новую команду RelayCommand (() => SaveFullWeight ()); }}}}
Класс масштабирования C #
Предварительный просмотр исходного кода до того, как полный исходный код будет выпущен на GitHub. Если вам нужен ранний доступ или вы хотите помочь, свяжитесь с автором этого проекта, используя LagoVista.Common.Commanding; используя System; используя System.Collections.Generic; используя System.Diagnostics; используя System.Linq; используя System .Text; использование System.Threading.Tasks; использование Windows.Devices.I2c; пространство имен LagoVista.IoT.Common.Kegerator.Scales {общедоступный класс Scale:DeviceBase {Windows.Devices.I2c.I2cDevice _scaleI2CChannel; int _countOffset; двойной? _calibrationFactor =null; частный TimeSpan _updateInterval; byte _address; публичная шкала (байтовый адрес) {_address =адрес; } private void WriteValue (адрес байта, значение int) {if (! IsDemoMode) {var offsetBuffer =новый байт [5]; offsetBuffer [0] =адрес; offsetBuffer [1] =(байт) (значение>> 24); offsetBuffer [2] =(байт) (значение>> 16); offsetBuffer [3] =(байт) (значение>> 8); offsetBuffer [4] =(байт) (значение); _scaleI2CChannel.Write (offsetBuffer); }} инициализация общедоступной асинхронной задачи (String i2cDeviceId, TimeSpan updateInterval) {var settings =new I2cConnectionSettings (_address) {BusSpeed =I2cBusSpeed.StandardMode, SharingMode =I2cSharingMode.Shared}; _updateInterval =updateInterval; IsDemoMode =String.IsNullOrEmpty (i2cDeviceId); if (! IsDemoMode) {_scaleI2CChannel =ждать Windows.Devices.I2c.I2cDevice.FromIdAsync (i2cDeviceId, настройки); 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)]); попробуйте {WriteValue ((byte) 'O', _countOffset); } catch (Exception ex) {Debug.WriteLine ("Масштабирование в автономном режиме"); }} 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 ="Готово";}); }} else {LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {Status ="Готово";}); }} int? _lastRaw =null; private int GetRaw () {попробуйте {var inbuffer =new byte [4]; _scaleI2CChannel.Write (новый байт [] {(байт) 0x11}); _scaleI2CChannel.Read (в буфере); / * Обратите внимание на масштаб, это длинный (64 бит), здесь это int (64 бит) * / var thisRaw =(int) (inbuffer [0] <<24 | inbuffer [1] <<16 | inbuffer [ 2] <<8 | в буфере [3]); if (_lastRaw.HasValue) {если (Math.Abs (_lastRaw.Value - thisRaw)> 0xFFFF) return _lastRaw.Value; } else _lastRaw =thisRaw; вернуть thisRaw; } catch (исключение) {return -1; }} общедоступное переопределение void Refresh () {LastUpdated =DateTime.Now; int rawResult =0; var isOnline =true; попробуйте {var inbuffer =new byte [4]; var statusBuffer =новый байт [1]; если (! IsDemoMode) {_scaleI2CChannel.Write (новый байт [] {(байт) 0x0A}); _scaleI2CChannel.Read (statusBuffer); rawResult =GetRaw (); } if (_calibrationFactor.HasValue) {Weight =(rawResult - _countOffset) * _calibrationFactor.Value; Debug.WriteLine (String.Format ("0x {0:X} ЗНАЧЕНИЕ ВЕСА => {1:0,00} фунтов", _address, Weight)); } else if (_countOffset> 0) Debug.WriteLine (String.Format ("0x {0:X} НУЛЕВОЕ ЗНАЧЕНИЕ => {1}", _address, rawResult - _countOffset)); иначе Debug.WriteLine (String.Format ("0x {0:X} RAW VALUE => 0x {1:X}", _address, rawResult)); } catch (Exception ex) {rawResult =-1; isOnline =false; Debug.WriteLine (например, сообщение); } 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 ="Не откалибровано";}} RaisePropertyChanged ("LastUpdateDisplay");}); } const int CALIBRATION_COUNT =10; public async void StoreOffset () {LagoVista.Common.PlatformSupport.Services.DispatcherServices.Invoke (() => {Status ="Обнуление";}); Debug.WriteLine («Запуск нулевого процесса»); long zeroSum =0; for (var idx =0; idx{Status ="Обнулен";}); } public async void Calibrate () {Статус ="Калибровка"; 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 ()); }}}}
Класс Kegerator C #
Предварительный просмотр исходного кода до того, как полный исходный код будет выпущен на GitHub. Если вам нужен ранний доступ или вы хотите помочь, свяжитесь с автором этого проектаиспользуя LagoVista.Common.Commanding; используя System; используя System.Collections.Generic; используя 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 {публичное событие PropertyChangedEventHandler PropertyChanged; частные Models.Keg _keg1; частные Models.Keg _keg2; частные Models.Keg _keg3; частные Models.Keg _keg4; частный CO2.CO2Tank _co2Tank; частный Kegerator () {} открытый список_devices =новый список (); частная недействительность RaisePropertyChanged ([CallerMemberName] строка propertyName =null) {var eventHandler =this.PropertyChanged; if (eventHandler! =null) {eventHandler (это, новый PropertyChangedEventArgs (имя_свойства)); }} private bool Set (ref T storage, T value, string columnName =null, [CallerMemberName] string propertyName =null) {if (object.Equals (storage, value)) return false; storage =значение; this.RaisePropertyChanged (имя_свойства); вернуть истину; } byte [] _scalesAddresses ={0x43, 0x41, 0x40, 0x42}; закрытая константная строка I2C_CONTROLLER_NAME ="I2C1"; частные Thermo.Temperatures _temperatures; частный Thermo.Controller _tempController; частные Scales.Scale _co2Scale; частный словарь _kegScales; частный CO2.PressureSensor _pressureSensor; частный LED.LEDManager _ledManager; частный REST.KegeratorServices _kegServices; частный статический кегератор _kegerator =новый кегератор (); общедоступный статический экземпляр Kegerator {get {return _kegerator; }} частный CloudServices.EventHubClient _eventHubClient; System.Threading.Timer _timer; частный bool _initialized =false; общедоступная асинхронная задача Init () {если (! _initialized) {_initialized =true; var selector =I2cDevice.GetDeviceSelector (I2C_CONTROLLER_NAME); / * Находим строку селектора для контроллера шины I2C * / var deviceInfo =(await DeviceInformation.FindAllAsync (selector)). FirstOrDefault (); / * Найдите устройство контроллера шины I2C с помощью нашей строки выбора * / var deviceId =deviceInfo ==null? (строка) null:deviceInfo.Id; _temperas =новые Thermo.Temperatures (0x48); ждать _temperas.Init (deviceId); _devices.Add (_temperas); _tempController =новый Thermo.Controller (); _tempController.Init (_temperas); _devices.Add (_tempController); _pressureSensor =новый CO2.PressureSensor (); ожидание _pressureSensor.Init (deviceId, TimeSpan.FromSeconds (1)); _devices.Add (_pressureSensor); _co2Scale =новый Scales.Scale (0x44); ожидание _co2Scale.Init (deviceId, TimeSpan.FromSeconds (1)); _devices.Add (_co2Scale); _co2Tank =новый CO2.CO2Tank (_co2Scale, TimeSpan.FromSeconds (2)); _co2Tank.Load (); _devices.Add (_co2Tank); _kegScales =новый словарь (); _eventHubClient =новый CloudServices.EventHubClient (это, TimeSpan.FromSeconds (2)); _devices.Add (_eventHubClient); для (var idx =0; idx <4; ++ idx) {var scale =new Scales.Scale (_scalesAddresses [idx]); ожидание scale.Init (deviceId, TimeSpan.FromMilliseconds (500)); _kegScales.Add (idx, масштаб); _devices.Add (масштаб); } _keg1 =новый Models.Keg (1, _kegScales [0], TimeSpan.FromMilliseconds (500)); _keg1.Load (); _devices.Add (_keg1); _keg2 =новый Models.Keg (2, _kegScales [1], TimeSpan.FromMilliseconds (500)); _keg2.Load (); _devices.Add (_keg2); _keg3 =новый Models.Keg (3, _kegScales [2], TimeSpan.FromMilliseconds (500)); _keg3.Load (); _devices.Add (_keg3); _keg4 =новый Models.Keg (4, _kegScales [3], TimeSpan.FromMilliseconds (500)); _keg4.Load (); _devices.Add (_keg4); DateInitialized =DateTime.Now.ToString (); Web.WebServer.Instance.StartServer (); _kegServices =новый REST.KegeratorServices () {порт =9500}; _kegServices.EventContent + =_kegServices_EventContent; _kegServices.StartServer (); _timer =новый System.Threading.Timer ((состояние) => {Refresh ();}, null, 0, 250); }} private void _kegServices_EventContent (отправитель объекта, строка e) {var parts =e.Split ('/'); if (parts.Count ()> 0) {switch (parts [1]) {case "ноль":{var scaleIndex =Convert.ToInt32 (parts [2]); _kegScales [scaleIndex] .StoreOffset (); } ломать; case "cal":{var scaleIndex =Convert.ToInt32 (parts [2]); _kegScales [scaleIndex] .CalibrationWeight =Convert.ToDouble (parts [3]); _kegScales [scaleIndex] .Calibrate (); } ломать; }}} 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; }} общедоступный Thermo.Controller TemperatureController {получить {return _tempController; }} private String _statusMessage; общедоступная строка StatusMessage {получить {вернуть _statusMessage; } set {Set (ref _statusMessage, значение); }} общедоступный список KegScales {получить {return _kegScales.Values.ToList (); }} public void ToggleCompressor () {если (_tempController.IsCompressorOn) _tempController.CompressorOff (); еще _tempController.CompressorOn (); } общедоступная строка DateInitialized {получить; набор; } общедоступная строка CurrentTimeDisplay {получить; набор; } общественные Scales.Scale CO2Scale {получить {return _co2Scale; }} public CO2.PressureSensor PressureSensor {get {return _pressureSensor; }} общедоступные Models.Keg Keg1 {get {return _keg1; }} общедоступные Models.Keg Keg2 {get {return _keg2; }} общедоступные Models.Keg Keg3 {get {return _keg3; }} общедоступные Models.Keg Keg4 {get {return _keg4; }} общественный CO2.CO2Tank CO2Tank {get {return _co2Tank; }} public RelayCommand ToggleCompressorCommand {получить {вернуть новую RelayCommand (ToggleCompressor); }}}}
Схема
Схема компонентов системы высокого уровняПроизводственный процесс
- Избегание препятствий с помощью искусственного интеллекта
- Удовольствие от гироскопа с кольцом NeoPixel
- Игровой контроллер Arduino
- Костюм облака
- Последователь промышленной линии для поставки материалов
- Pixie:наручные часы NeoPixel на базе Arduino
- Бутылка для воды с питанием от Arduino
- Праздничный театр теней
- Мобильная камера удаленного наблюдения
- Ultimate Wire EDM Machine на Тайване