LoPy4 and Deep Sleep Questions

Hi guys,
Apologies if this is in the wrong section.

As some might know Im new to MicroPython and the Pycom boards… but eager to learn more:)

I am currently playing with machine.deepsleep() - Details here: https://docs.pycom.io/firmwareapi/pycom/machine/

I basically have a few different requirements around using deepsleep for my LoPy4’s in the paddocks.

Requirement 1: Scheduled wake ups machine.deepsleep(21600000)
This will send a board to sleep for 6 hours and then wake up, at which point I will detect air and soil temperature in the paddock, upload to TTN and then go back to sleep for another 6 hours. Process simply continues in a loop. This isnt too much of a problem for me.

Requirement 2: Un-scheduled wake ups
I would like the board to be in deepsleep until such time as a gate is opened, at which point wake, send me a notification of the event and then go back to sleep.
The gate opening detection will be a simple reed-switch and sensing this via the GPIO is not my concern (might be simple code or an interrupt trigger (thoughts / advice please), nor is sending me a pushbullet notification; what I dont know though is how to make sense of this (taken from the above link):


machine.pin_deepsleep_wakeup(pins, mode, enable_pull)

Configure pins to wake up from deep sleep mode. The pins which have this capability are: P2, P3, P4, P6, P8 to P10 and P13 to P23 .

The arguments are:
** pins a list or tuple containing the GPIO to setup for deepsleep wakeup.*
** mode selects the way the configure GPIO s can wake up the module. The possible values are: machine.WAKEUP_ALL_LOW and machine.WAKEUP_ANY_HIGH .*
** enable_pull if set to True keeps the pull up or pull down resistors enabled during deep sleep. If this variable is set to True , then ULP or capacitive touch wakeup cannot be used in combination with GPIO wakeup.*


Any thoughts or words of wisdom / example code?

Thanks
Jon

Hi Jon,

If I’m reading that correctly you could use this code:

machine.pin_deepsleep_wakeup(P2, machine.WAKEUP_ALL_LOW, True)

A low signal to P2 should then wake up your device. I recommend making a simple sketch to test this.

Guys,
A bit of a change of direction… Before I start looking at waking from a PIN, need to get the basic code right first!

I thought my code was working fine… It isnt, not quite. Im having trouble with machine.deepsleep().

The LoPy4 goes to sleep fine but when it wakes up it restarts the machine, re-joins TTN and then uploads the next set of data. Consequently the counter is always 0 and perhaps more importantly (from a power management perspective) Im wasting a whole heap of battery power re-joining TTN every transmission.

This is a screenshot of watching the data come into TTN when using machine.deepsleep(20000) in my code:

And this is a screenshot when I simply remove the machine.deepsleep(20000) line from my LoPy4 code:

I have purposefully set the sleep time low so as to be able to diagnose easily.
I have also tried to use lora.nvram_save() and lora.nvram_restore() but I have not been able to solve the issue.

I have included my code below in the hopes that someone can show me the error of my ways!

from network import LoRa
import socket
import utime
import binascii
import pycom
import ustruct
import machine
import onewire
from machine import Pin
from machine import ADC
from machine import ADC
from onewire import DS18X20
from onewire import OneWire

#DS18B20 data line connected to pin P10
ow = OneWire(Pin('P10'))
temp = DS18X20(ow)

# Disable LED heartbeat (so we can control the LED)
#pycom.heartbeat(False)


# Temp measurement
def temp_measure():
    print("")
    print("Reading DS18B20 Sensors...")
    Current_Air_Temp = 0.0
    Current_Soil_Temp = 0.0
    # b = bytearray to hold ROM addresses from OneWire address scan
    utime.sleep(1)
    b = (onewire.OneWire.scan(ow))
    # Below prints the array, b
    #print("b = ", b)
    soil_temp_rom = b[0]
    #print("Soil Temp ROM = ", b[0])
    air_temp_rom = b[1]
    #print("Air Temp ROM = ", b[1])
    utime.sleep(1)
    temp.start_conversion(soil_temp_rom)
    temp.start_conversion(air_temp_rom)
    utime.sleep(1)
    Current_Soil_Temp = temp.read_temp_async(soil_temp_rom)
    Current_Air_Temp = temp.read_temp_async(air_temp_rom)
    print("Current Soil Temp = ", Current_Soil_Temp)
    print("Current Air Temp = ", Current_Air_Temp)
    return Current_Soil_Temp, Current_Air_Temp
#********************************

# takes battery voltage readings
def adc_battery():
	adc = ADC(0)                                        # initialise adc hardware
	adc_c = adc.channel(attn=3, pin='P16')              # create an object to sample ADC on pin 16 with attenuation of 11db (config 3)
	adc_samples = []                                    # initialise the list
	for count in range(100):                            # take 100 samples and append them into the list
		adc_samples.append(int(adc_c()))

	adc_samples = sorted(adc_samples)                   # sort the list
	adc_median = adc_samples[int(len(adc_samples)/2)]   # take the center list row value (median average)
	# apply the function to scale to volts
	adc_median = adc_median * 2 / 4095 / 0.3275
#	print(adc_samples)
	return adc_median


# lora config
# Do not need to connect, disconnect, reconnect to TTN.  Simply connect once and leave machine to sleep, wake, upload and sleep again.
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AS923)
# TTN access info
app_eui = binascii.unhexlify('********************')
app_key = binascii.unhexlify('%%%%%%%%%%%%%%%%%%%')

# attempt join - continues attempts background
lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

