Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for DS3231 RTC #73

Merged
merged 7 commits into from Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
87 changes: 74 additions & 13 deletions src/lib/terkin/datalogger.py
Expand Up @@ -175,7 +175,7 @@ def duty_task(self):
# Run downstream mainloop handlers.
self.duty_cycle()

# Sleep how ever.
# Sleep how configured
self.sleep()

def duty_cycle(self):
Expand Down Expand Up @@ -215,42 +215,47 @@ def duty_cycle(self):
machine.idle()

def sleep(self):
"""Sleep until the next measurement cycle."""
"""Sleep or shutoff until the next measurement cycle."""

lightsleep = self.settings.get('main.lightsleep', False)
deepsleep = self.settings.get('main.deepsleep', False)
shutoff = self.settings.get('main.shutoff', False)
interval = self.get_sleep_time()

# Amend deep sleep intent when masked through maintenance mode.
if self.device.status.maintenance is True:
lightsleep = False
deepsleep = False
shutoff = False
log.info('Device is in maintenance mode. Skipping deep sleep and '
'adjusting sleep time to {} seconds.'.format(interval))

# Prepare device shutdown.
try:

# Shut down sensor peripherals.
self.sensor_manager.power_off()

# Shut down networking.
if deepsleep:
if deepsleep or shutoff:
self.device.networking.stop()

except Exception as ex:
log.exc(ex, 'Power off failed')

# Activate device sleep mode.
try:
self.device.hibernate(interval, lightsleep=lightsleep, deepsleep=deepsleep)
if shutoff:
# shut off the MCU via DS3231
self.shutoff()
else:
# Activate device sleep mode.
try:
self.device.hibernate(interval, lightsleep=lightsleep, deepsleep=deepsleep)

# When hibernation fails, fall back to regular "time.sleep".
except Exception as ex:
log.exc(ex, 'Failed to hibernate, falling back to regular sleep')
# Todo: Emit error message here.
log.info('Sleeping for {} seconds'.format(interval))
time.sleep(interval)
# When hibernation fails, fall back to regular "time.sleep".
except Exception as ex:
log.exc(ex, 'Failed to hibernate, falling back to regular sleep')
# Todo: Emit error message here.
log.info('Sleeping for {} seconds'.format(interval))
time.sleep(interval)

def get_sleep_time(self):
"""
Expand Down Expand Up @@ -605,6 +610,7 @@ def transmit_readings(self, dataframe: DataFrame):
return success

def start_buttons(self):

"""
Configure ESP32 touchpads.
"""
Expand Down Expand Up @@ -649,6 +655,60 @@ def start_buttons(self):
#self.button_manager.setup_touchpad('P20', name='Touch8', location='Module-Right-Top-7th')
#self.button_manager.setup_touchpad('P19', name='Touch9', location='Module-Right-Top-8th')

def shutoff(self):
""" shut off the MCU """

import DS3231tokei
import utime
from machine import Pin, RTC

# LED an
led = Pin(14,Pin.OUT)
horn = Pin(26,Pin.OUT)
led.value(1)
horn.value(1)
utime.sleep(1)
led.value(0)
horn.value(0)

bus = self.sensor_manager.get_bus_by_name('i2c:0')
ds = DS3231tokei.DS3231(bus.adapter)
interval = self.settings.get('main.interval.shutoff', 10) * 60 # convert from minutes to seconds
(year,month,day,dotw,hour,minute,second) = ds.getDateTime() # get the current time

print('Time is: ', day,hour,minute)

rtc = RTC() # create RTC
if year < 2001:
year = 2001 # sanity check, as of mpy 1.12 year must be >= 2001
rtc.init((year,month,day,dotw,hour,minute,second,0)) # set time

# Compute sleeping duration from measurement interval and elapsed time.
elapsed = int(self.duty_chrono.read())
now_secs = utime.mktime(utime.localtime())
wake_at = now_secs - elapsed + interval
if (wake_at - now_secs) < 180: # don't shutoff for less than 3 minutes
wake_at += interval

print('Now:',now_secs, 'Wake at:', wake_at)

(year,month,day,hour,minute,second, dotw, doty) = utime.localtime(wake_at) # convert the wake up time

# set alarm
ds.setAlarm2(day,hour,minute, DS3231tokei.A2_ON_HOUR_MINUTE)

print('Wake at: ', day,hour,minute)

# turn off MCU via MOSFET
print('Good night')

utime.sleep(1)

ds.enableAlarm2()
ds.resetAlarm2()

# The End

def scale_wizard(self):
"""
Invoke scale adjustment wizard.
Expand All @@ -667,3 +727,4 @@ def scale_wizard(self):
from terkin.sensor.scale import ScaleAdjustment
adj = ScaleAdjustment(sensor_manager=self.sensor_manager)
adj.start_wizard()

