Золотой Лось ОЛК: нет.
|
Текущее время: 18.04.24 14:35 |
на страницу... 1 2 3
Версия для печати |
Сообщение
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Минимальный демон написан: начну с версии 2.0. Задействовано: БП ATX, камера, свет, вентиляция, температура. Пока демон ещё не опробован в услових реального выращивания.
Придумал условия алгоритма климат-контроля:
-при включении демона делается фото, если свет выключен - включается на время создания фото;
-есть четыре параметра температуры:
--минимальная = 16 град. - ниже этого предела вентилляция всегда выключается
--максимальная = 32 град. - выше этого предела всегда выключается освещение
--нормальная минимальная = 26 град. если меньше - понемногу уменьшается вентилляция.
--нормальная максимальная = 28 град. если больше - понемногу увеличивается вентилляция.
-прстой между периодами просчитывания автоматики = 1 минута.
-освещение... естественно по графику 18/6
Проведу ещё несколько тестов, а потом выложу код.
Придумал условия алгоритма климат-контроля:
-при включении демона делается фото, если свет выключен - включается на время создания фото;
-есть четыре параметра температуры:
--минимальная = 16 град. - ниже этого предела вентилляция всегда выключается
--максимальная = 32 град. - выше этого предела всегда выключается освещение
--нормальная минимальная = 26 град. если меньше - понемногу уменьшается вентилляция.
--нормальная максимальная = 28 град. если больше - понемногу увеличивается вентилляция.
-прстой между периодами просчитывания автоматики = 1 минута.
-освещение... естественно по графику 18/6
Проведу ещё несколько тестов, а потом выложу код.
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Поздравляю с положеным началом!
Вот эти условия по факту к реальному грову не очень применимы имхо - лучше 33-35 градусов на несколько часов, чем мигающий свет. То же самое про включение света при съемки фото.
А зачем делать фото при включении демона? Просто задаешь условие, например, каждую первую минуту часа снимать фото, если релеха освещения включена.
Цитата:
-при включении демона делается фото, если свет выключен - включается на время создания фото/quote]
Цитата:
--максимальная = 32 град. - выше этого предела всегда выключается освещение
Вот эти условия по факту к реальному грову не очень применимы имхо - лучше 33-35 градусов на несколько часов, чем мигающий свет. То же самое про включение света при съемки фото.
А зачем делать фото при включении демона? Просто задаешь условие, например, каждую первую минуту часа снимать фото, если релеха освещения включена.
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Цитата:
А зачем делать фото при включении демона? Просто задаешь условие, например, каждую первую минуту часа снимать фото, если релеха освещения включена.
Свет что-то часто отключали, хотел чтобы снимало при включении электричества, надо над этим ещё подумать.
Демон (сервис) 2.0
Тестить в реальных (садовых) условиях сейчас нет возможности (холодно). Выкладываю "как есть".
Код для каждого устройства разместил в отдельных файлах в виде классов - демон во время работы всех их подключает. Но также почти каждый файл можно использовать как самостоятельную программу, которую можно запускать отдельно.
Резместил демон в папке /tumbox/svc
Снимки складываются в папке /tumbox/svc/photo
Также у классов есть некоторая защита от многопоточного достуа.
Принцип работы
Сервис через автозагрузку включается при старте Оранжа. Сервис работает в два потока: один следит за климатом, второй через TCP/IP обрабатывает клиентские запросы (команды).
atx.py - блок питания ATX
Код:
#!/usr/bin/env python
import threading
from pyA20.gpio import gpio
from pyA20.gpio import port
class Atx:
LOCK = threading.RLock()
Port = port.PA7
Current = False
def __init__(self):
gpio.setcfg(self.Port, gpio.OUTPUT)
def Set(self, NewValue):
OutLevel = gpio.LOW
if NewValue:
OutLevel = gpio.HIGH
self.LOCK.acquire()
gpio.output(self.Port, OutLevel)
self.Current = NewValue
self.LOCK.release()
def Get(self):
self.LOCK.acquire()
self.Current = gpio.input(self.Port)
Cur = self.Current
self.LOCK.release()
return Cur
def On(self):
self.Set(True)
def Off(self):
self.Set(False)
if __name__ == "__main__":
import sys
gpio.init()
atx = Atx()
if len(sys.argv) >= 2:
StringCommand = sys.argv[1]
if StringCommand == "on":
atx.On()
print("ATX set on")
elif StringCommand == "off":
atx.Off()
print("ATX set off")
else:
print("Unknown command: %s", StringCommand)
else:
if atx.Get():
print("ATX is on")
else:
print("ATX is off")
import threading
from pyA20.gpio import gpio
from pyA20.gpio import port
class Atx:
LOCK = threading.RLock()
Port = port.PA7
Current = False
def __init__(self):
gpio.setcfg(self.Port, gpio.OUTPUT)
def Set(self, NewValue):
OutLevel = gpio.LOW
if NewValue:
OutLevel = gpio.HIGH
self.LOCK.acquire()
gpio.output(self.Port, OutLevel)
self.Current = NewValue
self.LOCK.release()
def Get(self):
self.LOCK.acquire()
self.Current = gpio.input(self.Port)
Cur = self.Current
self.LOCK.release()
return Cur
def On(self):
self.Set(True)
def Off(self):
self.Set(False)
if __name__ == "__main__":
import sys
gpio.init()
atx = Atx()
if len(sys.argv) >= 2:
StringCommand = sys.argv[1]
if StringCommand == "on":
atx.On()
print("ATX set on")
elif StringCommand == "off":
atx.Off()
print("ATX set off")
else:
print("Unknown command: %s", StringCommand)
else:
if atx.Get():
print("ATX is on")
else:
print("ATX is off")
При самомтоятельном выволнении:
без команы - показывает текущее сотояние включённости БП "on" или "off"
с командой "on" или "off" - включает/выключает ATX.
По такому принципу действуют и другие файлы устройтв.
light.py - управление светом
Код:
#!/usr/bin/env python
import datetime, threading
from pyA20.gpio import gpio
from pyA20.gpio import port
class Light:
LOCK = threading.RLock()
LOCK_Update = threading.RLock()
Port = port.PC4
Current = None
TemperatureObject = None
OnHour = 5
LightHours = 18
Automode = "off" #on, off, auto
def __init__(self, TemperatureObject = None):
self.TemperatureObject = TemperatureObject
gpio.setcfg(self.Port, gpio.OUTPUT)
def Set(self, NewValue):
self.LOCK.acquire()
OutLevel = gpio.LOW
if NewValue:
OutLevel = gpio.HIGH
gpio.output(self.Port, OutLevel)
self.Current = NewValue
self.LOCK.release()
def Get(self):
self.LOCK.acquire()
self.Current = gpio.input(self.Port)
Cur = self.Current
self.LOCK.release()
return Cur
def On(self):
self.Set(True)
def Off(self):
self.Set(False)
def Update(self):
self.LOCK_Update.acquire()
if self.Automode == "on":
self.On()
elif self.Automode == "off":
self.Off()
elif self.Automode == "auto":
# hard regulation
CurrentTem = self.TemperatureObject.GetCurrent()
if CurrentTem != None:
if CurrentTem.Temperature > self.TemperatureObject.GetMaxTem():
self.Off()
self.LOCK_Update.release()
return
# check hours
if self.LightHours >= 24 or self.OnHour >= 24:
self.On()
else:
OffHour = self.OnHour + self.LightHours
now = datetime.datetime.now()
if OffHour >= 24:
OffHour -= 24
# specific set light
if now.hour >= self.OnHour or now.hour < OffHour:
self.On()
else:
self.Off()
else:
# set light
if now.hour >= self.OnHour and now.hour < OffHour:
self.On()
else:
self.Off()
self.LOCK_Update.release()
return self.GetCurrent()
def GetCurrent(self):
self.LOCK.acquire()
Cur = self.Current
self.LOCK.release()
return Cur
def GetAutomode(self):
self.LOCK_Update.acquire()
Cur = self.Automode
self.LOCK_Update.release()
return Cur
def SetAutomode(self, NewMode):
self.LOCK_Update.acquire()
self.Automode = NewMode
self.LOCK_Update.release()
if __name__ == "__main__":
import sys
gpio.init()
light = Light()
if len(sys.argv) >= 2:
StringCommand = sys.argv[1]
if StringCommand == "on":
light.On()
print("Light set on")
elif StringCommand == "off":
light.Off()
print("Light set off")
else:
print("Unknown command: %s", StringCommand)
else:
if light.Get():
print("Light is on")
else:
print("Light is off")
import datetime, threading
from pyA20.gpio import gpio
from pyA20.gpio import port
class Light:
LOCK = threading.RLock()
LOCK_Update = threading.RLock()
Port = port.PC4
Current = None
TemperatureObject = None
OnHour = 5
LightHours = 18
Automode = "off" #on, off, auto
def __init__(self, TemperatureObject = None):
self.TemperatureObject = TemperatureObject
gpio.setcfg(self.Port, gpio.OUTPUT)
def Set(self, NewValue):
self.LOCK.acquire()
OutLevel = gpio.LOW
if NewValue:
OutLevel = gpio.HIGH
gpio.output(self.Port, OutLevel)
self.Current = NewValue
self.LOCK.release()
def Get(self):
self.LOCK.acquire()
self.Current = gpio.input(self.Port)
Cur = self.Current
self.LOCK.release()
return Cur
def On(self):
self.Set(True)
def Off(self):
self.Set(False)
def Update(self):
self.LOCK_Update.acquire()
if self.Automode == "on":
self.On()
elif self.Automode == "off":
self.Off()
elif self.Automode == "auto":
# hard regulation
CurrentTem = self.TemperatureObject.GetCurrent()
if CurrentTem != None:
if CurrentTem.Temperature > self.TemperatureObject.GetMaxTem():
self.Off()
self.LOCK_Update.release()
return
# check hours
if self.LightHours >= 24 or self.OnHour >= 24:
self.On()
else:
OffHour = self.OnHour + self.LightHours
now = datetime.datetime.now()
if OffHour >= 24:
OffHour -= 24
# specific set light
if now.hour >= self.OnHour or now.hour < OffHour:
self.On()
else:
self.Off()
else:
# set light
if now.hour >= self.OnHour and now.hour < OffHour:
self.On()
else:
self.Off()
self.LOCK_Update.release()
return self.GetCurrent()
def GetCurrent(self):
self.LOCK.acquire()
Cur = self.Current
self.LOCK.release()
return Cur
def GetAutomode(self):
self.LOCK_Update.acquire()
Cur = self.Automode
self.LOCK_Update.release()
return Cur
def SetAutomode(self, NewMode):
self.LOCK_Update.acquire()
self.Automode = NewMode
self.LOCK_Update.release()
if __name__ == "__main__":
import sys
gpio.init()
light = Light()
if len(sys.argv) >= 2:
StringCommand = sys.argv[1]
if StringCommand == "on":
light.On()
print("Light set on")
elif StringCommand == "off":
light.Off()
print("Light set off")
else:
print("Unknown command: %s", StringCommand)
else:
if light.Get():
print("Light is on")
else:
print("Light is off")
Настройки:
OnHour - о котором часу включать свет
LightHours - количество часов работы
Возможна недоделка: OnHour + LightHours не должно привышать число 24.
tem.py - датчик температуры и влажности DHT11
Код:
!/usr/bin/env python
# v.1.2017.5.6
from pyA20.gpio import gpio
from pyA20.gpio import port
import sys, time, datetime, threading
sys.path.append("/tumbox/DHT11_Python")
import dht11
class TemResult:
Temperature = 0
Humidity = 0
class Tem:
LOCK = threading.RLock()
Port = port.PA1
Instance = None
Current = None
LastNiceCurrent = None
LastNiceCurrentTime = None
AsDaemon = False
# Check parameters
Skip = 1
Take = 3
TryCount = 10
Pause = 2
# Source for automation
MinTem = 16
MaxTem = 32
NormalMinTem = 26
NormalMaxTem = 28
def __init__(self, AsDaemon):
self.AsDaemon = AsDaemon
self.Instance = dht11.DHT11(pin = self.Port)
def Read(self):
self.Update()
self.LOCK.acquire()
Cur = self.Current
self.LOCK.release()
return Cur
def GetLastNiceCurrent(self):
self.LOCK.acquire()
Cur = self.LastNiceCurrent
self.LOCK.release()
return Cur
def GetLastNiceCurrentTime(self):
self.LOCK.acquire()
Cur = self.LastNiceCurrentTime
self.LOCK.release()
return Cur
def Update(self):
self.LOCK.acquire()
self.Current = None
CurrentTryCount = 0
# skip
Count = 0
while (Count < self.Skip) and (CurrentTryCount <= self.TryCount):
result = self.Instance.read()
if result.is_valid():
Count +=1
if self.AsDaemon == False:
print("Tem.Update():skip:OK %d C" % result.temperature)
else:
if self.AsDaemon == False:
print("Tem.Update():skip:ERROR")
CurrentTryCount += 1
time.sleep(self.Pause)
if CurrentTryCount > self.TryCount:
self.LOCK.release()
return
# take
Count = 0
ResultsTem = []
ResultsHum = []
while (Count < self.Take) and (CurrentTryCount <= self.TryCount):
result = self.Instance.read()
if result.is_valid():
Count +=1
ResultsTem.append(result.temperature)
ResultsHum.append(result.humidity)
if self.AsDaemon == False:
print("Tem.Update():take:OK %d C" % result.temperature)
else:
if self.AsDaemon == False:
print("Tem.Update():take:ERROR")
CurrentTryCount += 1
time.sleep(self.Pause)
if CurrentTryCount > self.TryCount and len(ResultsTem)==0:
self.LOCK.release()
return
# calculate results
Result = TemResult()
for OneTem in ResultsTem:
Result.Temperature += OneTem
Result.Temperature /= len(ResultsTem)
for OneTem in ResultsHum:
Result.Humidity += OneTem
Result.Humidity /= len(ResultsHum)
self.Current = Result
# nice result
self.LastNiceCurrent = Result
self.LastNiceCurrentTime = datetime.datetime.now()
self.LOCK.release()
def GetMinTem(self):
self.LOCK.acquire()
Cur = self.MinTem
self.LOCK.release()
return Cur
def GetMaxTem(self):
self.LOCK.acquire()
Cur = self.MaxTem
self.LOCK.release()
return Cur
def GetNormalMinTem(self):
self.LOCK.acquire()
Cur = self.NormalMinTem
self.LOCK.release()
return Cur
def GetNormalMaxTem(self):
self.LOCK.acquire()
Cur = self.NormalMaxTem
self.LOCK.release()
return Cur
def GetCurrent(self):
self.LOCK.acquire()
Cur = self.Current
self.LOCK.release()
return Cur
if __name__ == "__main__":
gpio.init()
tem = Tem(False)
result = tem.Instance.read()
if result.is_valid():
print("Temperature: %d C" % result.temperature)
print("Humidity: %d %%" % result.humidity)
else:
print("Read ERROR")
# v.1.2017.5.6
from pyA20.gpio import gpio
from pyA20.gpio import port
import sys, time, datetime, threading
sys.path.append("/tumbox/DHT11_Python")
import dht11
class TemResult:
Temperature = 0
Humidity = 0
class Tem:
LOCK = threading.RLock()
Port = port.PA1
Instance = None
Current = None
LastNiceCurrent = None
LastNiceCurrentTime = None
AsDaemon = False
# Check parameters
Skip = 1
Take = 3
TryCount = 10
Pause = 2
# Source for automation
MinTem = 16
MaxTem = 32
NormalMinTem = 26
NormalMaxTem = 28
def __init__(self, AsDaemon):
self.AsDaemon = AsDaemon
self.Instance = dht11.DHT11(pin = self.Port)
def Read(self):
self.Update()
self.LOCK.acquire()
Cur = self.Current
self.LOCK.release()
return Cur
def GetLastNiceCurrent(self):
self.LOCK.acquire()
Cur = self.LastNiceCurrent
self.LOCK.release()
return Cur
def GetLastNiceCurrentTime(self):
self.LOCK.acquire()
Cur = self.LastNiceCurrentTime
self.LOCK.release()
return Cur
def Update(self):
self.LOCK.acquire()
self.Current = None
CurrentTryCount = 0
# skip
Count = 0
while (Count < self.Skip) and (CurrentTryCount <= self.TryCount):
result = self.Instance.read()
if result.is_valid():
Count +=1
if self.AsDaemon == False:
print("Tem.Update():skip:OK %d C" % result.temperature)
else:
if self.AsDaemon == False:
print("Tem.Update():skip:ERROR")
CurrentTryCount += 1
time.sleep(self.Pause)
if CurrentTryCount > self.TryCount:
self.LOCK.release()
return
# take
Count = 0
ResultsTem = []
ResultsHum = []
while (Count < self.Take) and (CurrentTryCount <= self.TryCount):
result = self.Instance.read()
if result.is_valid():
Count +=1
ResultsTem.append(result.temperature)
ResultsHum.append(result.humidity)
if self.AsDaemon == False:
print("Tem.Update():take:OK %d C" % result.temperature)
else:
if self.AsDaemon == False:
print("Tem.Update():take:ERROR")
CurrentTryCount += 1
time.sleep(self.Pause)
if CurrentTryCount > self.TryCount and len(ResultsTem)==0:
self.LOCK.release()
return
# calculate results
Result = TemResult()
for OneTem in ResultsTem:
Result.Temperature += OneTem
Result.Temperature /= len(ResultsTem)
for OneTem in ResultsHum:
Result.Humidity += OneTem
Result.Humidity /= len(ResultsHum)
self.Current = Result
# nice result
self.LastNiceCurrent = Result
self.LastNiceCurrentTime = datetime.datetime.now()
self.LOCK.release()
def GetMinTem(self):
self.LOCK.acquire()
Cur = self.MinTem
self.LOCK.release()
return Cur
def GetMaxTem(self):
self.LOCK.acquire()
Cur = self.MaxTem
self.LOCK.release()
return Cur
def GetNormalMinTem(self):
self.LOCK.acquire()
Cur = self.NormalMinTem
self.LOCK.release()
return Cur
def GetNormalMaxTem(self):
self.LOCK.acquire()
Cur = self.NormalMaxTem
self.LOCK.release()
return Cur
def GetCurrent(self):
self.LOCK.acquire()
Cur = self.Current
self.LOCK.release()
return Cur
if __name__ == "__main__":
gpio.init()
tem = Tem(False)
result = tem.Instance.read()
if result.is_valid():
print("Temperature: %d C" % result.temperature)
print("Humidity: %d %%" % result.humidity)
else:
print("Read ERROR")
Настройки получения замера:
Skip = 1 - сколько первых замеров пропускать, первый замер может оставаться из предыдущего раза
Take = 3 - сколько успешных замеров должно быть получено для успешного результата
TryCount = 10 - количество попыток получения замеров, 10 - производиться максимум десять замеров, если успешных будет 4 (1 + 3) - значит общий замер удался.
Pause = 2 - пауза в секундах между попытками получения замеров.
Настройки для автоматизации:
MinTem - минимально допустимая температура, текущая меньше её - значит пипец - выключение вентилляции.
MaxTem - максимально допустимая температура, текущая больше её - опять пипец - выключение освещения.
NormalMinTem/NormalMaxTem - диапазон температур считающихся нормальным (удерживается оборотами вентиляции).
vent.py - вентиляция
Код:
#!/usr/bin/env python
# v1.2017.5.6
import threading
from pyA20.gpio import gpio
from pyA20.gpio import port
import tem
class Vent:
LOCK = threading.RLock()
LOCK_Update = threading.RLock()
#LOCK_GetMinMaxSpeed = threading.RLock()
WentPort1 = port.PG8
WentPort2 = port.PG9
WentPort3 = port.PG6
WentPort4 = port.PG7
CurrentSpeed = 0
MinSpeed = 5
MaxSpeed = 15
TemperatureObject = None
Automode = "manual" #manual, auto
def __init__(self, TemperatureObject = None):
self.TemperatureObject = TemperatureObject
gpio.setcfg(self.WentPort1, gpio.OUTPUT)
gpio.setcfg(self.WentPort2, gpio.OUTPUT)
gpio.setcfg(self.WentPort3, gpio.OUTPUT)
gpio.setcfg(self.WentPort4, gpio.OUTPUT)
def SetSpeed(self, NewSpeed):
self.LOCK.acquire()
if NewSpeed != 0 and (NewSpeed < self.MinSpeed or NewSpeed > self.MaxSpeed):
self.LOCK.release()
return False
self.CurrentSpeed = NewSpeed
# WentPort1
if self.CurrentSpeed & 1 == 1:
gpio.output(self.WentPort1, gpio.HIGH)
else:
gpio.output(self.WentPort1, gpio.LOW)
# WentPort2
if self.CurrentSpeed & 2 == 2:
gpio.output(self.WentPort2, gpio.HIGH)
else:
gpio.output(self.WentPort2, gpio.LOW)
# WentPort3
if self.CurrentSpeed & 4 == 4:
gpio.output(self.WentPort3, gpio.HIGH)
else:
gpio.output(self.WentPort3, gpio.LOW)
# WentPort4
if self.CurrentSpeed & 8 == 8:
gpio.output(self.WentPort4, gpio.HIGH)
else:
gpio.output(self.WentPort4, gpio.LOW)
self.LOCK.release()
return True
def GetSpeed(self):
self.LOCK.acquire()
self.CurrentSpeed = 0
if gpio.input(self.WentPort1):
self.CurrentSpeed += 1
if gpio.input(self.WentPort2):
self.CurrentSpeed += 2
if gpio.input(self.WentPort3):
self.CurrentSpeed += 4
if gpio.input(self.WentPort4):
self.CurrentSpeed += 8
Cur = self.CurrentSpeed
self.LOCK.release()
return Cur
def Update(self):
self.LOCK_Update.acquire()
if self.Automode == "auto":
CurrentTem = self.TemperatureObject.GetCurrent()
if CurrentTem != None:
NewSpeed = self.GetSpeed()
# hard regulation
if CurrentTem.Temperature < self.TemperatureObject.GetMinTem():
NewSpeed = 0
else:
# delicat regulation
if CurrentTem.Temperature < self.TemperatureObject.GetNormalMinTem():
if NewSpeed > self.MinSpeed:
NewSpeed -= 1
elif CurrentTem.Temperature > self.TemperatureObject.GetNormalMaxTem():
if NewSpeed < self.MaxSpeed:
NewSpeed += 1
if NewSpeed == 1:
NewSpeed = self.MinSpeed
self.SetSpeed(NewSpeed)
self.LOCK_Update.release()
def GetAutomode(self):
self.LOCK_Update.acquire()
Cur = self.Automode
self.LOCK_Update.release()
return Cur
def SetAutomode(self, NewMode):
self.LOCK_Update.acquire()
self.Automode = NewMode
self.LOCK_Update.release()
def GetMinSpeed(self):
self.LOCK_Update.acquire()
Cur = self.MinSpeed
self.LOCK_Update.release()
return Cur
def GetMaxSpeed(self):
self.LOCK_Update.acquire()
Cur = self.MaxSpeed
self.LOCK_Update.release()
return Cur
if __name__ == "__main__":
import sys
gpio.init()
vent = Vent()
if len(sys.argv) >= 2:
NewSpeed = int(sys.argv[1])
print("Vent set speed - %r" % vent.SetSpeed(NewSpeed))
else:
print("Vent speed: %d" % vent.GetSpeed())
# v1.2017.5.6
import threading
from pyA20.gpio import gpio
from pyA20.gpio import port
import tem
class Vent:
LOCK = threading.RLock()
LOCK_Update = threading.RLock()
#LOCK_GetMinMaxSpeed = threading.RLock()
WentPort1 = port.PG8
WentPort2 = port.PG9
WentPort3 = port.PG6
WentPort4 = port.PG7
CurrentSpeed = 0
MinSpeed = 5
MaxSpeed = 15
TemperatureObject = None
Automode = "manual" #manual, auto
def __init__(self, TemperatureObject = None):
self.TemperatureObject = TemperatureObject
gpio.setcfg(self.WentPort1, gpio.OUTPUT)
gpio.setcfg(self.WentPort2, gpio.OUTPUT)
gpio.setcfg(self.WentPort3, gpio.OUTPUT)
gpio.setcfg(self.WentPort4, gpio.OUTPUT)
def SetSpeed(self, NewSpeed):
self.LOCK.acquire()
if NewSpeed != 0 and (NewSpeed < self.MinSpeed or NewSpeed > self.MaxSpeed):
self.LOCK.release()
return False
self.CurrentSpeed = NewSpeed
# WentPort1
if self.CurrentSpeed & 1 == 1:
gpio.output(self.WentPort1, gpio.HIGH)
else:
gpio.output(self.WentPort1, gpio.LOW)
# WentPort2
if self.CurrentSpeed & 2 == 2:
gpio.output(self.WentPort2, gpio.HIGH)
else:
gpio.output(self.WentPort2, gpio.LOW)
# WentPort3
if self.CurrentSpeed & 4 == 4:
gpio.output(self.WentPort3, gpio.HIGH)
else:
gpio.output(self.WentPort3, gpio.LOW)
# WentPort4
if self.CurrentSpeed & 8 == 8:
gpio.output(self.WentPort4, gpio.HIGH)
else:
gpio.output(self.WentPort4, gpio.LOW)
self.LOCK.release()
return True
def GetSpeed(self):
self.LOCK.acquire()
self.CurrentSpeed = 0
if gpio.input(self.WentPort1):
self.CurrentSpeed += 1
if gpio.input(self.WentPort2):
self.CurrentSpeed += 2
if gpio.input(self.WentPort3):
self.CurrentSpeed += 4
if gpio.input(self.WentPort4):
self.CurrentSpeed += 8
Cur = self.CurrentSpeed
self.LOCK.release()
return Cur
def Update(self):
self.LOCK_Update.acquire()
if self.Automode == "auto":
CurrentTem = self.TemperatureObject.GetCurrent()
if CurrentTem != None:
NewSpeed = self.GetSpeed()
# hard regulation
if CurrentTem.Temperature < self.TemperatureObject.GetMinTem():
NewSpeed = 0
else:
# delicat regulation
if CurrentTem.Temperature < self.TemperatureObject.GetNormalMinTem():
if NewSpeed > self.MinSpeed:
NewSpeed -= 1
elif CurrentTem.Temperature > self.TemperatureObject.GetNormalMaxTem():
if NewSpeed < self.MaxSpeed:
NewSpeed += 1
if NewSpeed == 1:
NewSpeed = self.MinSpeed
self.SetSpeed(NewSpeed)
self.LOCK_Update.release()
def GetAutomode(self):
self.LOCK_Update.acquire()
Cur = self.Automode
self.LOCK_Update.release()
return Cur
def SetAutomode(self, NewMode):
self.LOCK_Update.acquire()
self.Automode = NewMode
self.LOCK_Update.release()
def GetMinSpeed(self):
self.LOCK_Update.acquire()
Cur = self.MinSpeed
self.LOCK_Update.release()
return Cur
def GetMaxSpeed(self):
self.LOCK_Update.acquire()
Cur = self.MaxSpeed
self.LOCK_Update.release()
return Cur
if __name__ == "__main__":
import sys
gpio.init()
vent = Vent()
if len(sys.argv) >= 2:
NewSpeed = int(sys.argv[1])
print("Vent set speed - %r" % vent.SetSpeed(NewSpeed))
else:
print("Vent speed: %d" % vent.GetSpeed())
Настройки:
MinSpeed = 5 - минимальная скорость, меньше этой скорости мои кулера не крутиться
MaxSpeed = 15 - максимальная скорость, больше просто нельзя задать аппаратно.
cam.py - камера, создание снимков
Код:
#!/usr/bin/env python
import subprocess, datetime, time, os, glob
class Cam:
LightObject = None
CameraDevice = None
MakePhotoCommand = "ffmpeg -t 1 -f video4linux2 -s 640x480 -r 10 -i %s -f image2 %swebcam1_%%05d.png"
PhotoDirectory = "/tumbox/svc/photo/"
PhotoTempDirectory = "/tumbox/svc/photo/temp/"
def __init__(self, LightObject = None, CameraDevice = "/dev/video0"):
self.LightObject = LightObject
self.CameraDevice = CameraDevice
def Shot(self, LightAutoOn = False):
# if camera device not exists
if os.path.isfile(self.CameraDevice) == False:
return False
# check now time
now = datetime.datetime.now()
# auto on light
AutoOn = False
if self.LightObject != None and LightAutoOn and self.LightObject.Get() == False:
AutoOn = True
self.LightObject.On()
time.sleep(2) #delay for power on
# making temp shots
if os.path.isdir(self.PhotoTempDirectory) == False:
os.makedirs(self.PhotoTempDirectory)
ShellCommand = self.MakePhotoCommand % (self.PhotoTempDirectory, self.CameraDevice)
subprocess.call(ShellCommand, shell=True)
# check exists photo directory
CurrentPhotoDirectory = self.PhotoDirectory + now.strftime("%Y/%m/")
if os.path.isdir(CurrentPhotoDirectory) == False:
os.makedirs(CurrentPhotoDirectory)
# check target file if exists
TargetFile = CurrentPhotoDirectory + "shot_%s.png" % now.strftime("%d_%H-%M-%S")
while os.path.isfile(TargetFile) == True:
now = datetime.datetime.now()
TargetFile = CurrentPhotoDirectory + "shot_%s.png" % now.strftime("%d_%H-%M-%S")
# copy last shot to photo directory
SourceFile = self.PhotoTempDirectory + "webcam1_00010.png"
if os.path.isfile(SourceFile) == True:
ShellCommand = "mv %s %s" % (SourceFile, TargetFile)
subprocess.call(ShellCommand, shell=True)
# delete temp shots
for file in glob.glob(self.PhotoTempDirectory + "*"):
os.remove(file)
else:
TargetFile = False
# auto off light
if AutoOn:
self.LightObject.Off()
return TargetFile
if __name__ == "__main__":
cam = Cam()
print("New shot: " + cam.Shot())
import subprocess, datetime, time, os, glob
class Cam:
LightObject = None
CameraDevice = None
MakePhotoCommand = "ffmpeg -t 1 -f video4linux2 -s 640x480 -r 10 -i %s -f image2 %swebcam1_%%05d.png"
PhotoDirectory = "/tumbox/svc/photo/"
PhotoTempDirectory = "/tumbox/svc/photo/temp/"
def __init__(self, LightObject = None, CameraDevice = "/dev/video0"):
self.LightObject = LightObject
self.CameraDevice = CameraDevice
def Shot(self, LightAutoOn = False):
# if camera device not exists
if os.path.isfile(self.CameraDevice) == False:
return False
# check now time
now = datetime.datetime.now()
# auto on light
AutoOn = False
if self.LightObject != None and LightAutoOn and self.LightObject.Get() == False:
AutoOn = True
self.LightObject.On()
time.sleep(2) #delay for power on
# making temp shots
if os.path.isdir(self.PhotoTempDirectory) == False:
os.makedirs(self.PhotoTempDirectory)
ShellCommand = self.MakePhotoCommand % (self.PhotoTempDirectory, self.CameraDevice)
subprocess.call(ShellCommand, shell=True)
# check exists photo directory
CurrentPhotoDirectory = self.PhotoDirectory + now.strftime("%Y/%m/")
if os.path.isdir(CurrentPhotoDirectory) == False:
os.makedirs(CurrentPhotoDirectory)
# check target file if exists
TargetFile = CurrentPhotoDirectory + "shot_%s.png" % now.strftime("%d_%H-%M-%S")
while os.path.isfile(TargetFile) == True:
now = datetime.datetime.now()
TargetFile = CurrentPhotoDirectory + "shot_%s.png" % now.strftime("%d_%H-%M-%S")
# copy last shot to photo directory
SourceFile = self.PhotoTempDirectory + "webcam1_00010.png"
if os.path.isfile(SourceFile) == True:
ShellCommand = "mv %s %s" % (SourceFile, TargetFile)
subprocess.call(ShellCommand, shell=True)
# delete temp shots
for file in glob.glob(self.PhotoTempDirectory + "*"):
os.remove(file)
else:
TargetFile = False
# auto off light
if AutoOn:
self.LightObject.Off()
return TargetFile
if __name__ == "__main__":
cam = Cam()
print("New shot: " + cam.Shot())
PhotoDirectory - куда складывать файлы фото, автоматом создаются подпапки года и месяца
PhotoTempDirectory - сюда делается серия снимков одного раза, и изымается только последнее фото для PhotoDirectory, остальные удаляются
svc.py - главный файл демона
Код:
#!/usr/bin/python
# Tumbox daemon v2.2017.5.6
# standart import
import os, sys, socket, time, threading
# daemonize
AsDaemon = False
if "daemon" in sys.argv:
pid = os.fork()
if pid > 0:
# parent process
print ("Daemon started")
sys.exit()
elif pid == 0:
# daemon (child)
AsDaemon = True
#Hight priority
os.nice(-20)
#gpio import
from pyA20.gpio import gpio
from pyA20.gpio import port
# my import
import tem, atx, light, vent, cam
# initialize objects
gpio.init()
Temperature = tem.Tem(AsDaemon)
Atx = atx.Atx()
Atx.On() # allways atx on
Light = light.Light(Temperature)
Vent = vent.Vent(Temperature)
Cam = cam.Cam(Light)
Cam.Shot(True) # allways making shot on start daemon
AcceptingSocket = True
# enable automation for all
if "noauto" not in sys.argv:
Light.SetAutomode("auto")
Vent.SetAutomode("auto")
# initialize cross-thread objects
LockHalting = threading.RLock()
Halting = False
# worker thread
def worker():
global Temperature
global Light
global Vent
global Halting
while True:
Temperature.Update()
Light.Update()
Vent.Update()
time.sleep(1*60)
# halting
LockHalting.acquire()
if Halting:
if AsDaemon == False:
print("need to stop")
LockHalting.release()
break
else:
if AsDaemon == False:
print("non stop")
LockHalting.release()
# processing lines
def processing_line(line):
global Temperature
global Light
global Vent
global Halting
global AcceptingSocket
global Cam
if line == "info":
InfoString = ""
# Tem
AdditionalString = "[%d..%d] (%d..%d) " % (Temperature.GetNormalMinTem(), Temperature.GetNormalMaxTem(), Temperature.GetMinTem(), Temperatur$
AdditionalString += Temperature.GetLastNiceCurrentTime().strftime("%H:%M:%S %Y-%m-%d")
ReadedTem = Temperature.GetLastNiceCurrent()
if(ReadedTem == None):
InfoString += "Temperature: none %s\nHumidity: none\n" % AdditionalString
else:
InfoString += "Temperature: %d C %s\n" % (ReadedTem.Temperature, AdditionalString)
InfoString += "Humidity: %d %%\n" % ReadedTem.Humidity
# ATX
InfoString += "ATX: "
if Atx.Get():
InfoString += "on"
else:
InfoString += "off"
InfoString += "\n"
# Light
InfoString += "Light: "
InfoString += "Light: "
if Light.Get():
InfoString += "on"
else:
InfoString += "off"
InfoString += " (%s)" % Light.GetAutomode()
InfoString += "\n"
# Vent
InfoString += "Vent: %d (%s) [0, %s..%s]\n" % (Vent.GetSpeed(), Vent.GetAutomode(), Vent.GetMinSpeed(), Vent.GetMaxSpeed())
return InfoString
elif line == "halt":
LockHalting.acquire()
Halting = True
LockHalting.release()
AcceptingSocket = False
return "Stopping daemon..."
elif line == "light on":
Light.SetAutomode("on")
Light.Update()
return "ok"
elif line == "light off":
Light.SetAutomode("off")
elif line == "light auto":
Light.SetAutomode("auto")
Light.Update()
return "ok"
elif line == "vent manual":
Vent.SetAutomode("manual")
Vent.Update()
return "ok"
elif line == "vent auto":
Vent.SetAutomode("auto")
Vent.Update()
return "ok"
elif line.find("vent ") == 0:
NewSpeed = line[5:]
try:
NewSpeed = int(NewSpeed)
except Exception:
return "incorrect speed"
if Vent.SetSpeed(NewSpeed):
Vent.Update()
return "ok"
else:
return "cancel"
elif line == "cam shot":
return "New shot: " + Cam.Shot(True)
return "error line: %s" % line
# -main-
# starting check thread
CheckThread = threading.Thread(target=worker)
CheckThread.start()
# setup socket server
sock = socket.socket()
sock.bind(('127.0.0.1', 9191))
sock.listen(1)
#waiting for connection
while AcceptingSocket:
conn, addr = sock.accept()
if AsDaemon == False:
print("Accept client: ", addr)
string_line = ""
string_buffer = ""
# reading
reading = True
while AcceptingSocket and reading:
data = conn.recv(1024)
# end connecion
if not data:
if AsDaemon == False:
print("End connection: ", addr)
break
# parsing one string line
string_buffer += data
try:
n_index = string_buffer.index('\n')
except ValueError:
n_index = -1
while n_index >=0:
string_line = string_buffer[0:n_index]
n_index +=1
string_buffer = string_buffer[n_index:]
try:
n_index = string_buffer.index('\n')
except ValueError:
n_index = -1
while n_index >=0:
string_line = string_buffer[0:n_index]
n_index +=1
string_buffer = string_buffer[n_index:]
try:
n_index = string_buffer.index('\n')
except ValueError:
n_index = -1
answer = processing_line(string_line)
# sending answer
try:
conn.send(answer)
except socket.error:
conn.close()
n_index = -1
reading = False
if AsDaemon == False:
print("Client closed: ", addr)
sock.close()
# Tumbox daemon v2.2017.5.6
# standart import
import os, sys, socket, time, threading
# daemonize
AsDaemon = False
if "daemon" in sys.argv:
pid = os.fork()
if pid > 0:
# parent process
print ("Daemon started")
sys.exit()
elif pid == 0:
# daemon (child)
AsDaemon = True
#Hight priority
os.nice(-20)
#gpio import
from pyA20.gpio import gpio
from pyA20.gpio import port
# my import
import tem, atx, light, vent, cam
# initialize objects
gpio.init()
Temperature = tem.Tem(AsDaemon)
Atx = atx.Atx()
Atx.On() # allways atx on
Light = light.Light(Temperature)
Vent = vent.Vent(Temperature)
Cam = cam.Cam(Light)
Cam.Shot(True) # allways making shot on start daemon
AcceptingSocket = True
# enable automation for all
if "noauto" not in sys.argv:
Light.SetAutomode("auto")
Vent.SetAutomode("auto")
# initialize cross-thread objects
LockHalting = threading.RLock()
Halting = False
# worker thread
def worker():
global Temperature
global Light
global Vent
global Halting
while True:
Temperature.Update()
Light.Update()
Vent.Update()
time.sleep(1*60)
# halting
LockHalting.acquire()
if Halting:
if AsDaemon == False:
print("need to stop")
LockHalting.release()
break
else:
if AsDaemon == False:
print("non stop")
LockHalting.release()
# processing lines
def processing_line(line):
global Temperature
global Light
global Vent
global Halting
global AcceptingSocket
global Cam
if line == "info":
InfoString = ""
# Tem
AdditionalString = "[%d..%d] (%d..%d) " % (Temperature.GetNormalMinTem(), Temperature.GetNormalMaxTem(), Temperature.GetMinTem(), Temperatur$
AdditionalString += Temperature.GetLastNiceCurrentTime().strftime("%H:%M:%S %Y-%m-%d")
ReadedTem = Temperature.GetLastNiceCurrent()
if(ReadedTem == None):
InfoString += "Temperature: none %s\nHumidity: none\n" % AdditionalString
else:
InfoString += "Temperature: %d C %s\n" % (ReadedTem.Temperature, AdditionalString)
InfoString += "Humidity: %d %%\n" % ReadedTem.Humidity
# ATX
InfoString += "ATX: "
if Atx.Get():
InfoString += "on"
else:
InfoString += "off"
InfoString += "\n"
# Light
InfoString += "Light: "
InfoString += "Light: "
if Light.Get():
InfoString += "on"
else:
InfoString += "off"
InfoString += " (%s)" % Light.GetAutomode()
InfoString += "\n"
# Vent
InfoString += "Vent: %d (%s) [0, %s..%s]\n" % (Vent.GetSpeed(), Vent.GetAutomode(), Vent.GetMinSpeed(), Vent.GetMaxSpeed())
return InfoString
elif line == "halt":
LockHalting.acquire()
Halting = True
LockHalting.release()
AcceptingSocket = False
return "Stopping daemon..."
elif line == "light on":
Light.SetAutomode("on")
Light.Update()
return "ok"
elif line == "light off":
Light.SetAutomode("off")
elif line == "light auto":
Light.SetAutomode("auto")
Light.Update()
return "ok"
elif line == "vent manual":
Vent.SetAutomode("manual")
Vent.Update()
return "ok"
elif line == "vent auto":
Vent.SetAutomode("auto")
Vent.Update()
return "ok"
elif line.find("vent ") == 0:
NewSpeed = line[5:]
try:
NewSpeed = int(NewSpeed)
except Exception:
return "incorrect speed"
if Vent.SetSpeed(NewSpeed):
Vent.Update()
return "ok"
else:
return "cancel"
elif line == "cam shot":
return "New shot: " + Cam.Shot(True)
return "error line: %s" % line
# -main-
# starting check thread
CheckThread = threading.Thread(target=worker)
CheckThread.start()
# setup socket server
sock = socket.socket()
sock.bind(('127.0.0.1', 9191))
sock.listen(1)
#waiting for connection
while AcceptingSocket:
conn, addr = sock.accept()
if AsDaemon == False:
print("Accept client: ", addr)
string_line = ""
string_buffer = ""
# reading
reading = True
while AcceptingSocket and reading:
data = conn.recv(1024)
# end connecion
if not data:
if AsDaemon == False:
print("End connection: ", addr)
break
# parsing one string line
string_buffer += data
try:
n_index = string_buffer.index('\n')
except ValueError:
n_index = -1
while n_index >=0:
string_line = string_buffer[0:n_index]
n_index +=1
string_buffer = string_buffer[n_index:]
try:
n_index = string_buffer.index('\n')
except ValueError:
n_index = -1
while n_index >=0:
string_line = string_buffer[0:n_index]
n_index +=1
string_buffer = string_buffer[n_index:]
try:
n_index = string_buffer.index('\n')
except ValueError:
n_index = -1
answer = processing_line(string_line)
# sending answer
try:
conn.send(answer)
except socket.error:
conn.close()
n_index = -1
reading = False
if AsDaemon == False:
print("Client closed: ", addr)
sock.close()
В автозагрузке он должен запускаться с параметром "daemon", чтобы после запуска "отпустить" командную строку.
Параметр "noauto", отключает переход демона в автоматический (рабочий) режим управления.
Если запустить без параметра он будет "держать" командную строку занатой всё время работы - это удобно для отладки - показываются некоторые процессы работы.
client.py - клиент демона
Код:
import sys, socket
SkipConnecting = False
# check command
StringCommand = 'info'
if len(sys.argv) >= 2:
StringCommand = sys.argv[1]
if StringCommand == "help":
print("|Command| |Description|")
print("help show this help info")
print("info show all current info")
print("halt shutdown daemon")
print("light on/off/auto set mode for light")
print("vent manual/auto set mode for ventilation")
print("vent <speed: 0, 5..15> set speed for ventilation")
print("cam shot make shot from camera now")
sys.exit()
else:
StringCommand = ' '.join(sys.argv[1:])
else:
print("Default command: " + StringCommand)
# connecting
sock = socket.socket()
sock.connect(('127.0.0.1', 9191))
# send command
StringCommand += '\n'
sock.send(StringCommand)
data = sock.recv(1024)
print(data)
sock.close()
SkipConnecting = False
# check command
StringCommand = 'info'
if len(sys.argv) >= 2:
StringCommand = sys.argv[1]
if StringCommand == "help":
print("|Command| |Description|")
print("help show this help info")
print("info show all current info")
print("halt shutdown daemon")
print("light on/off/auto set mode for light")
print("vent manual/auto set mode for ventilation")
print("vent <speed: 0, 5..15> set speed for ventilation")
print("cam shot make shot from camera now")
sys.exit()
else:
StringCommand = ' '.join(sys.argv[1:])
else:
print("Default command: " + StringCommand)
# connecting
sock = socket.socket()
sock.connect(('127.0.0.1', 9191))
# send command
StringCommand += '\n'
sock.send(StringCommand)
data = sock.recv(1024)
print(data)
sock.close()
через запуск этой программы можно управлять запущенным демоном, смотреть текущее состояние.
Запуск с параметром help показывает все доступные команды с описанием.
Запуск без параметров - отображение текущей информации (режим работы и значения всех устройств).
Для автозагрузки демона в файл /tumbox/autostart.sh я добавил строку:
Код:
/tumbox/svc/svc.py daemon noauto
noauto - потомучто ничего сейчас не растёт
Жду тепла
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
DHT11 с оранжом хреново работает, бывает и по пол дня не может получить температуру. Это сводит все усилия на нет. Наверное надо его как-то через PIC-контроллер сделать.
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
аКустик, DHT вообще так себе работают) 22й чуть получше. У меня в одной итерации считывания инфы - 5 опросов DHT22 - стабильно 1 из 5 невалидный и еще часто выдает в 2 раза большие значения. Я сделал логическую валидацию и все статистически выбивающиеся значения отбрасываю.
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Что я только-что прочитал
_________________
Organic garden коло хати, and bees між шишками гудуть...
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
аКустик, ты с нами?
_________________
И, пожалуйста, помните: мы - не юристы, правила - не УК, наказания раздают не святые.
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Ну как дела? Работает ебанина?
_________________
-- In Code We Trust --
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Вобщем ничего не вышло.
Посадил одну растиху, ударила жара и бедняжка скончалась (в гровинге дилетант). Если на улице 35, два кулера ниже этой температуры не охладят никогда, надо использовать элементы Пельтье.
Факт роста энергопотребления скрыть от домашних не удалось - это и поставило крест на проекте. Надо было начинать с солнечных батарей и аккумулятора.
Куча программного кода на форуме только путает людей, это наверно надо сайт заводить с обновлениями программы.
Сейчас Оранж интернет раздаёт Может будет какой-нибудь "умный дом":)
Посадил одну растиху, ударила жара и бедняжка скончалась (в гровинге дилетант). Если на улице 35, два кулера ниже этой температуры не охладят никогда, надо использовать элементы Пельтье.
Факт роста энергопотребления скрыть от домашних не удалось - это и поставило крест на проекте. Надо было начинать с солнечных батарей и аккумулятора.
Куча программного кода на форуме только путает людей, это наверно надо сайт заводить с обновлениями программы.
Сейчас Оранж интернет раздаёт Может будет какой-нибудь "умный дом":)
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
аКустик [07.01.18 15:09] писал(а):
Вобщем ничего не вышло.
Посадил одну растиху, ударила жара и бедняжка скончалась (в гровинге дилетант). Если на улице 35, два кулера ниже этой температуры не охладят никогда, надо использовать элементы Пельтье.
Факт роста энергопотребления скрыть от домашних не удалось - это и поставило крест на проекте. Надо было начинать с солнечных батарей и аккумулятора.
Куча программного кода на форуме только путает людей, это наверно надо сайт заводить с обновлениями программы.
Сейчас Оранж интернет раздаёт Может будет какой-нибудь "умный дом":)
Посадил одну растиху, ударила жара и бедняжка скончалась (в гровинге дилетант). Если на улице 35, два кулера ниже этой температуры не охладят никогда, надо использовать элементы Пельтье.
Факт роста энергопотребления скрыть от домашних не удалось - это и поставило крест на проекте. Надо было начинать с солнечных батарей и аккумулятора.
Куча программного кода на форуме только путает людей, это наверно надо сайт заводить с обновлениями программы.
Сейчас Оранж интернет раздаёт Может будет какой-нибудь "умный дом":)
В нашем деле главное на свободе оставаться! Скажу тебе так, тут не с солнечных панелей начинать надо, а с помещения о котором никто не знает.
_________________
И, пожалуйста, помните: мы - не юристы, правила - не УК, наказания раздают не святые.
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
Всегда жаль, когда такие здравые идеи не могут развиваться из-за отсутствия условий.
_________________
ice cold killer
Заголовок сообщения: Re: Тумбокс 2: тумбочка + Orange PI One
аКустик, Скажи майнингом домашним занялся
А так по факту жаль бро что старания так кончились...
А так по факту жаль бро что старания так кончились...
_________________
Дело сделанное не вовремя, становится проблемой
[ Сообщений: 62 ]
на страницу... 1 2 3
Похожие топики | Автор | Ответы | Просмотры | Последнее сообщение | ||
---|---|---|---|---|---|---|
Тумбочка из хлама | LED ~ 220 watt | ВШГ 60x60x30
в форуме Строй-репорт |
7 |
1368 |
06.11.19 14:17 |
|||
Damnesia fem, тумбочка, COB 50W, LST, земля, Hesi
в форуме Grow reports |
23 |
1716 |
29.08.22 16:45 |
|||
Auto Amnesia / Тумбочка / Бытовые LED 118w / Земля / GHE
в форуме Grow Autoflowers |
24 |
4034 |
23.07.21 14:24 |
|||
Auto Amnesia / Тумбочка 40х40х70 / Бытовые LED 173w / Земля / GHE
в форуме Grow Autoflowers |
60 |
6344 |
15.11.20 07:01 |
|||
Auto Daiquiri Lime / Тумбочка 40х40х70 / Бытовые LED 104w / Земля / GHE
в форуме Grow LED |
64 |
6033 |
10.02.21 17:12 |
Журнал вахтёра |
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 6 |
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения