8. Logging data to the microSD card - ControlBits/EMIT GitHub Wiki

The EMIT development board, includes a microSD card socket to provide local data storage.

The microSD card can be used for many tasks such as storing temperature and humidity measurements in an environmental monitoring application (as covered in this guide), to buffer data while awaiting transmission via MQTT or to store hardware settings.

In this case, we're going to store our timestamp, temperature and humidity data to a .csv (comma seperated value) file called 'data.csv'.

8.1 Setting up the microSD card socket

The standard MicroPython distribution for the ESP32 includes all of the basic SD card functional building blocks we need in the ‘machine’ and ‘os’ libraries. We just need to configure the GPIO pins and write a couple of helper functions.

We already have 'machine' imported so we just need to import the 'os' module in our boot.py:

import os

EMIT's microSD Card socket is connected to the ESP32's SPI port on ‘Slot 2’ (i.e. SCK = GPIO18, CS = GPIO5, MISO = GPIO19 & MOSI = GPIO23). There is also a hardware ‘Card Detect’ (CD) switch connected to GPIO4 pin.

Setting up the microSD card socket's pins in boot.py is handled by the machine.SDCard() function:

sd_card = machine.SDCard(slot=2, width=1, cd=machine.Pin(4) ,sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19), cs=machine.Pin(5))

Next, we need to define the name of the file we wish to write to on the microSD card:

fileName = '/sd/data.csv'  # name of file (data.csv) to save data to on SD Card

We also need to define a variable 'sd_mounted' to hold the status of the microSD card mounting process:

sd_mounted = 0

Finally, we need to create a couple of functions ... the first will to attempt to mount the microSD card to the ESP32's operating system. With some error handling, our 'mount_sd()' function is:

# define function to mount SD card
def mount_sd():
  global sd_mounted   # use the global variable: sd_mounted
  try:
    os.mount(sd_card, '/sd')      # try to mount the SD Card to the OS as '/sd'
    print("SD Card mounted!")     # print success message to Shell
    sd_mounted = 1                # set sd_mounted to 1
  
  except OSError as e:
    print('SD Card error: ', e)   # else print error message to Shell
    sd_mounted = 0                # set sd_mounted to 0

We also need a function, 'write_to_sd(data)', to write data to the microSD card:

# define function for writing data to SD Card
def write_to_sd(data):
  with open(fileName, 'a') as f:  # open file 'fileName' (as f) for appending
    f.write(data)                 # write data to file

The completed boot.py code should now look like this:

# general board setup
import machine, time, dht
import network, ntptime
import os

from umqtt.simple2 import MQTTClient
import ubinascii

# configure GPIO pins
RedLED = machine.Pin(16, machine.Pin.OUT)
RedLED.value(0)

GreenLED = machine.Pin(17, machine.Pin.OUT)
GreenLED.value(0)

AM2302 = dht.DHT22(machine.Pin(14))

Relay = machine.Pin(26, machine.Pin.OUT)
Relay.value(0)

relayState = 'OFF'        # define relayState string and set to 'OFF'
tempSet = 20.0            # temerature set point for relay
hysteresis = 0.5          # relay control loop hysteresis

# configure SDCard port
sd_card = machine.SDCard(slot=2, width=1, cd=machine.Pin(4) ,sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19), cs=machine.Pin(5))

  
# define Wi-Fi settings
wifiSSID = 'your-WiFi-SSID-goes-here'
wifiPasswd = 'your-WiFi-password-goes-here'

# Please generate a uniquie UUID at: https://www.uuidgenerator.net/ and replace the one below
# Don't forget the single quotations start and finish!
deviceUUID = '1116fa09-973c-4fa3-aff1-3f9859c819ff'

# define MQTT settings
client_id = ubinascii.hexlify(deviceUUID)
topic = bytes(deviceUUID, 'utf-8')

mqtt_broker = 'public.mqtthq.com' 
mqtt_port = 1883


fileName = '/sd/data.csv'  # name of file to save data to on SD Card
sd_mounted = 0


# define function for setting up Wi-Fi network
def wifi_connect():
    wifi = network.WLAN(network.STA_IF)  # create our 'wifi' network object
    wifi.active(True)  # turn on the Wi-Fi hardware 

    # if not connected ...
    while wifi.isconnected() == False:
        
        GreenLED.value(1)  # turn Green LED ON
        print('trying WiFi connection ', wifiSSID)  # print 'trying..' message to Shell 
  
        wifi.connect(wifiSSID, wifiPasswd)  # try connecting to wifi 
    
        time.sleep(1)  # wait 1 second 

        GreenLED.value(0)  # turn Green LED OFF
    
        time.sleep(2)  # wait 2 second 
        
    # if connected ...
    GreenLED.value(1)  # turn Green LED ON
    print('WiFi connection successful')  # print 'WiFi connection successful' message to Shell 
    print(wifi.ifconfig())  # print WiFi network settings (inc. IP Address) to Shell
    
    
# define function to synchronise local clock with NTP time
def sync_to_NTP():
    ntpConnected = 0   # initialise an 'ntpConnected' variable to hold status, set it to 

    while ntpConnected == 0:   # while not connected to NTP network ...
  
        try:
            print('Trying to Sync with NTP servers')   # print debug message to Shell
            ntptime.settime()   # set localtime to NTP time
            ntpConnected = 1   # set ntpConnected status to 1
  
        except:
            print ('NTP error')   # print debug message to Shell
            ntpConnected = 0   # set ntpConnected status to 0

    print('Local time synchronised: ', get_time_str()) # print the new localtime as a string
    
    