11 changes: 10 additions & 1 deletion src/lib/terkin/device.py
Expand Up @@ -186,7 +186,7 @@ def configure_rgb_led(self):
def blink_led(self, color, count=1):
"""

:param color:
:param color: rgb value as three hex values 0-255, e.g. 0x00FF00 for green
:param count: (Default value = 1)

"""
Expand All @@ -199,6 +199,15 @@ def blink_led(self, color, count=1):
time.sleep(0.15)
pycom.rgbled(0x000000)
time.sleep(0.10)
elif self.settings.get('main.rgb_led.simple', False):
led = machine.Pin(int(self.settings.get('main.rgb_led.pin')[1:]),machine.Pin.OUT)
for _ in range(count):
led.value(1)
time.sleep(0.15)
led.value(0)
time.sleep(0.10)



def start_telemetry(self):
""" """
Expand Down
2 changes: 1 addition & 1 deletion src/lib/terkin/driver/ds18x20_sensor.py
Expand Up @@ -49,7 +49,7 @@ def start(self):

platform_info = get_platform_info()

# Vanilla MicroPython 1.11
# Vanilla MicroPython >=1.11
if platform_info.vendor == platform_info.MICROPYTHON.Vanilla or use_native_driver:
import ds18x20_native
self.driver = ds18x20_native.DS18X20(onewire_bus)
Expand Down
88 changes: 88 additions & 0 deletions src/lib/terkin/driver/ds3231_sensor.py
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# License: GNU General Public License, Version 3
from terkin import logging
from terkin.sensor import AbstractSensor
import DS3231tokei as DS3231
from machine import RTC

log = logging.getLogger(__name__)


class DS3231Sensor(AbstractSensor):
"""
A generic DS3231 sensor/actor component.
The DS3231 has a RTC which can be read & set. It also has a good temperature sensor.
If the battery on the RTC has to be changed the time is set to 1.1.2000, 0.00h
Used by terkin/datalogger to register and read() from this sensor.
start() & read() are mandatory.
"""

def __init__(self, settings=None):

# from terkin/sensor/core.py class AbstractSensor
super().__init__(settings=settings)

# Can be overwritten by ``.set_address()``.
self.address = 0x68

def start(self):
""" Getting the bus """
if self.bus is None:
raise KeyError("Bus missing for DS3231")

# Initialize the hardware driver.
try:
self.driver = DS3231(i2c=self.bus.adapter)
return True

except Exception as ex:
log.exc(ex, 'DS3231 hardware driver failed')

# set date/time of RTC
self.set_time()

def read(self):
""" the DS3231 has a sensor for temperature compensation """

if self.bus is None or self.driver is None:
return self.SENSOR_NOT_INITIALIZED

#log.info('Acquire reading from DS3231')

data = {}

temperature = self.driver.getTemperature()

# Prepare readings.
values = {
"temperature": temperature,
}

# Build telemetry payload.
fieldnames = values.keys()
for name in fieldnames:
fieldname = self.format_fieldname(name, hex(self.address))
value = values[name]
data[fieldname] = value

if not data:
log.warning("I2C device {} has no value: {}".format(self.address, data))

log.debug("I2C data: {}".format(data))

return data

def set_time(self):
""" set the system time to RTC time """

rtc = RTC()
[year,month,day,dotw,hour,minute,second] = self.getDateTime() # get the date/time from the DS3231
if year > 2019: # check valid data
rtc.init((year,month,day,dotw,hour,minute,second,0)) # set date/time
log.debug("Time set: {}".format(rtc.datetime()))
else:
log.warning("DS3231 date/time not set, not setting RTC")




23 changes: 19 additions & 4 deletions src/settings.example.py
Expand Up @@ -3,19 +3,34 @@
# General settings.
main = {

# Measurement intervals in seconds.
# Measurement intervals in seconds, for shutoff in minutes.
'interval': {

# Apply this interval if device is in field mode.
'field': 60.0,
'field': 15.0,

# Apply this interval if device is in maintenance mode.
# https://community.hiveeyes.org/t/wartungsmodus-fur-den-terkin-datenlogger/2274
'maintenance': 15.0,

# Apply this interval if device goes into shutoff
'shutoff': 10,
# night & winter mode: during the night or winter the interval is doubled (-> in a winter night it is quadrupled)
# beginning & end months are included: night from 20 to 5 -> 20.00h to 5.59h, winter from 10 to 2 -> October 1st to February 29th
# a start value of 0 means there is no night/winter, night_start > night_end, winter_start > winter_end
'night_start': 20,
'night_end' : 5,
'winter_start' : 10,
'winter_end' : 2,
},

# the next three options are logically exclusive but if you insist: shutoff > deepsleep > lightsleep
# Whether to completely shutoff between measurements (requires DS3231!)
'shutoff': False,
# Whether to use deep sleep between measurement cycles.
'deepsleep': False,
# Whether to use ight sleep between measurement cycles.
'lightsleep': False,

# Configure logging.
'logging': {
Expand Down Expand Up @@ -489,7 +504,7 @@
'enabled': False,
'bus': 'i2c:0',
'address': 0x36,
},
},
],
'buses': [
{
Expand All @@ -512,7 +527,7 @@
"id": "bus-onewire-0",
"family": "onewire",
"number": 0,
"enabled": True,
"enabled": False,
"pin_data": "P11",
"driver": "native",
},
Expand Down