Deepsleep call hangs



  • Re: Deepsleep issue on FiPy

    I have been working to identify an issue on my FiPy where it appears that a call to deepsleep does not actually result in the unit entering deepsleep.

    I have spent a lot of time looking at the hardware and optimising the power supply to the unit. I now believe that this is not the cause of the issue. I have a good 3A supply feeding the device (from a solar/battery system) and I have 470uF low esr caps on both the Vin and 3v3 output pins on the FiPy.

    After a number of hours of successful operation (code running correctly and then periodically entering deepsleep for 2 minutes) I randomly find that a call to deepsleep does not seem to occur.

    I am monitoring debug output (application print statements) on the console port. When the deepsleep call appears to fail, I do not observe that the terminal port has been deactivated (when correctly going to sleep the serial port enters a non marking state).

    At this point, I have avoided trying to use the WDT as I do not want to mask the problem, I want to find the problem.

    I have read on other forums that the low level espressif code has previously been suspected of lockups associated with deepsleep operation.

    Can someone from Pycom please also direct me to the documentation for the recommended components around the power supply for the Pycom products.

    Any assistance with identification and rectification of this issue would be greatly appreciated.

    Peter.



  • I have now moved back to the stable firmware release (1.18.2.r4) which appears to have solved the Guru Mediation errors, but I still have the original deepsleep issue.

    Does anyone have any other ideas on this?



  • It appears I was a bit over optimistic about the fix for the original deepsleep problem.

    During a 10 hour run period, there were 3 instances where the WDT kicked in due to sleep call failures.

    On the Guru error issue, I have run up the entire code on a second FiPy device (a total clean install) and I get exactly the same issue.

    I will try the NV commands from the REPL.

    Can someone from Pycom please respond to this issue!!!!!!



  • @tuftec Can you try an nvram_save call from REPL? Can you also try other NVS calls (nvs_set IIRC)?

    Maybe there's some corruption of the NVS space. Don't remember off the top of my head if there's a way to clear all that an how, probably some option of the firmware update tool CLI?



  • @philwilkinson I will have a more detailed look into your suggestions tomorrow. You have suggested some good ideas. I am using a fairly simple form of instantiation for the LoRa stuff. Maybe something has changed with the default parameters in the LoRa call for the RC8 version of firmware. Interestingly, the data actually gets transmitted to my gateway/cloud application. The system core dumps straight after sending the data. So possibly safe to assume that the LoRa system is set up correctly.



  • @philwilkinson Hi Phil, this code has been working for some time.
    I have not made any changes to it. I have just updated to RC8.
    The NB-IOT and Sigfox sections work fine.

    For everyone else, I have now had my test unit running for 8 hours now (using NB-IOT). With the previous firmware version (RC7) I would have seen deepsleep calls fail by now and the WDT kick in. So it looks like the recent updates to a newer IDF may have solved the original problem I was seeing with deepsleep failures.

    Peter.



  • @tuftec Peter, I am struggling to follow your script!
    can you try something simple like below just to see if you are instantiating the lora object correctly. You will need to set data, app_eui app_key with your constants.
    If this works well, then nvram_restore is likely the cause. I have struggled a lot getting this to work well...!
    you could then run a try-except exception handler to check if restore is working
    lora = LoRa(mode=LoRa.LORAWAN)
    lora.nvram_restore()

    lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915, adr=False, tx_retries=0, device_class=LoRa.CLASS_A)
    for i in range(16, 65): #AU915 TTN gateways need channels removed
        lora.remove_channel(i)
    for i in range(66, 72):
        lora.remove_channel(i)
    app_eui = binascii.unhexlify(config.app_eui)
    app_key = binascii.unhexlify(config.app_key)
    
    lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
    while not lora.has_joined():
        print('trying to join OTAA')
        time.sleep(0.5)
    print('joined')
    time.sleep(2)
    s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
    s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
    s.setblocking(True)
    s.send(bytes(data))
    print('sent data: ', data)
    s.setblocking(False)
    rev_data = s.recv(64)
    print('recieved data: ', rev_data)
    


  • Further, initial testing with NB-IOT and Sigfox does not result in the Guru Mediation error.
    This further implicates the lora.nvram_save() call.

    Peter.



  • Ok. I have now upgraded to 1.20.0.rc8.
    What a disaster!!!!!!

    I am now getting Guru Mediation errors.
    I have not made any changes to my code.

    As best as I can make out, it looks like my code crashes at the call to lora.nvram_save() or the following line which is a socket.close() command.

    Here is an extract of the terminal log (The unprinatable characters are just where the USART is disabled during sleep. My terminal program also time stamps the lines.):

    2019/04/01 13:52:42.2: ÷Õ“u[0B][17]}Œ~‘3üW[02]ð[01] [00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00][00]Initializing filesystem as LittleFS!
    2019/04/01 13:54:40.5: date/time =  10 5 25 9 16
    raw pressure value =  0.0
    2019/04/01 13:54:46.3: Batt ADC value =  727.875
    RealBatt =  50
    light off
    PurgeTimer =  0  PurgePeriod =  4
    feed the dog
    going to sleep for:  110162  ms
    ÿï¶Ý×ïãý;»ý埻OÏ‹RB ˆÅ[07]A¡@JÙ[12]øÈ[06][01]0$[03][01]$[02][01]+FÉð€€@[05]	p[08][10][10]H[08][00][08][08][01][02]H€[01][01]@[00][00][08][00][01]À[00][00]0[00][19][00][10][00][00][01][00][10]@[00][00][00][10]‚@@ [00][00] [00][00][12][04] [10]H[00][02][00][00][00][00][00][00][10][00][00][00][00][00][00][00][00][00][00][00][00][00][00]Initializing filesystem as LittleFS!
    2019/04/01 13:56:39.9: date/time =  10 5 25 9 18
    2019/04/01 13:56:41.1: sending LoRa data
    2019/04/01 13:56:44.0: data received =  b''
    Guru Meditation Error: Core  1 panic'ed (StoreProhibited). Exception was unhandled.
    Core 1 register dump:
    PC      : 0x4020aabf  PS      : 0x00060330  A0      : 0x801df3d0  A1      : 0x3ffe2c30  
    A2      : 0x00000000  A3      : 0x00000004  A4      : 0x3f952604  A5      : 0x3f952608  
    A6      : 0x00000015  A7      : 0x00000001  A8      : 0x00000000  A9      : 0x3ffe2c10  
    A10     : 0x3ffd2ad0  A11     : 0x00000001  A12     : 0x00000000  A13     : 0x3f952608  
    A14     : 0x00000039  A15     : 0x00001cd6  SAR     : 0x00000020  EXCCAUSE: 0x0000001d  
    EXCVADDR: 0x00000038  LBEG    : 0x4009d494  LEND    : 0x4009d4c2  LCOUNT  : 0x00000000  
    
    Backtrace: 0x4020aabf:0x3ffe2c30 0x401df3cd:0x3ffe2c50 0x401d7b49:0x3ffe2c70 0x4010d0fd:0x3ffe2c90 0x40108cfd:0x3ffe2cb0 0x400e3b45:0x3ffe2cd0 0x400fa8d5:0x3ffe2cf0 0x400f7109:0x3ffe2d10 0x400f7171:0x3ffe2d30 0x40102712:0x3ffe2d50 0x400fa9e4:0x3ffe2df0 0x400f7109:0x3ffe2e20 0x40102677:0x3ffe2e40 0x400fa9e4:0x3ffe2ee0 0x400f7109:0x3ffe2f10 0x400f7171:0x3ffe2f30 0x40102712:0x3ffe2f50 0x400fa9e4:0x3ffe2ff0 0x400f7109:0x3ffe3050 0x400f7171:0x3ffe3070 0x40102712:0x3ffe3090 0x400fa9e4:0x3ffe3130 0x400f7109:0x3ffe31a0 0x400f7136:0x3ffe31c0 0x400df282:0x3ffe31e0 0x400df4f5:0x3ffe3280 0x400de189:0x3ffe32a0
    

    Any ideas what is going on now??????



  • @tuftec said in Deepsleep call hangs:

    • currently using firmware v1.20.0.rc7

    pycom updated some IDF stuff with rc8. Helped with deepsleep problems with the pytrack. i2c problems seems to be the cause in this case. Deepsleep on ESP32 itself shall have another cause, but give a try.

    Pycom uses both cores and there are still a lot of problems with multicore ESP32/IDF. Keeping it as new as possible may fix your particular problem.



  • @tuftec can you please edit your post and add ``` alone on a line at the beginning and end of your code, or select the whole code and click on the code icon? It will make it a lot more readable :-)



  • @jcaron I have reviewed and included my main code here for reference.

    The code is still very green and has some clumbsy bits in it. The RTC stuff is done in so/w currently as I had some advice that the h/w RTC did not work during deepsleep. I have yet to go back and revisit this.

    You should be able to pick up the general arrangement for the deepsleep and WDT bits. It is the deepsleep call right at the end that appears not to work correctly.

    # DipStik test code - Application bit - DipApp.py
    
    import network
    import pycom
    import time
    import sys
    import struct
    import binascii
    from network import Sigfox
    from network import LoRa
    from network import LTE
    import socket
    import machine
    import json
    from struct import *
    from machine import WDT
    
    # This code is called from DipBoot.
    
    # define rgb colours for later use
    RED=0x3f0000
    BLUE=0x00003f
    GREEN=0x003f00
    YELLOW=0x3f3f00
    WHITE=0x3f3f3f
    CYAN=0x003f3f
    MAGENTA=0x3f003f
    LEDOFF=0
    
    global  dipconfig, app_eui, app_key, fp, nvfp, NBIoTconnect
    
    NBIoTconnect = False
    
    #define real time clock
    rtc = machine.RTC()
    
    #define other h/w devices here
    Warning_Light=machine.Pin('P10',mode=machine.Pin.OUT) # this is a hold pin so do not set value here.
    Purge_Pump=machine.Pin('P11', mode=machine.Pin.OUT)
    Mpx_Pwr=machine.Pin('P21', mode=machine.Pin.OUT)
    Boom_Up=machine.Pin('P9', mode=machine.Pin.OUT)
    Modem_Pwr=machine.Pin('P23', mode=machine.Pin.OUT)
    Boom_Down=machine.Pin('P8', mode=machine.Pin.OUT)
    Latch_Open=machine.Pin('P22', mode=machine.Pin.OUT)
    
    # This module contains all of the main application bits.
    # This code runs through to the end and then just calls deep-sleep.
    # it must check the WAKE REASON to determine what it needs to do.
    # the code just keeps running until it has nothing further that it needs to immediately do and then just goes to sleep.
    
    # All the system interrupt code is defined in here. Very few interrupt routines as such.
    # NOTE: interrupts WILL not run during sleep. So code must keep awake to service these.
    
    # serial port RX service routine
    
    # camera rx service routine
    
    def StartRunTimer():
    	global RunTimer
    	RunTimer = machine.Timer.Chrono()
    	RunTimer.start()
    
    # tick timer ISR. This is actually a specical case pseudo ISR as it is based on sleep time.
    def TickISR():
    	##### TIMER TICKS OCCUR EVERY 2 MINUTES. #####
    	# defined by the deepsleep time
    	# now update the timers.
    	####### ASSUMES THAT NO CODE RUNS LONGER THAN ABOUT 110 SECONDS ######
    	# this is called by the main line of code
    	global  nvvar
    
    	nvvar['Ticks'] = nvvar['Ticks'] + 1 # A tick = 2 mins
    
    	# Sample Timer is in seconds.
    	# this is always going to be a multiple of 120
    	nvvar['Sample_Timer'] = nvvar['Sample_Timer'] + 120
    
    	# Comms Timer, Log timer and Camera timers are in minutes.
    	nvvar['Comms_Timer'] = nvvar['Comms_Timer'] + 2
    
    	if nvvar['Log_Timer'] != 0:
    		# If not expired, decrement Log count down timer.
    		nvvar['Log_Timer'] = nvvar['Log_Timer'] - 2
    
    	nvvar['Camera_Timer'] = nvvar['Camera_Timer'] + 2
    
    	if nvvar['Ticks'] >= 30:
    		#Test Timer is in hours.
    		nvvar['Test_Timer'] = nvvar['Test_Timer'] + 1
    		#Run Timer is in hours
    		nvvar['RunTimer'] = nvvar['RunTimer'] + 1
    		#Increment the purge timer every hour.
    		nvvar['PurgeTimer'] = nvvar['PurgeTimer'] + 1
    		nvvar['Ticks'] = 0
    
    	if (nvvar['Warning_Flag'] == True) or (nvvar['RemoteWarningFlag'] ==True):
    		#Turn on the warning light.
    		# Not sure this bit of code is needed here!!!!!
    		#WarningLightOn()
    		pass
    	else:
    		# Else turn it off ???????
    		pass
    
    	# Things to do every 2 MINUTES can go in here.
    	nvvar['Minute'] = nvvar['Minute'] + 2
    
    	# Need to test every 6 minutes and increment Rain Array index
    	MinuteTemp = nvvar['Minute'] % 6
    	if MinuteTemp == 0:
    		# A multiple of 6 minutes
    		# Shift the rain array pointer to the next slot.
    		# Note - this assumes that the Rain Array has 256 slots and the index is a byte to support rap around back to start.
    		# Hmmm!! Probably a better way to do this in Python
    		#nvvar['RainTipsIndex'] = (nvvar['RainTipsIndex'] + 1) % 256
    		# Now clear the rainfall accumulation slot.
    		nvvar['RainTipsArray'][nvvar['RainTipsIndex']] = 0
    
    	if nvvar['Minute'] > 59:
    		# Things to do every HOUR can go in here.
    		nvvar['Minute'] = 0
    		nvvar['Hour'] = nvvar['Hour'] + 1
    		# Need to test here (at hour rollover) for need to reset Rain totals
    		# If hour has incremented to 9am then need to clear Rain Hour total.
    		if nvvar['Hour'] == 9:
    			nvvar['RainDayTips'] = 0
    			# If hour has incremented to 9am and day = 1st then also clear Rain Month total.
    			if nvvar['Day'] == 1:
    				nvvar['RainMonthTips'] = 0
    		if nvvar['Hour'] > 23:
    			# Things to do every day can go in here.
    			nvvar['Hour'] = 0
    			nvvar['Day'] = nvvar['Day'] + 1
    			if nvvar['Month'] == 4 or nvvar['Month'] == 6 or nvvar['Month'] == 11:
    				# 30 day month
    				if nvvar['Day'] > 30:
    					nvvar['Day'] = 1
    					nvvar['Month'] = nvvar['Month'] + 1
    			else:
    				if nvvar['Month'] == 2:
    					Leap = Year % 4
    					if Leap == 0:
    						if nvvar['Day'] > 29:
    							# It's a leap year
    							nvvar['Day'] = 1
    							nvvar['Month'] = nvvar['Month'] + 1
    					else:
    						# Not a leap year
    						if nvvar['Day'] > 28:
    							nvvar['Day'] = 1
    							nvvar['Month'] = nvvar['Month'] + 1
    				else:
    					# 31 day month?
    					if nvvar['Day'] > 31:
    						nvvar['Day'] = 1
    						nvvar['Month'] = nvvar['Month'] + 1
    			if nvvar['Month'] > 12:
    				nvvar['Month'] = 1
    				nvvar['Year'] = nvvar['Year'] + 1
    	pass
    	#End of TickISR
    
    def WarningLightOn():
    	# Warning lamp flash routine. This probably can not be implimented as tick time is too long!!!!!!
    	# will need to just turn the lamp signal on and use external logic to flash it.
    	# will also need to use hold pin command to ensure warning lamp signal does not change during deepsleep.
    	global Warning_Light
    
    	Warning_Light.hold(False) # unfreeze the pin before setting it
    	Warning_Light.value(1)
    	Warning_Light.hold(True) # freeze the pin for deepsleep
    	print("light on")
    
    def WarningLightOff():
    	# Turn off the warning lamp and hold the output for deepsleep.
    	global Warning_Light
    
    	Warning_Light.hold(False) # unfreeze the pin before clearing it
    	Warning_Light.value(0)
    	Warning_Light.hold(True) # freeze the pin for deepsleep
    	print("light off")
    
    # TBRG service routine.
    def RainISR():
    	# This is the routine to service TBRG interrupts/wake ups
    	# This routine gets called on code entry if the wake source is external pin P16.
    	# All this routine needs to do is increment the rain gauge counter
    	# The counter needs to be recovered from NVRAM
    	# At close this routine must save the counter and then reload the sleep timer
    	# This routine gets called at every rain gauge bucket tip.
    	# This is typically at 0.2mm intervals.
    	# The rain bucket volume is defined in hundreths of mm by NV variable RainCal.
    	# It is assumed in here that the Rain Array has 256 elements.
    	# The Rain Array is set up as a circular file.
    	# The index points to the current time slot in the array.
    	# Increment Rain Array value
    	# The RainTipsIndex is stored in NVRAM and managed by the main code.
    
    	global  nvvar
    
    	# may need to disable the wake pin and then re-enable it again on exit to stop multiple triggers!!!!!
    
    	#Rain totals and index recovered on start up using NV_Recover (called from main).
    
    	# now increment the rain totals
    	nvvar['RainTipsArray'][nvvar['RainTipsIndex']] = nvvar['RainTipsArray'][nvvar['RainTipsIndex']] + 1
    
    	# Increment Rain Day total
    	nvvar['RainDayTips'] = nvvar['RainDayTips'] + 1
    
    	# Increment Rain Month Total
    	nvvar['RainMonthTips'] = nvvar['RainMonthTips'] + 1
    
    	# Note - Index for Rain Array (list) will be adjusted by TickISR
    	# Note - Rain Day and Month totals will be reset by TickISR at date/time rollover
    
    	time.sleep_ms(200) # wait for 200ms to debounce TBRG input. This may actually be unnecessary.
    
    	#Save the rain totals prior to sleeping again.
    	NV_Backup()
    
    	# close open files
    	fp.close()
    	nvfp.close()
    
    	#get remaining sleep time
    	remainsleep=machine.remaining_sleep_time()
    	# have a guess at the runtime for this interrupt (ms) assuming it is relatived fixed in length
    	rainrun = RunTimer.read_ms()
    	RunTimer.stop()
    	fiddlefactor = 3375
    	sleeptime = int(remainsleep - rainrun - fiddlefactor)
    	if sleeptime < 1:
    		# need to ensure that we sleep for at least 1ms
    		sleeptime = 1
    	# now go back to sleep for the remaining time
    	machine.deepsleep(sleeptime)
    
    # Hardware init routine defined here
    def HWinit():
    	# need to set up digital and analogue ports here
    	# set outputs to their inactive/default state where needed
    
    	global  Warning_Light, Purge_Pump, Mpx_Pwr, Boom_Up, Boom_Down, Modem_Pwr,Latch_Open, lte
    
    	print("start HW init")
    	Purge_Pump.value(0)
    	Mpx_Pwr.hold(False) # unfreeze pin
    	Mpx_Pwr.value(1)
    	Mpx_Pwr.hold(True) # freeze pin
    	Boom_Up.hold(False) # unfreeze pin
    	Boom_Up.value(0)
    	Boom_Up.hold(True) # freeze pin
    	Modem_Pwr.hold(False) # unfreeze
    	Modem_Pwr.value(1)
    	Modem_Pwr.hold(True) # freeze
    	Boom_Down.hold(False) # unfreeze
    	Boom_Down.value(0)
    	Boom_Down.hold(True) # freeze
    	Latch_Open.hold(False) # unfreeze
    	Latch_Open.value(0)
    	Latch_Open.hold(True) # freeze
    
    	# Turn off the warning lamp and hold the output for deepsleep.
    	Warning_Light.hold(False) # unfreeze the pin before clearing it
    	Warning_Light.value(0)
    	Warning_Light.hold(True) # freeze the pin for deepsleep
    
    	# probably need to set up modem when SMS 3G/4G comms needed. Note this clashes with the REPL serial port.
    
    	# need to set up RTC here. Default to 25 May 2010, 9am.
    	rtc.init((2010, 5, 25, 9, 0, 0, 0, 0))
    
    	# Need to turn of the LTE modem here.
    	# The modem should only be turned on as required to save power.
    	# Note: with the modem normally turned off, we can not wake on SMS messages or other network data signals.
    	lte = LTE()
    	#lte.deinit()
    
    	#flash the warning light
    	WarningLightOn()
    	time.sleep_ms(500)
    	WarningLightOff()
    
    	#run the purge pump to test
    	Purge_Pump.value(1)
    	time.sleep(1)
    	Purge_Pump.value(0)
    
    	print("HW init done")
    
    # General system variable initialisation routine
    def InitGenVars():
    	# need to make sure that general (not NV) variables are intialised prior to use
    	# Probably not needed. Just need to make sure each variable is initialised prior to use in the main code sections
    	print("init gen vars")
    	pass
    
    #Initialise NV variables
    def InitNVvars():
    	# initilise appropriate fields in the NVvar JSON file on power up (but not after wake from sleep)
    	# need to setup and save the raintips array in here.
    
    	global  nvvar
    
    	print("init NV vars")
    	nvvar['RainTipsArray']=[0]*256
    	#set default date/time (Tuftec registration day)
    	nvvar['Year']=10
    	nvvar['Month']=5
    	nvvar['Day']=25
    	nvvar['Hour']=9
    	nvvar['Second']=0
    	#set up other variables
    	# more than really desired here as RAM is not preserved during deepsleep
    	nvvar['First_Sample']=True
    	nvvar['Batt']=100
    	nvvar['Level']=0
    	nvvar['Ticks'] = 0
    	nvvar['TimeCount'] = 0
    	nvvar['RunTimer'] = 0
    	nvvar['Sample_Timer'] = 0
    	nvvar['Comms_Timer'] = 0
    	nvvar['Test_Timer'] = 0
    	nvvar['PurgeTimer'] = 0
    	nvvar['Log_Timer'] = 0
    	nvvar['Old_Level'] = 0
    	nvvar['Rate'] = 0
    	nvvar['RemoteWarningFlag'] = False
    	nvvar['Camera_Timer'] = 0
    	nvvar['SnapNow'] = False
    
    	#do we need to save yet????? Probably just wait for save prior to going to sleep.
    
    def InitLoRa():
    	# Initialize LoRa in LORAWAN mode.
    	# Australia = LoRa.AU915
    
    	global  lora, app_eui, app_key
    
    	print("init lora")
    	lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915)
    
    	# Remove channels that are not required for AU915, TTN
    	for index in range(0, 8):
    		lora.remove_channel(index)
    	for index in range(16, 65):
    		lora.remove_channel(index)
    	for index in range(65, 72):
    		lora.remove_channel(index)
    
    	# need to attempt an OTAA connection here and save the connection parameters for use later (after wake).
    	lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
    
    	# wait until the module has joined the network
    	pycom.rgbled(YELLOW)
    	joincount = 0
    	while (lora.has_joined() != True) and (joincount < 50):
    		time.sleep(2.5)
    		print('Trying to join TTN Network!')
    		joincount = joincount + 1
    	if joincount < 50:
    		print('LoRa network joined!!!!!')
    	pycom.rgbled(GREEN)
    	# wait a while to show status indication
    	time.sleep_ms(200)
    	pycom.rgbled(LEDOFF)
    
    	# now save the LoRa parameters for later
    	lora.nvram_save()
    
    def InitSigfox():
    	### There might not actually need to be an initialisation section for Sigfox!!!! ###
    	# Most of the stuff below will need to execute after a wake also.
    	# init Sigfox for RCZ4 (Australia)
    
    	global  sigfox, SigSock
    
    	print('starting Sigfox connection')
    	pycom.rgbled(MAGENTA)
    	sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ4)
    	# create a Sigfox socket
    	SigSock = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW)
    	# make the socket blocking. Be carefull here!!!! If no response, we will not get past here.
    	SigSock.setblocking(True)
    	# configure it as uplink only. Might need to change this later for downlink data.
    	SigSock.setsockopt(socket.SOL_SIGFOX, socket.SO_RX,False)
    	time.sleep_ms(50)
    	pycom.rgbled(LEDOFF)
    	# should now be ready to send data.
    
    def InitNBIoT():
    	### Initialise FiPy for Nb-Iot use.
    
    	global lte, dipconfig, NBIoTconnect
    
    	print("init NBIot")
    	lte = LTE()
    	# initialise the modem after a deep sleep
    	lte.init()
    	# attempt to make a connetcion to the network.
    	time.sleep(1)
    	apn="apn="+dipconfig['APN']
    	print("send attach", apn)
    	#lte.attach(apn=dipconfig['APN'])
    	lte.attach(band=8, apn="spe.inetd.vodafone.nbiot")
    	# wait until the module has joined the NB-IoT network
    	pycom.rgbled(YELLOW)
    	joincount = 0
    	NBIoTconnect = False
    	while (lte.isattached() != True) and (joincount < 50):
    		time.sleep(2.5)
    		print('Trying to join NB-IoT Network!')
    		joincount = joincount + 1
    	if joincount < 50:
    		print('NB-IoT network connection established!!!!!')
    		NBIoTconnect = True
    	else:
    		print('NB-IoT network connection failed!!!!!')
    	pycom.rgbled(WHITE)
    	# wait a while to show status indication
    	time.sleep_ms(200)
    	pycom.rgbled(LEDOFF)
    	# now set up a data connection
    	joincount = 0
    	lte.connect()
    	while (lte.isconnected() != True) and (joincount <50):
    		time.sleep(2.5)
    		print('Trying to establish data connection with NB-IoT Network.')
    		joincount = joincount + 1
    	if joincount < 50:
    		print('NB-IoT data connection established!!!!!')
    		pycom.rgbled(GREEN)
    		time.sleep_ms(200)
    		pycom.rgbled(LEDOFF)
    	else:
    		print('NO DATA connection!!!!!')
    		#clear connected flag
    		NBIoTconnect = False
    		pycom.rgbled(RED)
    		time.sleep_ms(200)
    		pycom.rgbled(LEDOFF)
    	# now ready to send data
    
    # Routine to recover NVvar variables from JSON file.
    def NV_Recover():
    	# NVvar dictionary is used for storing various bits of state information that needs to be preserved during sleep /resets
    	# Stored in a JSON file in FLASH
    	# on power up the file will get initialised
    
    	global  nvvar, nvfp
    
    	nvfp=open('NVvar.json','r')
    	nvvar=json.load(nvfp)
    
    #routine to save NV variables
    def NV_Backup():
    	# save NVvar data to JSON file
    	# open for writing
    
    	global  nvvar, nvfp
    
    	nvfp=open('NVvar.json','w')
    	nvfp.write(json.dumps(nvvar))
    	# close after write
    	nvfp.close()
    
    def AlarmCheck():
    	# checks for alarms. Called every time system wakes up
    	# need to check water level sensor activation controlled by (P21, active low). ADC1_0 (P13) is input.
    	# need to check battery. ADC1_1 (P14) is input.
    	# calculate rate of rise
    	# update max and min values
    	# check against alarm levels and call alarm action code if needed
    	# check for warning lamp activation and other actions that are needed
    
    	global  Mpx_Pwr, nvvar, Purge_Pump, dipconfig
    
    	# Set MPX_PWR PowerOn
    	Mpx_Pwr=machine.Pin('P21', mode=machine.Pin.OUT)
    	Mpx_Pwr.hold(False) # unfreeze the pin
    	Mpx_Pwr.value(0)
    	for count in range(2):
    		pycom.rgbled(BLUE)
    		time.sleep_ms(100)
    		pycom.rgbled(LEDOFF)
    		time.sleep_ms(200)
    
    	# clear rain warning flag
    	nvvar['RainWarn'] = False
    
    	# Get pressure and battery level data.
    	# Convert pressure to water level and rate values.
    	nvvar['Old_Level'] = nvvar['Level']
    
    	# Read level and round to nearest 24mm by stripping 2 x lsb.
    	# Also work with 10 bit resolution
    	# Max sensor range is 5m @ 4.7V full scale reading.
    	# Allowing for offset adjustment we get a max range of 930 counts for 5m.
    	TempWord = 0
    	adc = machine.ADC()
    	pressure = adc.channel(pin='P13', attn=machine.ADC.ATTN_11DB)
    
    	# Average Level reading for improved accuracy.
    	for count in range(10):
    		TempWord = TempWord + pressure()
    		time.sleep_ms(10)
    
    	# turn level (pressure) sensor off
    	Mpx_Pwr.value(1)
    	Mpx_Pwr.hold(True) # freeze the pin ready for deepsleep
    
    	# convert to mm measurement
    	TempWord = (TempWord / 40) # also adjust back to 10 bit resolution
    	print('raw pressure value = ', TempWord)
    	Measured = int(((TempWord - dipconfig['Cal_Offset']) * dipconfig['Level_K']) / 11)
    
    	# Adjust for underflow. Just make it zero.
    	if Measured < 0:
    		Measured = 0
    	#End If
    	nvvar['Level'] = Measured + dipconfig['Zero_Depth']
    	if nvvar['First_Sample'] == True:
    		nvvar['Old_Level'] = nvvar['Level']
    		nvvar['First_Sample'] = False
    	#End If
    
    	# Rate result is in mm/hr.
    	nvvar['Rate'] = int((((nvvar['Level'] - nvvar['Old_Level']) * 80) / dipconfig['SampleNow']) * 45)
    	if nvvar['Rate'] < 0:
    		# if rate is decreasing, just set it to 0.
    		nvvar['Rate'] = 0
    	#End If
    
    	# Wait a while for battery voltage to settle.
    	time.sleep(5) # this was 15 in original DipStik code. Don't think we need to wait this long.
    
    	# At this point modem, camera, pump and warning lamp need to be off to get close to O/C volts of battery.
    	##### Do not have direct control of lamp. So just assume all ok and ignore it. #####
    	# Batt result is in %. Capped at 100% to exclude overvoltage on charging.
    	# When the solar panel is not charging, the battery measurement will be close to O/C volts.
    
    	# All other peripherals should be off at this point also.
    	TempWord = 0
    
    	battery = adc.channel(pin='P14', attn=machine.ADC.ATTN_11DB)
    
    	# Average battery voltage over 10 readings to remove effects of noise.
    	for count in range(10):
    		TempWord = TempWord + battery()
    		time.sleep_ms(10)
    	TempWord = (TempWord / 40) # also adjust back to 10 bit resolution
    	print('Batt ADC value = ', TempWord)
    	if TempWord < dipconfig['LowVolts']:
    		nvvar['Batt'] = 0
    	else:
    		# convert to % full.
    		nvvar['Batt'] = int((TempWord - dipconfig['LowVolts'])* 100 / 79)
    	#End If
    	RealBatt = nvvar['Batt']
    	if nvvar['Batt'] > 99:
    		nvvar['Batt'] = 100
    	#End If
    	print('RealBatt = ', RealBatt)
    
    	# Record Min/Max battery levels
    	if RealBatt > nvvar['Batt_Max']:
    
    		# Set Batt Max
    		nvvar['Batt_Max'] = RealBatt
    		#NV_Backup
    	#End If
    	if RealBatt < nvvar['Batt_Min']:
    
    		# Set Min Batt value
    		nvvar['Batt_Min'] = RealBatt
    		#NV_Backup
    	#End If
    
    	#need to put stuff in here to deal with other alarms.
    
    	#check if we need to turn on Warning lamp.
    	if nvvar['Level'] > dipconfig['Warning_Level']:
    		# turn on warning lamp
    		WarningLightOn()
    	else:
    		# turn off
    		WarningLightOff()
    	#endif
    
    	nvvar['Sample_Timer'] = 0
    
    
    def PurgeNow():
    	# this code controls the purge pump (P11, active high)
    	# NOTE THIS PIN CAN NOT BE HELD DURING DEEPSLEEP. But it does not matter.
    
    	global  Purge_Pump, nvvar, dipconfig, fp
    
    	print('check that we can purge')
    	if (dipconfig['FwsConfig'] & 1) == 0:
    		# We only want to execute the pump purge when Wig Wag option not set!!!!
    		print('Need to activate purge pump')
    		Purge_Pump=machine.Pin('P11', mode=machine.Pin.OUT)
    		#Set PurgePump On
    
    		Purge_Pump.value(1)
    		# Run the pump for a short period of time to clear out the sense line
    		time.sleep(2)
    		#Set PurgePump Off
    		Purge_Pump.value(0)
    		# Wait for the pressure to stabilise in the line.
    		time.sleep(2)
    		if dipconfig['PurgePeriod'] == 0:
    
    			# PurgePeriod of 0 is just for operation testing.
    			# Only want a single purge to test operation. Then set to 24 hour purge period.
    			print('reset the purge pump')
    			dipconfig['PurgePeriod'] = 24
    			# now need to write this back to the config file
    			fp=open('DipConfig.json','w')
    			fp.write(json.dumps(dipconfig))
    			time.sleep(1)
    			fp.close()
    			# open again for later reading
    			fp=open('DipConfig.json','r')
    		# End If
    	# End If
    	print('clear the purge timer')
    	nvvar['PurgeTimer'] = 0
    
    def CommsCheck():
    	# checks for commands recieved. Primarily for SMS but also to periodically check LoRa, Sigfox & NB-Iot.
    	global lora, app_eui, app_key, lte, NBIoTconnect
    
    	# Check for SigFox messages
    	if dipconfig['SigfoxEnable'] == True:
    		InitSigfox()
    		# send some bytes
    		# Cayenne, LPP style data format
    		# channel 1 = level data, analogue input, 2 bytes
    		# channel 2 = battery, analogue input, 2 bytes
    		print('send data via Sigfox')
    		SigSock.send(bytes([0x01, 0x02])+pack('>h',nvvar['Level']&0xffff)+bytes([0x02, 0x02])+pack('>h',nvvar['Batt']&0xffff))
    		SigSock.close()
    
    	if dipconfig['LoRaEnable'] == True:
    		# set up the radio system for LoRaWAN
    		lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915)
    
    		# recover LoRa communications parameters and set previous state
    		lora.nvram_restore()
    
    		# Remove channels that are not required for AU915, TTN
    		for index in range(0, 7):
    			lora.remove_channel(index)
    		for index in range(16, 65):
    			lora.remove_channel(index)
    		for index in range(66, 72):
    			lora.remove_channel(index)
    
    		# wait a short time to ensure LoRa is set up.
    		time.sleep(1)
    		if not lora.has_joined():
    			print('something wrong with recovered LoRa state')
    			# try to join a network using OTAA if we do not have a valid connection
    			lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
    			# wait until the module has joined the network
    			pycom.rgbled(YELLOW)
    			joincount = 0
    			while (lora.has_joined() != True) and (joincount < 50):
    				time.sleep(2.5)
    				print('Re-Trying to join TTN Network!')
    				joincount = joincount + 1
    			# saving the state
    			lora.nvram_save()
    			pycom.rgbled(GREEN)
    			time.sleep_ms(200)
    			pycom.rgbled(LEDOFF)
    		#print('Now send some data')
    
    		# create a LoRa socket
    		LoRaSock = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
    
    		# set the LoRaWAN data rate
    		LoRaSock.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)
    
    		# make the socket blocking
    		# (waits for the data to be sent and for the 2 receive windows to expire)
    		# this should take about 2 seconds.
    		LoRaSock.setblocking(True)
    
    		# IF THERE IS SOMETHING WRONG WITH THE SETUP WE WILL PROBABLY HANG IN HERE.
    		# send some data
    		# Cayenne, LPP style data format
    		# channel 1 = level data, analogue input, 2 bytes
    		# channel 2 = battery, analogue input, 2 bytes
    		print('sending LoRa data')
    		LoRaSock.send(bytes([0x01, 0x02])+pack('>h',nvvar['Level']&0xffff)+bytes([0x02, 0x02])+pack('>h',nvvar['Batt']&0xffff))
    
    		# make the socket non-blocking
    		# (because if there's no data received it will block forever...)
    		LoRaSock.setblocking(False)
    
    		# get any data received (if any...)
    		LoRaRxdata = LoRaSock.recv(64)
    		print("data received = ", LoRaRxdata)
    		lora.nvram_save()
    		LoRaSock.close()
    
    	if dipconfig['NBIoTEnable'] == True:
    		# set up the radio system for NB-Iot
    		InitNBIoT()
    
    		if NBIoTconnect == True:
    			# create an lte socket
    			LTESock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    			# get the IP address and port numbers for the cloud app
    			# just use FWS gateway details for the time being. Port number needs to be different than used for LoRa and Sigfox.
    			# this enables source streams to be seperated at the cloud application end
    			servaddr = (dipconfig['FwssgIP'], dipconfig['RedPort'])
    			print('set up LTE socket, sending NB-IoT data')
    			# now send some data
    			# Cayenne, LPP style data format
    			# channel 1 = level data, analogue input, 2 bytes
    			# channel 2 = battery, analogue input, 2 bytes
    			LTESock.sendto(bytes([0x01, 0x02])+pack('>h',nvvar['Level']&0xffff)+bytes([0x02, 0x02])+pack('>h',nvvar['Batt']&0xffff), servaddr)
    
    			# now make the socket non-blocking
    			# (because if there's no data received it will block forever...)
    			LTESock.setblocking(False)
    			print("data sent")
    
    			# get any data received (if any...). Wait a bit first.
    			time.sleep(1)
    			LTERxdata = LTESock.recv(64)
    			print("data received = ", LTERxdata)
    			LTESock.close()
    			# wait a while and then put the modem to sleep.
    			time.sleep(2)
    		# turn off the LTE modem, but do not deattach
    		lte.deinit(dettach=False)
    
    # ##########
    
    
    	nvvar['Comms_Timer'] = 0
    	print("finishing up")
    	pass
    
    def RemoteWarningCheck():
    	# need to add code here to check for remote control activation by other DipStik units
    	pass
    
    def TestCheck():
    	# periodic system health check
    	# sends heartbeat messages (when relevant) via the configured comms channel(s)
    	# might not be necessary since communications are more frequent than SMS based DipStik units.
    	# heart beat message sending code needs to go in here.
    	nvvar['Test_Timer'] = 0
    	pass
    
    def TakeSnap():
    	# takes image using camera and shuffles data down the comms channel.
    	# only relevant for 3G/4G and possibly NB-CatM when configured.
    	# not enough bandwidth to be usable with LoRa, Sigfox and NB-IoT.
    	# leave this here in case code ends up dealing with 4G/5G or Cat-M1 connections
    	pass
    
    #### THIS IS THE MAIN ROUTINE ####
    def AppMain():
    
    	global  dipconfig, app_eui, app_key, fp, nvfp, NBIoTconnect
    
    	# recover the system configuration variables from the JSON file.
    	fp=open('DipConfig.json','r')
    	dipconfig=json.load(fp)
    
    	# recover the NVvar variables from Flash (also a JSON file).
    	NV_Recover()
    
    	# get LoRa device parameters from config file.
    	app_eui = binascii.unhexlify(dipconfig['AppEui'].replace(' ',''))
    	app_key = binascii.unhexlify(dipconfig['AppKey'].replace(' ',''))
    
    	# Test reset/wake reason to work out required code entry point.
    	ResetCause=machine.reset_cause()
    	WakeCause=machine.wake_reason()
    	# Note - WakeCause is a tuple!
    
    	if ResetCause == machine.DEEPSLEEP_RESET and WakeCause[0] == machine.PIN_WAKE:
    		# This is as close as we can get to a real time interrupts from deepsleep
    		# this is for TBRG interrupts (rain gauge bucket tips)
    
    		#blink LED
    		pycom.rgbled(CYAN)
    		time.sleep_ms(100)
    		pycom.rgbled(LEDOFF)
    
    		print('RAIN BUCKET TIP')
    		RainISR() # Call the Rain (TBRG) handler. There is no return from this routine. Just results in deepsleep again
    	#End If reset cause due to TBRG
    
    	if ResetCause != machine.DEEPSLEEP_RESET:
    		###### POWER UP entry point (power up or reset condition) ######
    		### Could also be a watch dog time out ###
    		# we need to initialise everything first
    		# Note: This section gets skipped after a normal wake from deepsleep
    
    		print('SYSTEM RESTART or WDT')
    		# All the application specific hardware bits are set up here.
    		# indicate a power up condition
    		pycom.rgbled(RED)
    		time.sleep_ms(500)
    		pycom.rgbled(GREEN)
    		time.sleep_ms(500)
    		pycom.rgbled(BLUE)
    		time.sleep_ms(500)
    		pycom.rgbled(LEDOFF)
    
    		# Initialise the HW
    		HWinit()
    
    		# Call variable init routines
    		InitNVvars()
    
    		# Set up serial ports as needed
    			#required for camera and 3G/4G modem. Also used for micropython debug/REPL
    
    		# Init LoRaWAN system as needed
    		if dipconfig['LoRaEnable'] == True:
    			InitLoRa()
    		#End If
    
    		# Init Sigfox system as needed
    		if dipconfig['SigfoxEnable'] == True:
    			InitSigfox()
    		#End If
    
    		#Init NB-IoT connection as needed
    		if dipconfig['NBIoTEnable'] == True:
    			NBIoTconnect = False
    			InitNBIoT()
    			# now put the modem to sleep.
    			# turn off the LTE modem, but do not deattach
    			lte.deinit(dettach=False)
    		#End If
    
    		# Set up 3G/4G/5G modem connection as needed
    
    		# Set up camera system
    			# only needed for 3G/4G/5G modem or maybe Cat-M1
    
    		# Set system time (from mobile network???)
    			# not sure how this should be handled.
    			# Maybe need to think about low cost GPS module to get time and position information??????? Adds extra cost though.
    			# For the time being, just set it to some default date. (Gets initialised with NVvar init stuff and HWInit section)
    			# currently the RTC is set to a default value in HWinit.
    			# probably only really needed to ensure rain totals roll over correctly (9am and 1st of month)
    			# Should be able to get time in an NB-IoT/Cat-M1 network and 3G/4G/5G but may be difficult with other networks.
    	#End If reset cause not deepsleep
    
    	# set up the WDT
    	wdt = WDT(timeout=130000) # set up WDT for 130 secs. There should not be anything that runs longer than this time.
    	while True:
    		###THIS IS THE MAIN LOOP.###
    		# Functions run to completion then the code sleeps for a while.
    		# The actual while statement is probably not realy needed
    
    		time.sleep_ms(100)
    		pycom.rgbled(GREEN) # green
    		time.sleep_ms(50)
    		pycom.rgbled(LEDOFF) # off
    
    		# need to call TickISR to update all of the system timers and soft RTC.
    		TickISR() # maybe this should be done at an earlier stage????
    
    		print('date/time = ',nvvar['Year'],nvvar['Month'],nvvar['Day'],nvvar['Hour'],nvvar['Minute'])
    		if (nvvar['Sample_Timer'] >= dipconfig['SampleNow']):
    			AlarmCheck() # checks for various alarm conditions
    			#check trigger conditions for both LOCAL & REMOTE actions.
    			RemoteWarningCheck() # check for remote warnings to be sent to other devices (not needed at present)
    		#End If
    		if (nvvar['Comms_Timer'] >= dipconfig['CommsNow']):
    			CommsCheck()
    		#End If
    		if (nvvar['Test_Timer'] >= dipconfig['TestNow']):
    			TestCheck()
    		#End If
    		print('PurgeTimer = ',nvvar['PurgeTimer'],' PurgePeriod = ',dipconfig['PurgePeriod'])
    		if (nvvar['PurgeTimer'] >= dipconfig['PurgePeriod']):
    			print('calling PurgeNow')
    			PurgeNow()
    		#End If
    		if (dipconfig['SvrIP'] != "") or (dipconfig['SvrName'] != ""):
    			#The camera code will only be executed if the Image Server IP or DN have been defined.
    			#Both SrvIP and SrvName must be set to NULL to disable camera!!!!
    			if (nvvar['Camera_Timer'] >= dipconfig['SnapTime']):
    				nvvar['Camera_Timer'] = 0
    				TakeSnap()
    			#End If
    			if (nvvar['Camera_Timer'] >= dipconfig['AlarmSnapTime']) and (nvvar['Alarm_Active'] == True):
    				nvvar['Camera_Timer'] = 0
    				TakeSnap()
    			#End If
    			if nvvar['SnapNow'] == True:
    				nvvar['SnapNow'] == False
    				TakeSnap()
    			#End If
    		#End If
    		#if MODEM_PWR = PowerOn:
    
    			#Send network detach instruction to modem to close off cleanly.
    			#This is to minimise the posibility of unintended network deregistration/barring
    			#HSerPrint "AT+CFUN=0"
    			#HSerSend CR
    
    			#Give some time to detach.
    			#Wait 5 s
    			#Set MODEM_PWR PowerOff
    		#End If
    		if (nvvar['FirmwareDownload'] == True):
    
    			#Need to clear AppVersion and reboot
    			#This tells the BootLoader to check for updates.
    			dipconfig['AppVersion'] = 0
    
    			# Save it to JSON config file
    			NV_Backup()
    			fp=open('DipConfig.json','w')
    			fp.write(json.dumps(dipconfig))
    			fp.close()
    
    			#indicate firware update requested.
    			for count in range (4):
    				pycom.rgbled(BLUE) # blue
    				time.sleep_ms(200)
    				pycom.rgbled(LEDOFF) # off
    				time.sleep_ms(300)
    				pycom.rgbled(GREEN) # green
    				time.sleep_ms(200)
    				pycom.rgbled(LEDOFF) # off
    				time.sleep_ms(300)
    
    			# Old code. NEED TO CONVERT ALL THIS TO PYTHON CODE
    			#if MODEM_PWR = PowerOn:
    
    				#Send network detach instruction to modem to close off cleanly.
    				#This is to minimise the posibility of unintended network deregistration/barring
    				#HSerPrint "AT+CFUN=0"
    				#HSerSend CR
    
    				#Give some time to detach.
    				#Wait 5 s
    				#Set MODEM_PWR PowerOff
    			#End If
    			# Reset CPU to enable firware update to happen. Actual update gets handled by the DipStik BootLoader
    			machine.reset()
    		#End IfLoop Firmware download
    
    		# now finish up and go to sleep
    
    		# save NVvar back to file
    		NV_Backup()
    
    		# close open files
    		fp.close()
    		nvfp.close()
    		# guess the runtime up until this point
    		##### need a better way to compensate for runtime in the longer term #####
    		##### MAYBE JUST NEED TO FIND A WAY TO EFFECTIVELY USE THE RTC #####
    		runtime = RunTimer.read_ms()
    		RunTimer.stop()
    		fiddlefactor = 3375
    		# adjusts for startup/boot time not measured by runtimer.
    		#print('runtime = ', runtime)
    		# set sleep time to 120 seconds.
    		sleeptime = int(120000 - runtime - fiddlefactor)
    		if sleeptime < 1:
    			# need to ensure that we sleep for at least 1ms
    			# not sure what happens if sleeptime goes negative
    			sleeptime = 1
    		#print('sleeptime = ', sleeptime)
    		if dipconfig['NBIoTEnable'] == True:
    			#lte=LTE()
    			#lte.dettach()
    			# turn off the LTE modem, but do not deattach
    			#lte.deinit(deattach=False)
    			print('disabling LTE modem')
    		#End If
    
    		print('feed the dog')
    		wdt.feed() # if the deepsleep call fails this should result in a system restart.
    		print('going to sleep for: ',sleeptime,' ms')
    		machine.deepsleep(sleeptime)
    		#time.sleep_ms(sleeptime)
    		#machine.deepsleep(1)
    	# End While True
    #End AppMain
    


  • @jcaron Sorry for the lack of information. Here is a bit more background:

    • using FiPy (latest hardware)
    • problem occurs whether using LoRaWAN, Sifox or NB-IOT
    • wifi is disabled at the time.
    • LDO 5V regulator (3A output) and large caps 470uF + 100nF caps are located right at the socket for the device
    • can not post complete code here at present, too large and commercially sensitive. I will need to cut it down and test again first.
    • currently using firmware v1.20.0.rc7
    • no graphs available at present. Have monitored 3V3 rail (out of FiPy) with 60MHz DSO. Only real power issue observed is large dip of about 10us when unit comes out of reset/sleep. I suspect this is when the power supply gets switched from low power to high power within the module. Otherwise, voltage ripple is only 10's of mV.

    Peter.



  • @tuftec You should probably include details of your exact setup, as well as your code (most notably, anything related to any the wireless interfaces) and the firmware version in use.

    Any graph of power consumption or any other characteristic signal while failing to go to sleep could probably help pinpointing the issue.



  • I have now added WDT commands to my code. I feed the watch dog just prior to making the deepsleep call.

    I can now confirm that the deepsleep call definitely fails, as the WDT kicks in and resets the system.

    Can someone please look into this issue. It appears to be a bug in the Pycom firmware.


Log in to reply
 

Pycom on Twitter