資料介紹
描述
車庫水培
一年 365 天種植自己的農產品,產量比土壤高 40%。
4 周后,它們幾乎可以收割了!
首先在車庫中找到一個漂亮的空地,讓您可以進入水培系統的各個方面。
大多數車庫不加熱和冷卻,所以為了幫助保持一個更穩定的環境,絕緣是你的朋友。車庫最明顯和強制性的部分是首先絕緣是車庫門。使用可以在當地五金店買到的絕緣材料。我選擇了 Rmax R-Matte Plus-3 3/4",4 英尺 x 8 英尺的床單,我使用精確刀切割成合適的尺寸。然后將合適的尺寸水平切成兩半,并壓縮到車庫門槽中,確保箔側面向外面有半英寸的氣隙。這個氣隙給了我相當于 6 的總 R 系數。絕緣越好,你以后支付的加熱和冷卻費用就越少。
車庫零件
x6 Rmax R-Matte Plus-3 3/4" x 4' x 8'. R-5 聚異氰脲酸酯硬質泡沫絕緣板
注意:確保密封車庫中允許室外空氣進入的所有區域。
由于您將加熱、冷卻和提供人造植物光,因此建議為您的水培系統添加專用斷路器。讓有執照的電工為您添加一個新的 20 安培 GFI 斷路器。大多數斷路器都在車庫中,因此添加新電路應該是一種成本相對較低的選擇,以實現更好的隔離和安全。
在車庫的空地上搭建您的水培帳篷。
![pYYBAGNYp5SAZg8UAADLJZSwvfU548.jpg](https://file.elecfans.com/web2/M00/73/F5/pYYBAGNYp5SAZg8UAADLJZSwvfU548.jpg)
水培帳篷配件
x1 VIVOSUN 96"x48"x80" 聚酯薄膜水培種植帳篷房
x1 VIVOSUN 8" 直列管道風扇,帶 Speeder 空氣碳過濾器管道組合
x1 Quantum Storage 4 層線架單元,300 磅負載能力/架子。72"H x 48"W x 24"D.
x4 Durolux DLED8048W 320W LED 植物燈。4' x 1.5' 200W,白色 FullSun。x1
VIVOSUN 6" 2-Speed Clip On 擺動風扇。
x1 Pelonis 電動注油加熱器,帶可調節恒溫器黑色。
x1 AC/DC 5V-400V 緩沖板繼電器接觸保護吸收電路模塊。
架子/燈光設置
盡管架子有四層,但我只使用了三層,這樣我就有足夠的光線和成長空間。擱板可定制配置為任意數量的擱板和高度,每個擱板最多可容納 300 磅。在帳篷的右側,我添加了一個生長燈以容納更大的植物。我個人更喜歡白色 LED 燈,但您可以使用任何您喜歡的高品質水培植物燈。LED 燈是首選,因為它們功率較低,產生的熱量較少,而且使用壽命更長。Durolux DLED8048W 僅使用 200W,CCT 為 5946K 全太陽光譜。
![poYBAGNYp5iAd7paAABCKZqDd5E757.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp5iAd7paAABCKZqDd5E757.jpg)
碳過濾器/管道設置
在水培帳篷的右側,我安裝了碳過濾器和通風風扇,將空氣引向車庫門。如果需要,如果您的車庫較小,您也可以將空氣輸送到室外。循環新鮮空氣對于保持植物健康至關重要。
![pYYBAGNYp5qAft-xAADH70eCfq0377.jpg](https://file.elecfans.com/web2/M00/73/F5/pYYBAGNYp5qAft-xAADH70eCfq0377.jpg)
水培法
對于這個水培系統,我們將使用 Aeroponics 方法。這是我們將高度自動化的最先進的水培方法,因此您將能夠“觀察和種植”您的作物。這種方法允許最高的加速生長速率和作物產量。對于這種方法,植物根部將始終浸沒在富含氧氣的充氣水庫中。這也使得設置不同的水培方法變得最復雜和最困難。但不用擔心,通過適當的控制系統,它將易于管理和維護。
氣培零件
x1 VIVOSUN 氣泵 950 GPH 32W 60L/min 6 個出口。
x1 UDP 10' 1/4" ID x 7/16" 透明編織乙烯基管。
x1 UDP 10' 1/2" ID x 3/4" OD 透明編織乙烯基管。
x1 0.170" ID x 1/4" OD 20 英尺透明乙烯基管。
x1 Pawfly 5 件單向氧氣泵調節器止回閥。
x1 10 件 2 路透明彎頭水族箱空氣連接器。
x2 12" 空氣石氣泡窗簾桿
。x2 Sterilite 10 加侖。手提包黑色 25-3/4" x 18-1/4" x 7" h。
x1 Sterilite 4 加侖。手提包黑色 18" x 12-1/2" x 7" h.
x1 x25 黑色 3" 網壺杯 - 重型無拉通輪輞設計。
x110 升 HYDROTON 粘土卵石生長介質膨脹粘土巖石。
x1 VIVOSUN 6 Mil Mylar 薄膜卷 4' x 10' 金剛石薄膜箔卷。
氣培法設置
我們將設置兩個充氣水庫,每個水庫有九個花盆。首先創建一個 13.5" x 5.5" 的紙板模板,并在中心孔的兩側分別鉆出 6.75" 和 4.25" 的導向孔。
![poYBAGNYp5yAHQmrAABXEUGGPMo052.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp5yAHQmrAABXEUGGPMo052.jpg)
使用本指南在 10 加侖水庫手提袋的蓋子上鉆九個孔。
![pYYBAGNYp6GAWyy9AABeXnArD4o281.jpg](https://file.elecfans.com/web2/M00/73/F5/pYYBAGNYp6GAWyy9AABeXnArD4o281.jpg)
然后使用 3" 鉆頭,反向鉆出 3" 孔用于網杯。
![pYYBAGNYp6WAMG05AABugB9VxEI577.jpg](https://file.elecfans.com/web2/M00/73/F5/pYYBAGNYp6WAMG05AABugB9VxEI577.jpg)
鉆完九個孔后,確保網杯可以輕松安裝并齊平到孔中。必要時打磨。
![poYBAGNYp6eAHdreAABhJh3bQzE349.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp6eAHdreAABhJh3bQzE349.jpg)
選擇黑色手提包有一個非常具體的原因;它不允許任何光線進入水庫,從而減少藻類的生長,但黑色確實會吸收頭頂的光線,并會增加內部的水溫。為了緩解這種情況,我們將使用聚酯薄膜并制作一個折疊蓋,該蓋具有與手提袋蓋上相同的孔切口以反射這種光。
剪下一塊 39" x 32.5" 的長方形聚酯薄膜,在所有邊上折疊 6.5",然后折痕。將手提袋蓋放在聚酯薄膜底部并居中,使其與所有邊緣均勻貼合,并使用以手提袋蓋為模板,用記號筆畫出剪出的圓圈。沿著標記圓圈的外邊緣剪開,形成你的九個網杯孔。最后用你的折紙技巧,把角落折疊起來,釘好。
![poYBAGNYp6mAD1HoAABYNWu_oAg106.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp6mAD1HoAABYNWu_oAg106.jpg)
然后將蓋子蓋在蓋子上,用小粘土巖石填滿你的網杯。
![poYBAGNYp62ADWF8AADaulPSj0Y220.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp62ADWF8AADaulPSj0Y220.jpg)
或者,如果您想種植更大的葉子植物,則創建一個六盆水庫。創建一個 13.5" x 5.5" 的紙板模板,并在 4" 和 10" 處鉆導孔。
![pYYBAGNYp7CABEjFAABY3vVpm1k584.jpg](https://file.elecfans.com/web2/M00/73/F5/pYYBAGNYp7CABEjFAABY3vVpm1k584.jpg)
使用本指南,在 10 加侖水箱手提袋的蓋子上鉆六個孔。
![poYBAGNYp7OASYjsAABvWTi1Pxs975.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp7OASYjsAABvWTi1Pxs975.jpg)
在myar上使用相同的過程,切出六個網杯孔,折疊并組裝。
![poYBAGNYp7WAHYXAAABr1bwX_i0042.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp7WAHYXAAABr1bwX_i0042.jpg)
將氣泵固定到 4 加侖手提袋的底部,并用扎帶鉆孔并固定適當的空氣軟管,以便進出空氣流動。如果您可以在當地的五金店購買軟管,它會更便宜。將蓋子固定在手提包上。我們將氣泵隱藏起來,以幫助降低氣泵產生的噪音。空氣泵產生熱量。因此,在冬季,將氣泵手提袋放在水培帳篷中以幫助加熱,而在夏季,將其放在室外以幫助減少熱量。進氣軟管應始終從水培帳篷外部抽出空氣以獲取新鮮空氣。
![poYBAGNYp7eAQH67AACCuMyJvLU554.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp7eAQH67AACCuMyJvLU554.jpg)
為了進一步降低噪音,請在手提包的下側安裝 3/4" 自粘管絕緣層,并在手提包頂部放置重物
![pYYBAGNYp7qAbFxIAADDTF8R01A381.jpg](https://file.elecfans.com/web2/M00/73/F5/pYYBAGNYp7qAbFxIAADDTF8R01A381.jpg)
使用冷卻器儲水箱背面的切口,將空氣軟管連接到彎頭連接器、流量閥,最后連接到空氣石。彎頭將停留在槽口中,以防止空氣軟管扭結,并且止回流量值將阻止水箱中的任何水在斷電期間回流到氣泵中。確保您得到帶刺的檢查流量值,否則來自氣泵的壓力會不斷推開軟管。將 12 英寸的空氣石長距離放置在冷卻器盤管之間的手提包的中心底部。
注意:使用少量橄欖油可以更輕松地將空氣軟管滑到連接器上。
![poYBAGNYp72AO3rDAACPNegaeuQ626.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp72AO3rDAACPNegaeuQ626.jpg)
水培控制系統
水培控制系統實際上是兩個早期項目的組合和添加。
IO 擴展器專為需要極端傳感器 IO 的水培/魚菜共生系統而設計,當所有配件最終組裝在一起時,您將看到這一點。
![pYYBAGNYp7-AbD5hAADziVWgUcg223.jpg](https://file.elecfans.com/web2/M00/73/F6/pYYBAGNYp7-AbD5hAADziVWgUcg223.jpg)
功能列表
- 內部/外部溫度/濕度傳感器。
- 具有絕對濕度比較的智能通風風扇控制。
- 智能通風省電。
- 照明控制。
- 自動溫度控制。
- 用于調度的電池支持實時時鐘。
- 非易失性存儲。備份當前狀態。
- 智能電源控制和監控。
- WiFi 連接。
- WiFi記錄實時數據。
- WiFi 提醒您的智能手機。
水培控制系統零件
x1 IO 擴展器。
x1 IO 擴展器捆綁包。
x1 BMOUO 12V 30A 直流通用穩壓開關電源 360W。
x1 NodeMcu ESP8266 ESP-12E 無線 WiFi 板。
x1 12V 16 通道繼電器模塊。
x1 DS3231 AT24C32 I2C 精密實時時鐘內存模塊。
x2 FS200-SHT10 土壤溫度和濕度傳感器探頭。
x2 1 端口表面安裝盒白色。
x2 1.3" I2C 128x64 SSD1306 OLED LCD 顯示屏白色。x1
4件雙排 8 位螺絲端子排 600V
25A。x1 7 端子接地棒套件
。x1265x185x95mm 防水透明電子項目箱外殼塑料外殼。
接線圖
![poYBAGNYp8SAPnNmAAWdhKaBITE157.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp8SAPnNmAAWdhKaBITE157.jpg)
注意:您在電話線中看到“X”的位置表示反向接線。
OLED顯示屏
![pYYBAGNYp8eAd8FnAACOO7nnjxk295.jpg](https://file.elecfans.com/web2/M00/73/F6/pYYBAGNYp8eAd8FnAACOO7nnjxk295.jpg)
注意:概述的濕度低于最低值,反相溫度高于最高警告值。
那么為什么要使用 IO 擴展器呢?
- 設計更簡單。
- 現成的零件。
- 無需寫入 1-Wire 驅動程序。
- 沒有要寫入的繼電器驅動程序。
- 無需編寫 OLED 顯示驅動程序。
- 沒有顯示字體占用 ESP8266 代碼空間。
- 無需編寫濕度傳感器驅動程序。
- 無需寫入 DS3231 RTC 驅動程序。
- 無需寫入 AT24C32 EEPROM 驅動程序。
- 節省 ESP8266 上的代碼空間。
- 使用標準 RJ11 電話線易于接線。
- 沒有傳感器電纜長度問題。
- 比商業系統更便宜。
- 易于更改以適應個人需求。
- 單電源。
水培控制系統
在項目外殼底部鉆孔并固定電源端子。左側為110VAC,右側為12VDC。在項目外殼的底部鉆孔并安裝用于 110VAC、12VDC 和數據線輸入/輸出的壓蓋螺母。
![pYYBAGNYp8mAEpE6AACp5ArHHEg414.jpg](https://file.elecfans.com/web2/M00/73/F6/pYYBAGNYp8mAEpE6AACp5ArHHEg414.jpg)
警告:只有在您對高電壓工作感到滿意時才執行此操作!
![pYYBAGNYp8uAPcgjAAAy9VbPPJE264.jpg](https://file.elecfans.com/web2/M00/73/F6/pYYBAGNYp8uAPcgjAAAy9VbPPJE264.jpg)
連接所需的 110VAC 電源線,并將火線(黑色)連接到下部繼電器。
![poYBAGNYp86ATWDhAADHNuTrBvg853.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp86ATWDhAADHNuTrBvg853.jpg)
運行并將所需的 12V 繼電器電源線連接到上部繼電器。
![poYBAGNYp9OAN_wyAADbglxUatg741.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp9OAN_wyAADbglxUatg741.jpg)
一旦所有電源線都運行完畢,請確保將保護蓋放在接線盒上,以防止任何意外接觸。
在繼電器板下方和 12VDC 電源端子上方放置一層薄薄的絕緣泡沫,使其完全絕緣。
![poYBAGNYp9aAKo0-AADF8jDqivI044.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp9aAKo0-AADF8jDqivI044.jpg)
將 1-Wire 連接到 I2C 到 DS3231,然后連接到兩個 SSD1306 OLED 屏幕時,您將在 SDA 和 SCL 線上總共有四個不同的上拉電阻,如下圖黃色圓圈所示。這將有效地導致 4.7k / 4 = 1.175k 上拉,對于 I2C 總線來說太強而無法正常運行。
![pYYBAGNYp9iAZYYqAADc0C3VKxk265.jpg](https://file.elecfans.com/web2/M00/73/F6/pYYBAGNYp9iAZYYqAADc0C3VKxk265.jpg)
由于 DS3231 使用其他線路使用的電阻器組,請移除其他上拉電阻器:
- 1-Wire 到 I2C R3 和 R4。
- SSD1306 OLED R6 和 R7。
- 將第二個 OLED 屏幕上以綠色圈出的 4.7k 上拉從地址選擇 0x78 移動到 0x7A。
注意:根據您獲得的 1.3" OLED 顯示器的類型,顯示的電阻器可能不同。
為了連接成長模塊端口 1 和 2 需要通過添加 2.2K 上拉轉換為 1-wire? 過載端口。這可以通過在 IO 擴展器底部的引腳之間焊接一個 0603 2.2K 電阻器來輕松完成。
![poYBAGNYp9qAGqgnAABwWr65SB0272.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp9qAGqgnAABwWr65SB0272.jpg)
最后組裝所有板以完成水培控制系統。可以鉆孔和添加額外的支座以固定繼電器和 IO 擴展板。使用雙面膠帶固定較小的電路板。
![poYBAGNYp92ANTS5AAD4OwKqd4c070.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp92ANTS5AAD4OwKqd4c070.jpg)
ESP8266 代碼 (OTA)
對以下代碼進行必要的更改,以指定您的 WiFi 路由器 SSID、密碼和標有“** Change **”的傳感器地址。然后僅使用 USB 端口對 ESP8266 NodeMCU 進行一次編程。現在可以通過無線 (OTA) 進行未來的更新,因此您現在可以保持項目框關閉并仍然進行更新。
/* IO Expander
Garage Hydroponics System v2.0
*/
#include
#include <time.h>
#include /* qsort */
#if defined(ESP8266)
#include
#include
#include
#endif
#if defined(ARDUINO_ARCH_ESP32)
#include
#include
#endif
#include
#include
#include
#include
#include "IOExpander.h"
#ifndef SSID
#define SSID "RouterName" // *** Change RouterName
#define PSK "RouterPassword" // *** Change RouterPassword
#define HOST "http://www.mywebsite.com" // *** Change mywebsite.com
#define MySQL
#define MSSQL
#ifdef MySQL
#define MYSQL_URL "http://192.168.1.50/hydroponics/adddata.php" // *** Change 192.168.1.50
const char* mysql_url = MYSQL_URL;
#endif
#ifdef MSSQL
#define MSSQL_URL "http://www.zevendevelopment.com/hydroponics/adddata.aspx" // *** Change mywebsite.com
const char* mssql_url = MSSQL_URL;
#endif
#endif
#define TZ_POSIX "EST+5EDT,M3.2.0/2,M11.1.0/2"
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP); //, EST_OFFSET);
long tzoffset;
const char* ssid = SSID;
const char* password = PSK;
const char* host = HOST;
#define LED_BUILTIN 2
#define SerialDebug Serial1 // Debug goes out on GPIO02
#define SerialExpander Serial // IO Expander connected to the ESP UART
#define FAHRENHEIT
#define ONEWIRE_TO_I2C_MAIN "i4s08" // *** Change 08
#define RTC_SENSOR "s4te"
#define I2C_EEPROM "s4tf"
#define INIT_OLED1 "st13;si;sc;sd"
#define INIT_OLED2 "st133d;si;sc;sd"
//#define HUMIDITY_SENSOR_INSIDE "s6t5" // DHT22
//#define HUMIDITY_SENSOR_OUTSIDE "s8t1" // SHT10
// Free port 5-8 by using a splitter on port 3 and use I2C SHT3x humidity sensors
#define HUMIDITY_SENSOR_INSIDE "i3s5a;ic0;st3" // SHT3x 100kHz w/ 2.2k pullup *** Change 5a
#define HUMIDITY_SENSOR_OUTSIDE "i3s0e;ic0;st3" // SHT31 100kHz w/ 2.2k pullup *** Change 0e
#define ALL_RELAYS_OFF "esffff"
#define VENT_FAN_ON "e1o"
#define VENT_FAN_OFF "e1f"
#define LIGHTS_ON "e2o"
#define LIGHTS_OFF "e2f"
#define HEATER_ON "e3o"
#define HEATER_OFF "e3f"
#define CHILLER_ON "e4o"
#define CHILLER_OFF "e4f"
#define WATER_PUMP_ON "e5o"
#define WATER_PUMP_OFF "e5f"
#define HEATER_PAD_ON "e6o"
#define HEATER_PAD_OFF "e6f"
#define SEC_IN_MIN 60
#define MIN_IN_HOUR 60
#define HOURS_IN_DAY 24
#define MIN_IN_DAY (MIN_IN_HOUR * HOURS_IN_DAY)
#define DAYS_IN_WEEK 7
#define ROOM_VOLUME (96*48*80) // Grow room Length * Width * Height in inches
#define FOOT_CUBE (12*12*12) // Convert inches to feet volume
#define VENT_FAN_CFM 720 // Cubic Feet per Minute
#define VENT_FAN_POWER 190 // Fan power in Watts
#define DUCT_LENGTH 2 // Short=2, Long=3
#define AIR_EXCHANGE_TIME 5 // Exchange air time. Every 5 minutes
#define VENT_FAN_ON_TIME ((((ROOM_VOLUME*DUCT_LENGTH)/FOOT_CUBE)/VENT_FAN_CFM)+1)
uint8_t OVERRIDE_VENT_FAN;
uint16_t OVERRIDE_VENT_FAN_TIME = 0;
#define MIN_DAY_TEMP 70 // Warm season crops daytime (70-80)
#define MAX_DAY_TEMP 80
#define MAX_OFF_TEMP 90 // Max temp to turn lights off
#define HEATER_ON_DAY_TEMP 66.5
#define HEATER_OFF_DAY_TEMP 68.5
#define MIN_NIGHT_TEMP 60 // Nighttime (60-70)
#define MAX_NIGHT_TEMP 70
#define HEATER_ON_NIGHT_TEMP 66
#define HEATER_OFF_NIGHT_TEMP 64
#define MIN_HUMIDITY 50 // Relative humidity. Best=60%
#define MAX_HUMIDITY 70
#define MIN_WATER_TEMP 66 // 68F or 20C
#define MAX_WATER_TEMP 70
#define SOLENOID_ON_WATER_TEMP 68.25
#define SOLENOID_OFF_WATER_TEMP 67.75
#define CHILLER_ON_WATER_TEMP 45 //55
#define CHILLER_OFF_WATER_TEMP 40 //45
#define CHILLER_CYCLE_TIME 10 // Chiller minimum on/off time to protect compressor
#define CHILLER_RECOVERY_TIME 240 // Chiller recovery time needs to occur in this time
#define GERMINATION_ON_TEMP 74.5 // Germination heater pad temperature
#define GERMINATION_OFF_TEMP 75.5
#define LIGHTS_ON_HOUR 6 // Lights on from 6:00AM - 6:00PM (12 hrs)
#define LIGHTS_ON_MIN 0
#define LIGHTS_OFF_HOUR 18
#define LIGHTS_OFF_MIN 0
#define LIGHTS_POWER (192*2) // 4 Grow lights
#define LIGHTS_ON_DAY_MIN ((LIGHTS_ON_HOUR * MIN_IN_HOUR) + LIGHTS_ON_MIN)
#define LIGHTS_OFF_DAY_MIN ((LIGHTS_OFF_HOUR * MIN_IN_HOUR) + LIGHTS_OFF_MIN)
uint8_t OVERRIDE_LIGHTS;
uint16_t OVERRIDE_LIGHTS_TIME = 0;
#define IOEXPANDER_POWER 3 // IO Expander, NodeMCU, x16 Relay, etc power in Watts
#define AIR_PUMP_POWER 32 // Air Pump power in Watts
#define CIRCULATING_FAN_POWER 20 // Circulating fan in Watts
#define HEATER_POWER 560 // Radiator heater in tent
#define ALWAYS_ON_POWER (IOEXPANDER_POWER + AIR_PUMP_POWER + CIRCULATING_FAN_POWER)
#define DOSING_PUMP_POWER 8 // Peristaltic Dosing Pump 7.5W
#define CHILLER_SOLENOID_POWER 5 // Water Solenoid Valve 4.8W
#define CHILLER_POWER 121 // Freezer 5ct
#define WATER_PUMP_POWER 30 // Peristaltic Chiller Pump 1.4A * 12V = 16.8W
#define HEATER_PAD_POWER 20 // Germination Heat Pad in Watts
#define COST_KWH 9.8450 // First 1000 kWh/month
//#define COST_KWH 10.0527 // Over 1000 kWh/month
#define SERIAL_DEBUG
#define SERIAL_TIMEOUT 5000 // 5 sec delay between DHT22 reads
//#define MAX_SELECT_ROM 21
#define ERROR_NO_ROM -1
#define ERROR_OVER_SATURATED -2
#define ERROR_READ -3
#define CO2_SAMPLES_IN_MIN 5
#define CO2_INTERVAL (SEC_IN_MIN / CO2_SAMPLES_IN_MIN)
#define MAX_CO2_FAILS 10
#define NUTRIENT_MIX_TIME 2 // 2 minutes nutrient mix time.
#define MAX_WATER_PUMP_TIME 5 // 5 minutes of watering then give up
typedef struct {
uint32_t energy_usage[DAYS_IN_WEEK];
uint16_t energy_time[DAYS_IN_WEEK];
uint8_t energy_wday;
//uint8_t state;
uint8_t crc;
} NVRAM;
struct HS {
float temp;
float relative;
float absolute;
bool error;
};
#define ONEWIRE_TEMP "t2s0;tt;t1s0;tt" // DS18B20 on pins 2 and 1 on all grow beds, chiller, and germination
const char ONEWIRE_TO_I2C_GROW1[] = "i2s36"; // IO Adder w/ I2C Bus - OLED Screen/Light Sensor *** Change 36
const char ONEWIRE_TO_I2C_GROW2[] = "i2sfb"; // IO Adder w/ I2C Bus - OLED Screen/Light Sensor *** Change fb
const char ONEWIRE_TO_I2C_GROW3[] = "i2sde"; // RJ11 Keystone Crossover Out, T-Connector w/ I2C Bus - OLED Screen/Light Sensor *** Change de
const char TEMP1_SENSOR[] = "t2r92"; // RJ11 Keystone Crossover In for 1-Wire Junction DS18B20 *** Change 92
const char LEVEL1_SELECT[] = "i2s36;st1a38"; // IO Adder *** Change 36
const char LEVEL1_SENSOR[] = "sr6"; // IO Adder Optical Connector
const char TDS1_SELECT[] = "i2s36;st1b"; // IO Adder *** Change 36
const char TDS1_SENSOR[] = "sr0"; // IO Adder ADC
#define TDS1_CALIBRATION (488.0/488.0) // TDS Calibration (Desired/Actual) *** Change
#define WATER1_RELAY 9 // Relay Water Dosing Pump
#define NUTRIENT1_RELAY 9 // Relay Nutrient Dosing Pump
#define CHILLER1_RELAY 15 // Relay Chiller Solenoid
const char TEMP2_SENSOR[] = "t1r3f"; // RJ11 Keystone Crossover In for 1-Wire Junction DS18B20 *** Change 3f
#define LEVEL2_SELECT LEVEL1_SELECT
const char LEVEL2_SENSOR[] = "sr7"; // IO Adder Optical Connector
#define TDS2_SELECT TDS1_SELECT
const char TDS2_SENSOR[] = "sr1"; // IO Adder ADC
#define TDS2_CALIBRATION (488.0/488.0) // TDS Calibration (Desired/Actual) *** Change
#define WATER2_RELAY 10 // Relay Water Dosing Pump
#define NUTRIENT2_RELAY 10 // Relay Nutrient Dosing Pump
#define CHILLER2_RELAY 16 // Relay Chiller Solenoid
const char TEMP3_SENSOR[] = "t2r5b"; // RJ11 Keystone Crossover In for 1-Wire Junction DS18B20 *** Change 5b
const char LEVEL3_SELECT[] = "i2sfb;st1a38"; // IO Adder *** Change fb
const char LEVEL3_SENSOR[] = "sr6"; // IO Adder Optical Connector
const char TDS3_SELECT[] = "i2sfb;st1b"; // IO Adder *** Change fb
const char TDS3_SENSOR[] = "sr0"; // IO Adder ADC
#define TDS3_CALIBRATION (488.0/488.0) // TDS Calibration (Desired/Actual) *** Change
#define WATER3_RELAY 11 // Relay Water Dosing Pump
#define NUTRIENT3_RELAY 11 // Relay Nutrient Dosing Pump
#define CHILLER3_RELAY 13 // Relay Chiller Solenoid
const char TEMP4_SENSOR[] = "t1r24"; // RJ11 Keystone Crossover In for 1-Wire Junction DS18B20 *** Change 24
#define LEVEL4_SELECT LEVEL3_SELECT
const char LEVEL4_SENSOR[] = "sr7"; // IO Adder Optical Connector
#define TDS4_SELECT TDS3_SELECT
const char TDS4_SENSOR[] = "sr1"; // IO Adder ADC
#define TDS4_CALIBRATION (488.0/488.0) // TDS Calibration (Desired/Actual) *** Change
#define WATER4_RELAY 12 // Relay Water Dosing Pump
#define NUTRIENT4_RELAY 12 // Relay Nutrient Dosing Pump
#define CHILLER4_RELAY 14 // Relay Chiller Solenoid
const char TEMP5_SENSOR[] = "t2r72"; // RJ11 Keystone Crossover In for 1-Wire Junction DS18B20 *** Change 72
#define LEVEL5_SELECT NULL
const char LEVEL5_SENSOR[] = "g8i"; // RJ11 Keystone Crossover for Optical Connector
#define TDS5_SELECT NULL
#define TDS5_SENSOR NULL // No TDS Sensor
#define TDS5_CALIBRATION (488.0/488.0) // TDS Calibration (Desired/Actual) *** Change
#define WATER5_RELAY NULL // Relay Water Dosing Pump
#define NUTRIENT5_RELAY NULL // Relay Nutrient Dosing Pump
#define CHILLER5_RELAY NULL // No Chilling
const char TEMP6_SENSOR[] = "t1r58"; // RJ11 Keystone Crossover In for 1-Wire Junction DS18B20 *** Change 58
#define LEVEL6_SELECT NULL
const char LEVEL6_SENSOR[] = "g7i"; // RJ11 Keystone Crossover for Optical Connector
#define TDS6_SELECT NULL
#define TDS6_SENSOR NULL // No TDS Sensor
#define TDS6_CALIBRATION (488.0/488.0) // TDS Calibration (Desired/Actual) *** Change
#define WATER6_RELAY NULL // Relay Water Dosing Pump
#define NUTRIENT6_RELAY NULL // Relay Nutrient Dosing Pump
#define CHILLER6_RELAY NULL // No Chilling
const char ONEWIRE_TO_I2C_LIGHT[] = "i2s58"; // I2C BUS - Light Sensor *** Change 58
const char LIGHT_SENSOR[] = "st15;sp2"; // TCS34725 RGB Sensor; Turn LED off
const char ONEWIRE_TO_I2C_CO2[] = "i6s08"; // I2C BUS - CO2 Sensor *** Change 08
const char CO2_SENSOR[] = "st16;ic0"; // SCD30 CO2 Sensor 100kHz
const char INIT_CO2[] = "si;sc3,2"; // SCD30 Init; Config measurement interval to 50 sec
const char GERMINATION_SENSOR[] = "t2re0"; // Germination Sensor 1-Wire Junction DS18B20 *** Change e0
const char CHILLER_SENSOR[] = "t2r76"; // Chiller Sensor 1-Wire Junction DS18B20 *** Change 76
const char ONEWIRE_TO_I2C_PH[] = "i1s56"; // I2C BUS - pH Sensor *** Change 56
const char PH_SENSOR[] = "iw63"r""; // pH Sensor
const char PH_SLEEP[] = "iw63"Sleep""; // pH Sleep
const char ONEWIRE_TO_I2C_DO[] = "i1s5d"; // I2C BUS - DO Sensor *** Change 5d
const char DO_SENSOR[] = "iw61"r""; // DO Sensor
const char DO_SLEEP[] = "iw61"Sleep""; // DO Sleep
typedef struct {
bool active;
const char* onewire_i2c;
const char* temp_sensor;
const char* level_select;
const char* level_sensor;
const char* tds_select;
const char* tds_sensor;
uint8_t water_relay;
uint8_t nutrient_relay;
uint8_t chiller_relay;
float tds_calibration;
bool init_oled;
float water_temp;
bool water_temp_error;
bool water_level;
int16_t water_tds;
uint8_t water_pump;
uint8_t water_pump_timer;
uint8_t nutrient_pump;
float nutrient_level;
bool chiller_solenoid;
} GROWBED_t;
GROWBED_t grow_bed_table[] = {
{true, // Top Left
ONEWIRE_TO_I2C_GROW1,
TEMP1_SENSOR,
LEVEL1_SELECT,
LEVEL1_SENSOR,
TDS1_SELECT,
TDS1_SENSOR,
WATER1_RELAY,
NUTRIENT1_RELAY,
CHILLER1_RELAY,
TDS1_CALIBRATION,
true,
0.0,
false,
false,
0,
false,
0,
false,
488.0,
false},
{false, // Top Right
ONEWIRE_TO_I2C_GROW1,
TEMP2_SENSOR,
LEVEL2_SELECT,
LEVEL2_SENSOR,
TDS2_SELECT,
TDS2_SENSOR,
WATER2_RELAY,
NUTRIENT2_RELAY,
CHILLER2_RELAY,
TDS2_CALIBRATION,
true,
0.0,
false,
false,
0,
false,
0,
false,
488.0,
false},
{false, // Bottom Left
ONEWIRE_TO_I2C_GROW2,
TEMP3_SENSOR,
LEVEL3_SELECT,
LEVEL3_SENSOR,
TDS3_SELECT,
TDS3_SENSOR,
WATER3_RELAY,
NUTRIENT3_RELAY,
CHILLER3_RELAY,
TDS3_CALIBRATION,
true,
0.0,
false,
false,
0,
false,
0,
false,
488.0,
false},
{true, // Bottom Right
ONEWIRE_TO_I2C_GROW2,
TEMP4_SENSOR,
LEVEL4_SELECT,
LEVEL4_SENSOR,
TDS4_SELECT,
TDS4_SENSOR,
WATER4_RELAY,
NUTRIENT4_RELAY,
CHILLER4_RELAY,
TDS4_CALIBRATION,
true,
0.0,
false,
false,
0,
false,
0,
false,
488.0,
false},
{true, // Left Bucket
ONEWIRE_TO_I2C_GROW3,
TEMP5_SENSOR,
LEVEL5_SELECT,
LEVEL5_SENSOR,
TDS5_SELECT,
TDS5_SENSOR,
WATER5_RELAY,
NUTRIENT5_RELAY,
CHILLER5_RELAY,
TDS5_CALIBRATION,
true,
0.0,
false,
false,
0,
false,
0,
false,
488.0,
false},
{true, // Right Bucket
ONEWIRE_TO_I2C_GROW3,
TEMP6_SENSOR,
LEVEL6_SELECT,
LEVEL6_SENSOR,
TDS6_SELECT,
TDS6_SENSOR,
WATER6_RELAY,
NUTRIENT6_RELAY,
CHILLER6_RELAY,
TDS6_CALIBRATION,
true,
0.0,
false,
false,
0,
false,
0,
false,
488.0,
false},
};
int led = 13;
bool init_oled = true;
bool init_rtc = true;
long ontime, offtime;
bool init_co2 = true;
uint8_t co2_fail = false;
NVRAM nvram;
NVRAM nvram_test;
bool update_nvram = false;
uint32_t power;
int comparefloats(const void *a, const void *b)
{
return ( *(float*)a - *(float*)b );
}
char weekday[][4] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};
uint8_t crc8(uint8_t* data, uint16_t length)
{
uint8_t crc = 0;
while (length--) {
uint8_t inbyte = *data++;
for (uint8_t i = 8; i; i--) {
uint8_t mix = (uint8_t)((crc ^ inbyte) & 0x01);
crc >>= 1;
if (mix) crc ^= 0x8c;
inbyte >>= 1;
}
}
return crc;
}
#ifdef FAHRENHEIT
#define C2F(temp) CelsiusToFahrenheit(temp)
float CelsiusToFahrenheit(float celsius)
{
return ((celsius * 9) / 5) ez_plus 32;
}
#else
#define C2F(temp) (temp)
#endif
void SerialPrint(const char* str, float decimal, char places, char error)
{
Serial.print(str);
if (error) Serial.print(F("NA"));
else Serial.print(decimal, places);
}
float DewPoint(float temp, float humidity)
{
float t = (17.625 * temp) / (243.04 ez_plus temp);
float l = log(humidity / 100);
float b = l ez_plus t;
// Use the August-Roche-Magnus approximation
return (243.04 * b) / (17.625 - b);
}
#define MOLAR_MASS_OF_WATER 18.01534
#define UNIVERSAL_GAS_CONSTANT 8.21447215
float AbsoluteHumidity(float temp, float relative)
{
//taken from https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
//precision is about 0.1°C in range -30 to 35°C
//August-Roche-Magnus 6.1094 exp(17.625 x T)/(T + 243.04)
//Buck (1981) 6.1121 exp(17.502 x T)/(T + 240.97)
//reference https://www.eas.ualberta.ca/jdwilson/EAS372_13/Vomel_CIRES_satvpformulae.html // Use Buck (1981)
return (6.1121 * pow(2.718281828, (17.67 * temp) / (temp ez_plus 243.5)) * relative * MOLAR_MASS_OF_WATER) / ((273.15 ez_plus temp) * UNIVERSAL_GAS_CONSTANT);
}
void ReadHumiditySensor(HS* hs)
{
SerialCmd("sr");
if (SerialReadFloat(&hs->temp) &&
SerialReadFloat(&hs->relative)) {
//hs->dewpoint = DewPoint(hs->temp, hs->relative);
hs->absolute = AbsoluteHumidity(hs->temp, hs->relative);
hs->error = false;
}
else hs->error = true;
SerialReadUntilDone();
}
void HttpPost(const char *url, String &post_data)
{
HTTPClient http;
http.begin(url);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
int http_code = http.POST(post_data); // Send the request
String payload = http.getString(); // Get the response payload
SerialDebug.println(http_code); // Print HTTP return code
SerialDebug.println(payload); // Print request response payload
if (payload.length() > 0) {
int index = 0;
do
{
if (index > 0) index++;
int next = payload.indexOf('\n', index);
if (next == -1) break;
String request = payload.substring(index, next);
if (request.substring(0, 9).equals(")) break;
SerialDebug.println(request);
StaticJsonDocument<100> doc;
DeserializationError error = deserializeJson(doc, request);
if (!error) {
if (doc["OVERRIDE_LIGHTS_TIME"]) OVERRIDE_LIGHTS_TIME = doc["OVERRIDE_LIGHTS_TIME"];
if (doc["OVERRIDE_LIGHTS"]) OVERRIDE_LIGHTS = doc["OVERRIDE_LIGHTS"];
if (doc["OVERRIDE_VENT_FAN_TIME"]) OVERRIDE_VENT_FAN_TIME = doc["OVERRIDE_VENT_FAN_TIME"];
if (doc["OVERRIDE_VENT_FAN"]) OVERRIDE_VENT_FAN = doc["OVERRIDE_VENT_FAN"];
}
index = next;
} while (index >= 0);
}
http.end(); // Close connection
}
void AddPower(uint32_t watts)
{
nvram.energy_usage[nvram.energy_wday] ez_plus= (watts * 100) / MIN_IN_HOUR;
power ez_plus= watts;
delay(100);
}
void ControlRelay(uint8_t device, const char* on, const char* off, uint32_t power)
{
SerialCmdDone((device) ? on : off);
if (device) {
AddPower(power);
// Resend relay cmd again incase the relay board resets due to a large power drop due to heater or compressor.
SerialCmdDone((device) ? on : off);
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // Turn the LED on
#ifdef SERIAL_DEBUG
// !!! Debug output goes to GPIO02 !!!
SerialDebug.begin(115200);
SerialDebug.println("\r\nGarage Hydroponics");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
SerialDebug.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
swSerialEcho = &SerialDebug;
#endif
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH) {
type = "sketch";
} else { // U_SPIFFS
type = "filesystem";
}
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
SerialDebug.println("Start updating " ez_plus type);
});
ArduinoOTA.onEnd([]() {
SerialDebug.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
SerialDebug.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
SerialDebug.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) {
SerialDebug.println("Auth Failed");
} else if (error == OTA_BEGIN_ERROR) {
SerialDebug.println("Begin Failed");
} else if (error == OTA_CONNECT_ERROR) {
SerialDebug.println("Connect Failed");
} else if (error == OTA_RECEIVE_ERROR) {
SerialDebug.println("Receive Failed");
} else if (error == OTA_END_ERROR) {
SerialDebug.println("End Failed");
}
});
ArduinoOTA.begin();
SerialDebug.println("Ready");
SerialDebug.print("IP address: ");
SerialDebug.println(WiFi.localIP());
// Connect to NTP time server to update RTC clock
timeClient.begin();
timeClient.update();
// Initialize Time Zone and Daylight Savings Time
setenv("TZ", TZ_POSIX, 1);
tzset();
__tzinfo_type *tzinfo;
tzinfo = __gettzinfo();
tzoffset = tzinfo->__tzrule[0].offset;
SerialExpander.begin(115200);
delay(1000); // Delay 1 sec for IO Expander splash
}
void loop() {
HS inside, outside;
static bool vent_fan = false;
static bool lights = false;
static bool heater = false;
static int8_t heater_pad = false;
static int8_t chiller = false;
bool water_pump;
static tm rtc;
static tm clk;
tm trtc;
time_t rtc_time;
//time_t clk_time;
static time_t vent_fan_last_time;
static uint8_t vent_fan_on_time;
static uint8_t last_min = -1;
bool error_rtc;
static bool read_nvram = true;
static bool clear_nvram = false;
static bool init_relays = true;
float cost;
uint32_t energy_usage;
uint16_t energy_time;
long int r, g, b, c;
long int atime, gain;
uint16_t r2, g2, b2;
uint16_t ir;
float gl;
int color_temp, lux;
char error[40];
uint16_t clk_day_min;
uint8_t i, wday;
GROWBED_t* grow_bed;
GROWBED_t* prev_grow_bed;
signed long level;
float voltage, vref;
uint8_t t;
String post_data;
float co2, co2_temp, co2_relative;
static uint8_t co2_samples = 0;
static float co2_data[CO2_SAMPLES_IN_MIN];
float germination_temp;
bool germination_active = true;
float chiller_temp;
static uint8_t chiller_cycle = CHILLER_CYCLE_TIME;
static uint32_t chiller_recovery_time = 0;
char cmd[80];
long rc;
float pH,DO;
ArduinoOTA.handle();
while (Serial.available()) Serial.read(); // Flush RX buffer
Serial.println();
if (SerialReadUntilDone()) {
if (SerialCmdNoError(ONEWIRE_TO_I2C_MAIN) &&
SerialCmdDone(RTC_SENSOR)) {
if (init_rtc) {
rtc_time = timeClient.getEpochTime();
gmtime_r(&rtc_time, &rtc);
SerialWriteTime(&rtc);
init_rtc = false;
}
error_rtc = !SerialReadTime(&rtc);
if (!error_rtc) {
//rtc.tm_isdst = 0; // Do not mktime with daylight savings
trtc = rtc; // mktime corrupts rtc so use trtc
rtc_time = mktime(&trtc) - tzoffset;
localtime_r(&rtc_time, &clk); // Get wday.
if (vent_fan_last_time < rtc_time) vent_fan_last_time = rtc_time;
}
if (init_relays) {
SerialCmdDone(ALL_RELAYS_OFF);
init_relays = false;
}
if (read_nvram) {
if (SerialCmdNoError(I2C_EEPROM)) {
if (SerialReadEEPROM((uint8_t*)&nvram, 0, sizeof(nvram))) {
if (nvram.crc != crc8((uint8_t*)&nvram, sizeof(nvram) - sizeof(uint8_t))) {
clear_nvram = true;
SerialDebug.println("*** CRC Corruption ***");
}
if (clear_nvram) memset(&nvram, 0, sizeof(nvram));
read_nvram = false;
}
}
}
if (!init_co2 && clk.tm_sec % CO2_INTERVAL == 0)
{
if (co2_samples < CO2_SAMPLES_IN_MIN - 1)
{
if (SerialCmdNoError(ONEWIRE_TO_I2C_CO2) &&
SerialCmdDone(CO2_SENSOR))
{
SerialCmd("sr");
if (SerialReadFloat(&co2_data[co2_samples])) {
co2_samples++;
co2_fail = false;
}
else co2_fail++;
SerialReadUntilDone();
}
}
}
// Process only once every minute
if (clk.tm_min != last_min)
{
SerialCmdDone(ONEWIRE_TEMP); // Start temperature conversion for all DS18B20 on the 1-Wire bus.
if (SerialCmdDone(HUMIDITY_SENSOR_INSIDE))
ReadHumiditySensor(&inside);
if (SerialCmdDone(HUMIDITY_SENSOR_OUTSIDE))
ReadHumiditySensor(&outside);
// Check grow lights
if (OVERRIDE_LIGHTS_TIME) {
lights = OVERRIDE_LIGHTS;
OVERRIDE_LIGHTS_TIME--;
}
else {
clk_day_min = (clk.tm_hour * MIN_IN_HOUR) ez_plus clk.tm_min;
if (clk_day_min >= LIGHTS_ON_DAY_MIN &&
clk_day_min < LIGHTS_OFF_DAY_MIN)
lights = true;
else lights = false;
// Turn the lights off if the inside temp > MAX_VENT_TEMP and the vent fan has already tried to cool it down
if (lights && C2F(inside.temp) >= MAX_OFF_TEMP) lights = false;
}
// Check air ventilation
if (OVERRIDE_VENT_FAN_TIME) {
vent_fan = OVERRIDE_VENT_FAN;
OVERRIDE_VENT_FAN_TIME--;
}
else {
if (vent_fan_last_time <= rtc_time) {
vent_fan_last_time = vent_fan_last_time ez_plus (AIR_EXCHANGE_TIME * 60);
vent_fan_on_time = VENT_FAN_ON_TIME;
}
if (vent_fan_on_time) {
vent_fan_on_time--;
vent_fan = true;
}
else {
vent_fan = false;
if (lights) {
if ((C2F(inside.temp) < MIN_DAY_TEMP && C2F(outside.temp) > MIN_DAY_TEMP) ||
(C2F(inside.temp) > MAX_DAY_TEMP && C2F(outside.temp) < C2F(inside.temp)))
vent_fan = true;
}
else {
if ((C2F(inside.temp) < MIN_NIGHT_TEMP && C2F(outside.temp) > MIN_NIGHT_TEMP) ||
(C2F(inside.temp) > MAX_NIGHT_TEMP && C2F(outside.temp) < C2F(inside.temp)))
vent_fan = true;
}
}
}
// Check heater
if (clk_day_min >= LIGHTS_ON_DAY_MIN &&
clk_day_min < LIGHTS_OFF_DAY_MIN) {
if (heater) {
if (C2F(inside.temp) >= HEATER_OFF_DAY_TEMP) heater = false;
}
else {
if (C2F(inside.temp) <= HEATER_ON_DAY_TEMP) heater = true;
}
}
else {
if (heater) {
if (C2F(inside.temp) >= HEATER_OFF_NIGHT_TEMP) heater = false;
}
else {
if (C2F(inside.temp) <= HEATER_ON_NIGHT_TEMP) heater = true;
}
}
// Check chiller temp
if (SerialCmd(CHILLER_SENSOR)) {
if (SerialReadFloat(&chiller_temp)) {
if (chiller_cycle) chiller_cycle--;
else {
if (chiller) {
chiller_recovery_time++;
if (C2F(chiller_temp) <= CHILLER_OFF_WATER_TEMP) {
chiller_cycle = CHILLER_CYCLE_TIME;
chiller = false;
chiller_recovery_time = 0;
}
}
else {
if (C2F(chiller_temp) >= CHILLER_ON_WATER_TEMP) {
chiller_cycle = CHILLER_CYCLE_TIME;
chiller = true;
}
}
}
}
SerialReadUntilDone();
}
else {
chiller_temp = ERROR_NO_ROM;
chiller = false;
}
// Check for germination sensor
if (SerialCmd(GERMINATION_SENSOR)) {
if (SerialReadFloat(&germination_temp) && germination_active) {
if (heater_pad) {
if (C2F(germination_temp) > GERMINATION_OFF_TEMP) heater_pad = false;
}
else {
if (C2F(germination_temp) < GERMINATION_ON_TEMP) heater_pad = true;
}
}
else heater_pad = false;
SerialReadUntilDone();
}
else {
germination_temp = ERROR_NO_ROM;
heater_pad = false;
}
// Check for RGB light sensor
color_temp = -1; lux = -1;
if (SerialCmdNoError(ONEWIRE_TO_I2C_LIGHT) &&
SerialCmdDone(LIGHT_SENSOR)) {
SerialCmd("sr");
if (SerialReadInt(&r))
{
SerialReadInt(&g);
SerialReadInt(&b);
SerialReadInt(&c);
SerialReadInt(&atime);
SerialReadInt(&gain);
if (r == 0 && g == 0 && b == 0) {
color_temp = lux = 0;
}
else {
/* AMS RGB sensors have no IR channel, so the IR content must be */
/* calculated indirectly. */
ir = (r ez_plus g ez_plus b > c) ? (r ez_plus g ez_plus b - c) / 2 : 0;
/* Remove the IR component from the raw RGB values */
r2 = r - ir;
g2 = g - ir;
b2 = b - ir;
/* Calculate the counts per lux (CPL), taking into account the optional
arguments for Glass Attenuation (GA) and Device Factor (DF).
GA = 1/T where T is glass transmissivity, meaning if glass is 50%
transmissive, the GA is 2 (1/0.5=2), and if the glass attenuates light
95% the GA is 20 (1/0.05). A GA of 1.0 assumes perfect transmission.
NOTE: It is recommended to have a CPL > 5 to have a lux accuracy
< +/- 0.5 lux, where the digitization error can be calculated via:
'DER = (+/-2) / CPL'.
*/
float cpl = (((256 - atime) * 2.4f) * gain) / (1.0f * 310.0f);
/* Determine lux accuracy (+/- lux) */
float der = 2.0f / cpl;
/* Determine the maximum lux value */
float max_lux = 65535.0 / (cpl * 3);
/* Lux is a function of the IR-compensated RGB channels and the associated
color coefficients, with G having a particularly heavy influence to
match the nature of the human eye.
NOTE: The green value should be > 10 to ensure the accuracy of the lux
conversions. If it is below 10, the gain should be increased, but
the clear<100 check earlier should cover this edge case.
*/
gl = 0.136f * (float)r2 ez_plus /** Red coefficient. */
1.000f * (float)g2 ez_plus /** Green coefficient. */
-0.444f * (float)b2; /** Blue coefficient. */
lux = gl / cpl;
/* A simple method of measuring color temp is to use the ratio of blue */
/* to red light, taking IR cancellation into account. */
color_temp = (3810 * (uint32_t)b2) / /** Color temp coefficient. */
(uint32_t)r2 ez_plus 1391; /** Color temp offset. */
}
}
else {
// Check for over saturation
SerialReadUntil(NULL, NULL, 0, '\n');
SerialReadString(error, sizeof(error));
SerialDebug.println(error);
if (!strcmp(error, "E13")) color_temp = ERROR_OVER_SATURATED;
}
SerialReadUntilDone();
}
else color_temp = ERROR_NO_ROM;
// Check for CO2 sensor
co2 = -1; co2_temp = -1; co2_relative = -1;
if (SerialCmdNoError(ONEWIRE_TO_I2C_CO2) &&
SerialCmdDone(CO2_SENSOR)) {
if (init_co2) {
if (SerialCmdNoError(INIT_CO2)) {
init_co2 = false;
co2_fail = false;
}
}
else {
if (co2_samples) {
SerialCmd("sr");
if (SerialReadFloat(&co2_data[co2_samples]))
{
SerialReadFloat(&co2_temp);
SerialReadFloat(&co2_relative);
co2_samples++;
}
else co2_fail++;
SerialReadUntilDone();
}
else co2_fail++;
if (co2_samples > 2) {
qsort(co2_data, co2_samples, sizeof(float), comparefloats);
co2 = co2_data[co2_samples / 2]; // Median Filter
co2_samples = 0;
co2_fail = false;
}
else {
if (co2_fail >= MAX_CO2_FAILS) {
SerialCmdDone("sc10"); // Soft reset CO2 sensor
init_co2 = true;
co2_fail = false;
}
}
}
}
else {
co2 = ERROR_NO_ROM;
init_co2 = true;
}
// Check for Atlas Scientific pH probe
pH = -1;
if (SerialCmdNoError(ONEWIRE_TO_I2C_PH))
{
//delay(1000);
if (SerialCmdNoError(PH_SENSOR)) {
delay(900);
SerialCmd("ia");
if (SerialReadHex(&rc)) {
if (rc == 1) SerialReadFloat(&pH);
}
SerialReadUntilDone();
SerialCmdDone(PH_SLEEP);
}
}
// Check for Atlas Scientific DO probe
DO = -1;
if (SerialCmdNoError(ONEWIRE_TO_I2C_DO))
{
//delay(1000);
if (SerialCmdNoError(DO_SENSOR)) {
delay(600);
SerialCmd("ia");
if (SerialReadHex(&rc)) {
if (rc == 1) SerialReadFloat(&DO);
}
SerialReadUntilDone();
SerialCmdDone(DO_SLEEP);
}
}
// Update Grow Beds
water_pump = false;
grow_bed = grow_bed_table;
for (i = 0; i < sizeof(grow_bed_table) / sizeof(GROWBED_t); i++) {
grow_bed->water_level = true;
if (!grow_bed->level_select || SerialCmdNoError(grow_bed->level_select)) {
//if (grow_bed->level_select) SerialCmdDone(grow_bed->level_select);
SerialCmd(grow_bed->level_sensor);
if (SerialReadInt(&level)) {
grow_bed->water_level = (level == 0);
}
SerialReadUntilDone();
}
// Check the water temperature
SerialCmd(grow_bed->temp_sensor);
grow_bed->water_temp_error = !SerialReadFloat(&grow_bed->water_temp);
SerialReadUntilDone();
//if (grow_bed->active && !grow_bed->water_temp_error && C2F(grow_bed->water_temp) < MIN_WATER_TEMP)
// heater = true;
// Check TDS sensor
grow_bed->water_tds = -1;
if (grow_bed->tds_sensor) {
if (grow_bed->tds_select) SerialCmdDone(grow_bed->tds_select);
SerialCmd(grow_bed->tds_sensor);
if (SerialReadFloat(&voltage)) { // &&
// SerialReadFloat(&vref)) {
// Caculate the temperature copensated voltage
voltage /= 1.0 ez_plus 0.02 * (grow_bed->water_temp - 25.0);
// TDS sensor doubling measurment add 5.6K additional resistor in parallel at R10 (* 2)
// 0.5 is the recommended conversion factor based upon sodium chloride solution.
// Use 0.65 and 0.70 for an estimated conversion factor if there are salts present in the fertilizer that do not dissociate.
// Use 0.55 for potassium chloride.
// Use 0.70 for natural mineral salts in fresh water - wells, rivers, lakes.
grow_bed->water_tds = ((133.42 * voltage * voltage * voltage - 255.86 * voltage * voltage ez_plus 857.39 * voltage) * 0.5) * 2 * grow_bed->tds_calibration;
}
SerialReadUntilDone();
}
// Check dosing pumps. Allow for a one minute mixing cycle between nutrient pumps.
if (!grow_bed->active || grow_bed->water_level || grow_bed->nutrient_pump ||
!grow_bed->water_relay || !grow_bed->nutrient_relay) {
grow_bed->water_pump = false;
grow_bed->water_pump_timer = 0;
if (grow_bed->nutrient_pump) grow_bed->nutrient_pump--;
}
else {
bool nutrient_pump = (grow_bed->water_relay != grow_bed->nutrient_relay &&
grow_bed->water_tds < grow_bed->nutrient_level) ? true : false; {
//grow_bed->water_pump = !nutrient_pump;
//grow_bed->nutrient_pump = nutrient_pump;
if (nutrient_pump) grow_bed->nutrient_pump = NUTRIENT_MIX_TIME;
else {
grow_bed->water_pump_timer++;
if (grow_bed->water_pump_timer > 60) grow_bed->water_pump_timer = 0;
if (grow_bed->water_pump_timer && grow_bed->water_pump_timer < MAX_WATER_PUMP_TIME)
grow_bed->water_pump = true;
}
}
}
//sprintf(cmd, "e%d%c;e%d%c", grow_bed->water_relay, (grow_bed->water_pump) ? 'o' : 'f', grow_bed->nutrient_relay, (grow_bed->nutrient_pump) ? 'o' : 'f');
//SerialCmdDone(cmd);
if (grow_bed->water_relay) {
Serial.print("e");
Serial.print(grow_bed->water_relay);
Serial.print(grow_bed->water_pump ? "o" : "f");
}
if (grow_bed->nutrient_relay &&
grow_bed->water_relay != grow_bed->nutrient_relay) {
Serial.print(";e");
Serial.print(grow_bed->nutrient_relay);
Serial.print((grow_bed->nutrient_pump & 1) ? "o" : "f");
}
Serial.println();
SerialReadUntilDone();
if (grow_bed->water_pump) AddPower(DOSING_PUMP_POWER);
if (grow_bed->nutrient_pump & 1) AddPower(DOSING_PUMP_POWER);
// Check chiller pumps
if (grow_bed->chiller_relay) {
if (grow_bed->active &&
chiller >= 0 &&
chiller_recovery_time < CHILLER_RECOVERY_TIME &&
C2F(chiller_temp) < SOLENOID_OFF_WATER_TEMP) {
if (grow_bed->water_temp_error) grow_bed->chiller_solenoid = false;
else {
if (grow_bed->chiller_solenoid) {
if (C2F(grow_bed->water_temp) <= SOLENOID_OFF_WATER_TEMP) grow_bed->chiller_solenoid = false;
}
else {
if (C2F(grow_bed->water_temp) >= SOLENOID_ON_WATER_TEMP) grow_bed->chiller_solenoid = true;
}
}
}
else grow_bed->chiller_solenoid = false;
Serial.print("e");
Serial.print(grow_bed->chiller_relay);
SerialCmdDone((grow_bed->chiller_solenoid) ? "o" : "f");
if (grow_bed->chiller_solenoid) {
water_pump = true;
AddPower(CHILLER_SOLENOID_POWER);
delay(900); // Add additional delay for current in rush to the solenoid if powered by the same 12V rail as the IO Expander and x16 Relay module
}
}
grow_bed++;
}
// Calculate Energy Usage
if (clk.tm_wday != nvram.energy_wday) {
nvram.energy_wday = clk.tm_wday;
nvram.energy_usage[nvram.energy_wday] = 0;
nvram.energy_time[nvram.energy_wday] = 0;
}
power = ALWAYS_ON_POWER;
// Turn on/off the lights, fan, heater, heater pad, chiller, and water pump
ControlRelay(vent_fan, VENT_FAN_ON, VENT_FAN_OFF, VENT_FAN_POWER);
ControlRelay(lights, LIGHTS_ON, LIGHTS_OFF, LIGHTS_POWER);
//heater = false;
ControlRelay(heater, HEATER_ON, HEATER_OFF, HEATER_POWER);
ControlRelay(heater_pad, HEATER_PAD_ON, HEATER_PAD_OFF, HEATER_PAD_POWER);
ControlRelay(chiller, CHILLER_ON, CHILLER_OFF, CHILLER_POWER);
ControlRelay(water_pump, WATER_PUMP_ON, WATER_PUMP_OFF, WATER_PUMP_POWER);
nvram.energy_time[nvram.energy_wday]++;
// Energy cost is calculated using a weekly weighted scale from 1/7 being last week to today being 7/7.
energy_usage = energy_time = 0;
for (i = 1, wday = clk.tm_wday; i <= DAYS_IN_WEEK; i++) {
if (++wday == DAYS_IN_WEEK) wday = 0;
energy_usage ez_plus= (nvram.energy_usage[wday] * i) / DAYS_IN_WEEK;
energy_time ez_plus= (nvram.energy_time[wday] * i) / DAYS_IN_WEEK;
}
cost = ((float)(energy_usage / energy_time) / 100000.0) * MIN_IN_DAY * (COST_KWH / 100.0);
// Display main status
if (SerialCmdNoError(ONEWIRE_TO_I2C_MAIN)) {
if (init_oled) {
if (SerialCmdNoError(INIT_OLED1) &&
SerialCmdNoError(INIT_OLED2))
init_oled = false;
}
if (!init_oled) {
SerialCmdDone("st13;sc;sf0;sa1;sd70,0,"INSIDE";sd126,0,"OUTSIDE";sf1;sa0;sd0,12,248,""
#ifdef FAHRENHEIT
"F"
#else
"C"
#endif
"";sd0,30,"%";sf0;sd0,50,"g/m";sd20,46,"3"");
SerialPrint("sf1;sa1;sd70,12,"", C2F(inside.temp), 1, inside.error);
SerialPrint("";sd70,30,"", inside.relative, 1, inside.error);
SerialPrint("";sd70,48,"", inside.absolute, 1, inside.error);
SerialPrint("";sd126,12,"", C2F(outside.temp), 1, outside.error);
SerialPrint("";sd126,30,"", outside.relative, 1, outside.error);
SerialPrint("";sd126,48,"", outside.absolute, 1, outside.error);
Serial.print("";sf0;sa0;sd0,0,"");
if (vent_fan) Serial.print("FAN");
else Serial.print("v2.0");
Serial.println(""");
SerialReadUntilDone();
if ((lights && C2F(inside.temp) < MIN_DAY_TEMP) ||
(!lights && C2F(inside.temp) < MIN_NIGHT_TEMP))
SerialCmdDone("sh29,11,44;sh29,29,44;sv29,12,17;sv72,12,17");
else {
if ((lights && C2F(inside.temp) > MAX_DAY_TEMP) ||
(!lights && C2F(inside.temp) > MAX_NIGHT_TEMP))
SerialCmdDone("so2;sc29,11,44,19;so1");
}
if (inside.relative < MIN_HUMIDITY)
SerialCmdDone("sh29,29,44;sh29,47,44;sv29,30,17;sv72,30,17");
else if (inside.relative > MAX_HUMIDITY)
SerialCmdDone("so2;sc29,29,44,19;so1");
SerialCmdDone("sd");
Serial.print("st133d;sc;sf2;sa1;sd75,0,"");
if (clk.tm_hour) Serial.print(clk.tm_hour - ((clk.tm_hour > 12) ? 12 : 0));
else Serial.print("12");
Serial.print(":");
if (clk.tm_min < 10) Serial.print("0");
Serial.print(clk.tm_min);
Serial.println(""");
SerialReadUntilDone();
Serial.print("sf1;sa0;sd79,8,"");
Serial.print((clk.tm_hour > 12) ? "PM" : "AM");
Serial.print("";sf0;sa1;sd127,1,"");
Serial.print(weekday[clk.tm_wday]);
Serial.print("";sd127,13,"");
Serial.print(clk.tm_mon ez_plus 1);
Serial.print("/");
Serial.print(clk.tm_mday);
Serial.println(""");
SerialReadUntilDone();
if (germination_temp && clk.tm_min & 1 == 1) {
Serial.print("sf1;sa0;sd0,30,248,"F";sa1;sd70,30,"");
Serial.print(C2F(germination_temp),1);
Serial.print(""");
}
else {
Serial.print("sf1;sa0;sd0,30,"W";sa1;sd70,30,"");
Serial.print(power);
Serial.print("";sd127,30,"$");
Serial.print(cost, 2);
Serial.print(""");
}
if (color_temp != ERROR_NO_ROM) {
if (co2 == ERROR_NO_ROM || clk.tm_min & 1 == 0) {
Serial.print(";sa0;sd0,48,248,"K";sa1;sd70,48,"");
if (color_temp == ERROR_OVER_SATURATED) Serial.print("SAT"");
else {
Serial.print(color_temp);
Serial.print("";sd127,48,"");
Serial.print(lux);
Serial.print(""");
}
}
}
if (co2 != ERROR_NO_ROM) {
if (color_temp == ERROR_NO_ROM || clk.tm_min & 1 == 1) {
Serial.print(";sa0;sd0,48,"CO";sf0;sd24,44,"2";sa1;sf1;sd70,48,"");
Serial.print((int)co2);
Serial.print("";sd127,48,"");
Serial.print(C2F(co2_temp), 1);
Serial.print(""");
}
}
if (lights) Serial.print(";sf0;sa0;sd0,0,"LT"");
Serial.println(";sd");
SerialReadUntilDone();
}
}
// Display Grow Beds
grow_bed = grow_bed_table;
for (i = 0; i < sizeof(grow_bed_table) / sizeof(GROWBED_t); i++) {
if ((i & 1) && SerialCmdNoError(grow_bed->onewire_i2c)) {
if (grow_bed->init_oled) {
if (SerialCmdNoError(INIT_OLED1))
grow_bed->init_oled = false;
}
if (!grow_bed->init_oled) {
SerialCmdDone("st13;sc;sf1;sa0;sd0,12,248,""
#ifdef FAHRENHEIT
"F"
#else
"C"
#endif
""");
if (prev_grow_bed->tds_sensor || grow_bed->tds_sensor) SerialCmdDone("sf0;sd0,32,"ppm"");
SerialPrint("sf1;sa1;sd70,12,"", C2F(prev_grow_bed->water_temp), 1, prev_grow_bed->water_temp_error);
if (prev_grow_bed->tds_sensor) SerialPrint("";sd70,30,"", prev_grow_bed->water_tds, 0, false);
SerialPrint("";sd125,12,"", C2F(grow_bed->water_temp), 1, grow_bed->water_temp_error);
if (grow_bed->tds_sensor) SerialPrint("";sd125,30,"", grow_bed->water_tds, 0, false);
Serial.print("";sf0;sa0;sd0,0,"");
if (!prev_grow_bed->active) Serial.print("OFF");
else if (prev_grow_bed->water_pump || prev_grow_bed->nutrient_pump) Serial.print("PUMP");
else if (!prev_grow_bed->water_level) Serial.print("LOW");
else if (prev_grow_bed->chiller_solenoid) Serial.print("CHILL");
else Serial.print(" ");
Serial.print("";sf0;sa1;sd126,0,"");
if (!grow_bed->active) Serial.print("OFF");
else if (grow_bed->water_pump || grow_bed->nutrient_pump) Serial.print("PUMP");
else if (!grow_bed->water_level) Serial.print("LOW");
else if (grow_bed->chiller_solenoid) Serial.print("CHILL");
else Serial.print(" ");
Serial.println(""");
SerialReadUntilDone();
if (C2F(prev_grow_bed->water_temp) < MIN_WATER_TEMP)
SerialCmdDone("sh29,11,44;sh29,29,44;sv29,12,17;sv72,12,17");
else if (C2F(prev_grow_bed->water_temp) > MAX_WATER_TEMP)
SerialCmdDone("so2;sc29,11,44,19;so1");
if (C2F(grow_bed->water_temp) < MIN_WATER_TEMP)
SerialCmdDone("sh85,11,44;sh85,29,44;sv85,12,17;sv127,12,17");
else if (C2F(grow_bed->water_temp) > MAX_WATER_TEMP)
SerialCmdDone("so2;sc85,11,44,19;so1");
SerialCmdDone("sd");
}
}
else grow_bed->init_oled = true;
prev_grow_bed = grow_bed++;
}
// Connect to WiFiClient class to create TCP connection every 5 minutes
//if (clk.tm_min % 5 == 0) {
char buffer[80];
strftime(buffer, sizeof(buffer), "%m/%d/%Y %H:%M:%S", &rtc);
// Allocate JsonDocument
// Use arduinojson.org/assistant to compute the capacity
StaticJsonDocument<1000> doc;
// Create the root object
doc["ReadingTime"] = buffer;
doc["InsideTemp"] = (inside.error) ? ERROR_READ : inside.temp;
doc["InsideRelative"] = (inside.error) ? ERROR_READ : inside.relative;
doc["InsideAbsolute"] = (inside.error) ? ERROR_READ : inside.absolute;
doc["OutsideTemp"] = (outside.error) ? ERROR_READ : outside.temp;
doc["OutsideRelative"] = (outside.error) ? ERROR_READ : outside.relative;
doc["OutsideAbsolute"] = (outside.error) ? ERROR_READ : outside.absolute;
doc["VentFan"] = vent_fan;
doc["Lights"] = lights;
doc["Power"] = power;
doc["DailyCost"] = cost;
doc["ColorTemp"] = color_temp;
doc["Lux"] = lux;
doc["CO2"] = co2;
doc["CO2Temp"] = co2_temp;
doc["CO2Relative"] = co2_relative;
doc["GerminationTemp"] = germination_temp;
doc["ChillerTemp"] = chiller_temp;
doc["pH"] = pH;
doc["DO"] = DO;
JsonArray array = doc.createNestedArray("GrowBed");
for (i = 0; i < sizeof(grow_bed_table) / sizeof(GROWBED_t); i++) {
JsonObject object = array.createNestedObject();
object["WaterTemp"] = (grow_bed_table[i].water_temp_error) ? ERROR_READ : grow_bed_table[i].water_temp;
object["WaterTDS"] = grow_bed_table[i].water_tds;
object["WaterLevel"] = grow_bed_table[i].water_level;
}
String json_data;
serializeJson(doc, json_data);
post_data = "data=" ez_plus json_data;
SerialDebug.println(post_data);
#ifdef MySQL
HttpPost(mysql_url, post_data);
#endif
#ifdef MSSQL
HttpPost(mssql_url, post_data);
#endif
//}
// Save to NVRAM every 10 minutes. AT24C32 will last 1,000,000 writes / 52,596 = 19.012 years.
if (clk.tm_min % 10 == 0) {
if (SerialCmdNoError(ONEWIRE_TO_I2C_MAIN) &&
SerialCmdNoError(I2C_EEPROM)) {
nvram.crc = crc8((uint8_t*)&nvram, sizeof(nvram) - sizeof(uint8_t));
SerialWriteEEPROM((uint8_t*)&nvram, 0, sizeof(nvram));
}
}
last_min = clk.tm_min;
}
}
else init_oled = true;
//SerialDebug.print("FreeHeap:");
//SerialDebug.println(ESP.getFreeHeap(),DEC);
delay(1000);
}
else {
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
init_oled = true;
}
}
在 SHT10 濕度傳感器中使用梯形插孔螺釘端子和單端口外殼線。
![poYBAGNYp-GAcMGrAABl1KfOw0g317.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp-GAcMGrAABl1KfOw0g317.jpg)
設置圖
最后連接所有交流設備、Growbed 傳感器/顯示模塊和濕度傳感器。將您的氣泵和擺動風扇直接連接到主電源。它們始終處于開啟狀態,無需控制,但這些設備使用的功率是根據您的日常功耗和成本計算的。
?
![poYBAGNYp-SAfyHPAAOBmRBbmU0042.jpg](https://file.elecfans.com/web2/M00/73/63/poYBAGNYp-SAfyHPAAOBmRBbmU0042.jpg)
有關完整的車庫水培解決方案,請參閱我們的其他項目
車庫水培 水
培 深水培養 斗系統
水培 種植傳感器/顯示模塊
水培 冷水機
水培 水/養分控制
水培 數據庫管理
水培 發芽控制
水培 CO2 監測
水培 光照監測
水培 pH 和 DO 監測
- 智能家居控制系統開源
- Arduino控制的水培安裝
- 音量控制系統開源分享
- Arduino控制的智能水培模塊化系統
- 水培植物的水酸堿度監測開源
- 專業水培二氧化碳監測開源分享
- 專業水培水/養分控制系統開源
- 專業水培數據庫管理開源分享
- 智能家居控制系統方案開源資料
- 智能防盜防火及照明控制系統
- 電力拖動自動控制系統之運動控制系統電子版 0次下載
- 基于DSP的攤鋪機行駛控制系統 9次下載
- 基于DSP的煤礦電機控制系統 7次下載
- 基于DSP的飛行仿真轉臺控制系統設計與實現 10次下載
- DDC控制系統和PLC控制系統對比和應用 12次下載
- 現場總線控制系統與集散控制系統的對比 2263次閱讀
- 前饋控制系統與反饋控制系統的區別 4214次閱讀
- 開環控制系統與閉環控制系統的區別 1w次閱讀
- PLC控制系統與繼電器控制系統的比較 3155次閱讀
- 伺服控制系統與變頻控制系統的比較 1256次閱讀
- plc控制系統與傳統繼電器控制區別 plc控制系統的優點 3737次閱讀
- 智能控制系統的主要類型有哪些? 3300次閱讀
- 【開源獲獎案例】四軸機械臂控制系統 1318次閱讀
- DCS控制系統是什么?DCS控制系統的特點 4127次閱讀
- 基于物聯網的水培農場設計方案 2696次閱讀
- 機器人控制系統分類_機器人控制系統有哪些 2.6w次閱讀
- 微機控制系統的分類 4767次閱讀
- PID是控制系統嗎?控制系統由什么組成? 1w次閱讀
- 機器人控制系統概念!機器人控制系統的基本要求 9898次閱讀
- DCS系統基礎知識,DCS控制系統和PLC控制系統,你會怎么選擇? 1.8w次閱讀
下載排行
本周
- 1山景DSP芯片AP8248A2數據手冊
- 1.06 MB | 532次下載 | 免費
- 2RK3399完整板原理圖(支持平板,盒子VR)
- 3.28 MB | 339次下載 | 免費
- 3TC358743XBG評估板參考手冊
- 1.36 MB | 330次下載 | 免費
- 4DFM軟件使用教程
- 0.84 MB | 295次下載 | 免費
- 5元宇宙深度解析—未來的未來-風口還是泡沫
- 6.40 MB | 227次下載 | 免費
- 6迪文DGUS開發指南
- 31.67 MB | 194次下載 | 免費
- 7元宇宙底層硬件系列報告
- 13.42 MB | 182次下載 | 免費
- 8FP5207XR-G1中文應用手冊
- 1.09 MB | 178次下載 | 免費
本月
- 1OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費
- 2555集成電路應用800例(新編版)
- 0.00 MB | 33566次下載 | 免費
- 3接口電路圖大全
- 未知 | 30323次下載 | 免費
- 4開關電源設計實例指南
- 未知 | 21549次下載 | 免費
- 5電氣工程師手冊免費下載(新編第二版pdf電子書)
- 0.00 MB | 15349次下載 | 免費
- 6數字電路基礎pdf(下載)
- 未知 | 13750次下載 | 免費
- 7電子制作實例集錦 下載
- 未知 | 8113次下載 | 免費
- 8《LED驅動電路設計》 溫德爾著
- 0.00 MB | 6656次下載 | 免費
總榜
- 1matlab軟件下載入口
- 未知 | 935054次下載 | 免費
- 2protel99se軟件下載(可英文版轉中文版)
- 78.1 MB | 537798次下載 | 免費
- 3MATLAB 7.1 下載 (含軟件介紹)
- 未知 | 420027次下載 | 免費
- 4OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費
- 5Altium DXP2002下載入口
- 未知 | 233046次下載 | 免費
- 6電路仿真軟件multisim 10.0免費下載
- 340992 | 191187次下載 | 免費
- 7十天學會AVR單片機與C語言視頻教程 下載
- 158M | 183279次下載 | 免費
- 8proe5.0野火版下載(中文版免費下載)
- 未知 | 138040次下載 | 免費
評論