# wait for a connection
print('Waiting for LoRaWAN network connection...')
while not lora.has_joined():
	utime.sleep(1)
	# if no connection in a few seconds, then reboot
	if utime.time() > 500:
		print("possible timeout")
		machine.reset()
	pass

# we're online, set LED to green and notify via print
#pycom.rgbled(0x004600)
print('Network joined!')

# setup the socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
s.setblocking(False)
s.bind(1)
#lora.nvram_save()

#count = 0
# limit to 200 packets; just in case power is left on
# Alternitavely, in the final application the while statement could be a while True, can then remove count function
while True:
#    lora.nvram_restore()
    # Get Median Battery Voltage
    lipo_voltage = adc_battery()
    print('Battery voltage:  ', lipo_voltage)

    # Get Temperature from DS18x20 Sensors
    temps = temp_measure()
    # Combine Battery Voltage and temperature readings into one bytes package for sending to LORA
    # Note that the "3f" in the ustruck.pack code below means 3 variables are expected, each a float.
    send_packet = ustruct.pack('3f', *[temps[0], temps[1], lipo_voltage])
    print('Packed Send_packet:', send_packet)
    print ('Unpacked Send_packet is:', ustruct.unpack('<3f',send_packet))
    # send the prepared packet via LoRa
    s.send(send_packet)
    # The time delay below is to allow the packet to be sent before the machine goes to sleep.
#    utime.sleep(3)

    # check for a downlink payload, up to 64 bytes
    rx_pkt = s.recv(64)
    # check if a downlink was received
    if len(rx_pkt) > 0:
    	print("Downlink data on port 200:", rx_pkt)
    	pycom.rgbled(0xffa500)
#    	input("Downlink recieved, press Enter to continue")
        utime.sleep(3)
    	pycom.rgbled(0x004600)
        utime.sleep(3)
#    count += 1
#    print('Going to sleep now...')
#    lora.nvram_save()
    utime.sleep(3)
#    machine.deepsleep(3600000)
#    machine.deepsleep(20000)
#    lora.nvram_restore()
    utime.sleep(3)
print ("End Code")

Thanks
Jon

Setup network & sensors

if machine.reset_cause() == machine.DEEPSLEEP_RESET:
print(‘woke from a deep sleep’)
else:
print(‘power on or hard reset’) # Join LoRaWAN with OTAA

If waking from deep sleep, just go to sending, otherwise rejoin the network.

1 Like

Thanks @Robin57159,
I assume the code needs to go immediately after the machine.deepsleep(20000) line.

Further, if the the REPL in Atom displays something after waking then the waking is working, if it simply restarts, nothing would print??

I’ll try this tonight when I get home and see what happens.

Thx
J

Where does program execution begin after returning from deep sleep? At the start of the main loop.

https://docs.micropython.org/en/latest/library/machine.html

1 Like

And the big value-add with deep sleep is that RAM is how-it-was beforehand, and, the device remains activated on LoRaWAN. It can get on with what it had to do, without doing another join request.

Nooooo.

RAM may not be as it was, hence the need to save and restore RAM. Save RAM before deep sleep, when program loop commences, check to see how you got there. Decide if you’re back from deep sleep, and restore RAM without setting up LORA again, otherwise full startup.

1 Like

Oh, well there you go! Thanks Robin. I’ve likely got my wires crossed with LoRa activation as well - state of peripherals would be hard to maintain after an event like that.

Thanks guys, I’ll do some more work on this and see if I can get to the bottom of it:)

R
Jon

Well I think Ive fixed it:) At least the deep sleep & TTN counter problem.

My mistake was thinking that the machine should not “reboot” after waking from deepsleep and that my ‘While True:’ loop should keep looping. Once I understood this assumption to be wrong, it kind of made more sense.

I added the suggested code from @Robin57159 towards the top of the code:

if machine.reset_cause() == machine.DEEPSLEEP_RESET:
    print(‘woke from a deep sleep’)
else:
    print(‘power on or hard reset’) # Join LoRaWAN with OTAA

…and immediately determined the reason for waking. After that I simply removed the While True: loop

One thing I did need to do to get everything working was to duplicate the code to define lora and setup the socket - this seemingly is not restored by lora.nvram_restore(). I am still not sure if this was the right thing to do, but it works. I suppose I could create a seperate function to save code duplication.

For the benefit of others battling with this problem, the resulting (working) code looks like this:

if machine.reset_cause() == machine.DEEPSLEEP_RESET:
    print('Woke from a Deep Sleep')
    lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AS923)
    lora.nvram_restore()
    s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
    s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
    s.setblocking(False)
    s.bind(1)
else:
    print('Power on or Hard reset') # Join LoRaWAN with OTAA
    # lora config
    lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AS923)
    # TTN access info
    app_eui = binascii.unhexlify('****************')
    app_key = binascii.unhexlify('yyyyyyyyyyyyyyyyyyyyyyyyy')

    # attempt join - continues attempts background
    lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)

    # wait for a connection
    print('Waiting for LoRaWAN network connection...')
    while not lora.has_joined():
        # if no connection in a few seconds, then reboot
        if utime.time() > 500:
            print("possible timeout")
            machine.reset()
        pass
    print('Network joined!')
    # setup the socket
    s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
    s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
    s.setblocking(False)
    s.bind(1)

# From here the code goes on to read sensors, upload to TTN, then go back to deepsleep

I must say thanks to all who helped me get this far, you guys rock!

Hope that helps someone down the track!

Now time to see how long I can make the battery last!

Jon