# define function for getting local time as string
def get_time_str():
    my_time = time.localtime()   # get 'localtime' and store it in a variable 'my_time'
    my_time_string = '{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'.format(my_time[0], my_time[1], my_time[2], my_time[3], my_time[4], my_time[5])   # convert localtime to a formatted text string 'my_time_string'
    return my_time_string  # return the formatted string


# define function to connect to MQTT broker
def mqtt_connect():
  global client_id, mqtt_broker, mqtt_port   # use the global variables: client_id, mqtt_broker, mqtt_port
  
  client = MQTTClient(client_id, mqtt_broker, mqtt_port)  # create MQTT client using our MQTT settings

  # initialise our local variables (used to manage the connection process)
  broker_connected = 0   # broker connection status register
  connect_attempt = 1    # keep track of the number of connection attempts
  max_con_attempts = 5   # set the maximum number of connection attempts - we'll reboot after this number of attempts
  
  while (connect_attempt <= max_con_attempts) and broker_connected == 0:
      
      print('Connecting to MQTT broker:  '+mqtt_broker+', publishing to topic: ' + topic.decode("utf-8") + 'Attempt No. :' + str(connect_attempt))
      
      try:
          client.connect()   # attempt to connect to the MQTT broker
          
          print("Connection successful")   # if successful, send success message to Shell
          broker_connected = 1             # set broker connection status register to 1 
          
      except:
          print("Connection attempt failed")   # if NOT successful, send debug message to Shell
          
          if connect_attempt < max_con_attempts:      # if current connection attempt is less than max connection attempts ...
              connect_attempt = connect_attempt + 1   # incrament the connection_attempt count
              time.sleep(2)                           # wait 2 seconds before re-attempting a connection

          else:
              print('Failed to connect to MQTT broker after ' + str(connect_attempt) + ' attempts. Rebooting...')  # if max_con_attempts reached ...
              time.sleep(2)     # wait 2 seconds
              machine.reset()   # reset the ESP32 ... forcing a full re-connection process including Wi-Fi              

  return client   # if connection successful, return the client object


# define function to mount SD card
def mount_sd():
  global sd_mounted   # use the global variable: sd_mounted
  try:
    os.mount(sd_card, '/sd')      # try to mount the SD Card to the OS as '/sd'
    print("SD Card mounted!")     # print success message to Shell
    sd_mounted = 1                # set sd_mounted to 1
  
  except OSError as e:
    print('SD Card error: ', e)   # else print error message to Shell
    sd_mounted = 0                # set sd_mounted to 0
    
    
# define function for writing data to SD Card
def write_to_sd(data):
  with open(fileName, 'a') as f:  # open file 'fileName' (as f) for appending
    f.write(data)                 # write data to file

8.2 Using the microSD card

We're now ready to use our new microSD card functions in our main.py. First we need to mount the microSD card using:

# mount the microSD card
mount_sd()

Once we have checked that the microSD card is successfully mounted (i.e. sd_mounted == 1), all we need to do is pass the data we wish to write to the microSD card as a string to our write_to_sd() function.

In this case, we're going to concatenating a comma separated string containing our timestamp (get_time_str()), temperature (tempC) and humidity (humidity) data as follows:

  if sd_mounted == 1:     # if SD card is mounted, write timestamp, temperature and humidity data to it
    write_to_sd(get_time_str() + ',' + str(tempC) + ','  + str(humidity) + '\n')

And that's all there is to it ... now EMIT can record it's measurements to it's microSD card!

For reference, the completed main.py is:

# connect to wifi
wifi_connect()

# wait 5 seconds for network to settle
time.sleep(5)

# synchronise local clock with NTP time
sync_to_NTP()

# wait 1 second
time.sleep(1)

# mount the microSD card
mount_sd()

# set up MQTT connection and subscribe
client = mqtt_connect()
  

# this is the main program loop
while True:

  time.sleep(5)     # wait 5 seconds 
    
  RedLED.value(1)   # turn RedLED ON
  
  AM2302.measure()  # start AM2302 measurement
  
  RedLED.value(0)   # turn RedLED OFF

  tempC = AM2302.temperature()   # get temperature (Celsius) from AM2302
  humidity = AM2302.humidity()   # get humidity from AM2302
  
  if sd_mounted == 1:     # if SD card is mounted, write timestamp, temperature and humidity data to it
    write_to_sd(get_time_str() + ',' + str(tempC) + ','  + str(humidity) + '\n')

  # add relay control loop    
  if tempC >= tempSet:
    Relay.value(0)        # turn relay OFF
    relayState = 'OFF'    # set relayState string to 'OFF'
    
  if (tempC > (tempSet - hysteresis)) and relayState == 'OFF':
    Relay.value(0)        # keep relay off
    relayState = 'OFF'    # set relayState string to 'OFF'    
      
  else:
    Relay.value(1)        # turn relay ON
    relayState = 'ON'     # set relayState string to 'ON'
  
  
  JSONstring ='{"TimeUTC":"' + get_time_str() + '","TemperatureC":"' + str(tempC) + '","Humidity":"'  + str(humidity) + '","TempSetPointC":"' + str(tempSet) + '","Relay":"'  + relayState + '"}'
  print(JSONstring)
  
  MQTTmsg = bytes(JSONstring, 'utf-8')
  
  try:
    client.publish(topic, MQTTmsg)

  except:
    print("Can not publish message - reconnecting")
    mqtt_connect()