Techschool-temp-sensor

From Sensors in Schools
Jump to navigation Jump to search

Overview

  • This project is the proud creation of students from the Whittlesea Tech School.
  • It is a temperature sensor that can send data using LoRa to The Things Network (TTN).
  • Data is published using a MQTT feed from TTN.
  • A Raspberry Pi 4 running Node-RED subscribes to the MQTT feed to obtain the data.
  • The Raspberry Pi sends processed data to Dweet.io
  • Data in Dweet.io can then be accessed by Freeboard.io to create a public Dashboard.

techschool-temp-sensor Dashboard link

Freeboard.io Dashboard

techschool-temp-sensor Dashboard link

Construction of temperature sensor

Construction of LoPy4 temperature sensor

The Things Network

Payload Formatter

function decodeUplink(input) {
var bytes = input.bytes;

var soil_data = (bytes[0] * 256 + bytes[1]);
var bat_data = (bytes[2] * 256 + bytes[3]);
var temp_data = (bytes[4] * 256 + bytes[5]);
var humid_data = (bytes[6] * 256 + bytes[7]);

  return {
    data: {
      soil:soil_data/100,
      bat:bat_data/100,
      temp:temp_data/100,
      humid:humid_data/100
    },
    warnings: [],
    errors: []
  };
}

MQTT

MicroPython code for Pycom LoPy4 microcontroller

  • There are several different code segments used on the Pycom LoPy4 microcontroller to monitor temperature.
    • boot.py - bootstrap program that runs first.
    • main.py - main program where program code is contained
    • pymakr.conf - configuration file for Atom and Pymakr package
    • lib - directory that contains additional library files
      • config.py - list of static variables
      • dth.py - library for DHT22 internal temperature sensor
      • lora.py - library for LoRa. Contains private keys
      • onewire.py - onewire library to communicate with DS18B20 external temperature sensor


boot.py

import pycom
from machine import UART
import machine
import os
from network import Bluetooth
from network import WLAN
# test
if pycom.heartbeat() == True:
    pycom.heartbeat(False)

if pycom.wifi_on_boot() == True:
  pycom.wifi_on_boot(False)

#wlan = WLAN()
#wlan.deinit()

bluetooth = Bluetooth()
bluetooth.deinit()

uart = UART(0, baudrate=115200)
os.dupterm(uart)

machine.main('main.py')

main.py

# main.py
import binascii #module that makes conversion between binary and Ascii
import config
from dth import DTH
import gc # garbage collection
from lora import LoraAU915
import machine
from machine import Pin
from machine import ADC
from onewire import DS18X20
from onewire import OneWire
import pycom
import network
import socket
import time
import ustruct
import utime

#INIT EVERYTHING test
sleep_duration = 600  # in seconds (10 minutes)
pycom.rgbled(config.GREEN)

# power up sensors
sensor_power = Pin(config.POWER_PIN, mode=Pin.OUT)  # power for all sensors
sensor_power.value(1)  # power up
time.sleep(2)   # wait 2 seconds to stabilise

print("starting main")
pycom.rgbled(config.OFF)

# voltage divider setup to monitor battery voltage
adc = ADC()
time.sleep(2)   # wait 2 seconds to stabilise

# garbage collection enabled
gc.enable()

# config LoRa communications
s = LoraAU915.setupLora()

####################################
### LOOP IF NO NEED TO SLEEP #######
####################################

while True:
    sensor_power.value(1)  # power up
    time.sleep(1)
    pycom.rgbled(config.ORANGE)
    time.sleep(1)
    pycom.rgbled(config.OFF)


    # TEMPERATURE AND HUMIDITY OF AMBIENT AIR
    j=0
    th = DTH(config.DHT22_DATA,1)
    time.sleep(1)
    while j < 5:
        result = th.read()
        if result.temperature != 0:
            print("temperature is not zero - breaking from while loop")
            break
        time.sleep(1)
        j += 1
        print("Error code %d" % result.error_code)
        print(j)
    print("Temperature = = %.2f C" % result.temperature)
    print("Humidity = %.2f %%" % result.humidity)
    print("Error code final %d" % result.error_code)

    # BATTERY
    meanbatt=0
    j=0
    while j < 5:
        utime.sleep(0.2)
        batt = adc.channel(attn=3, pin=config.VOLTMETER)
        meanbatt += batt.voltage()/1000
        j += 1
    batt_volt = (meanbatt/j)*config.BATCOEFF #multiplcator coef to adjust the real value of the battery voltage
    print("Battery voltage = %.2f V" % batt_volt)

    #DS18B20 TEMPERATURE SENSOR
    #https://docs.pycom.io/tutorials/hardware/owd/#app

    #Also note - needed to add a 10k resistor pull up on the board
    #Internal pull up resistor was not sufficient
    #used this reference for Raspberry Pi circuit which includes 5k to 10k pull up resistor
    #https://www.circuitbasics.com/raspberry-pi-ds18b20-temperature-sensor-tutorial/

    ow = OneWire(Pin(config.DS18B20_DATA))
    temp = DS18X20(ow)

    soil_temp = temp.start_conversion()
    time.sleep(1)
    soil_temp = temp.read_temp_async()
    time.sleep(1)


    if soil_temp != None:
        print("Soil temperature = %.2f C" % soil_temp)
    else:
        print("No data received from soil temp probe")
        pycom.rgbled(config.RED)
        time.sleep(1)
        pycom.rgbled(config.OFF)
        soil_temp = 0.0

    soil_temp_t = int(soil_temp*100)
    print(soil_temp_t)
    batt_volt_t = int(batt_volt*100)
    print(batt_volt_t)
    sens_temp = int(result.temperature*100)
    print(sens_temp)
    sens_humi = int(result.humidity*100)
    print(sens_humi)

    # converting 2 byte values into single bytes for transmission
    soil1 = int(soil_temp_t//256)
    soil2 = int(soil_temp_t%256)
    batt1 = int(batt_volt_t//256)
    batt2 = int(batt_volt_t%256)
    temp1 = int(sens_temp//256)
    temp2 = int(sens_temp%256)
    humi1 = int(sens_humi//256)
    humi2 = int(sens_humi%256)

    print("sending to TTN")
    s.setblocking(True)
    print(soil1,soil2,batt1,batt2,temp1,temp2,humi1,humi2)
    s.send(bytes([soil1,soil2,batt1,batt2,temp1,temp2,humi1,humi2]))
    s.setblocking(False)

    # Save LoRa settings before deep sleep
    LoraAU915.saveLora()
    print("going to sleep")
    sensor_power.value(0)  # power down

    # note that batt_volt has been multiplied by 100 for transmission
    if (batt_volt > 4.0):
        # to minimise writes to onboard memory and for teaching
        sleep_duration = 600  # sleep for 10 minutes
        print("Time delay sleep only")
        time.sleep(sleep_duration) # sleep in seconds
    else:
        sleep_duration = 3600  # sleep for 60 minutes
        print("Sleep using deepsleep")
        machine.deepsleep(1000 * sleep_duration)   # deepsleep in ms


config.py

########################
# config.py
########################
#test
# PINs test
POWER_PIN = 'P9' # pin that powers enable pin on 5V Pololu regulator
DHT22_DATA = 'P10' # pin that receives data from internal temp/humidity sensor
DS18B20_DATA = 'P12' # pin that receives data from external temp sensor

VOLTMETER='P20' # pin connected to voltage divider
BATCOEFF = 2.0 # to calculate the real voltage

# led colors
OFF = 0x000000
WHITE=0xFFFFCC
RED = 0xff0000
BLUE = 0x0000ff
GREEN = 0x00ff00
YELLOW = 0x7f7f00
PURPLE = 0x7f007f
ORANGE = 0xFF9900

dth.py

import time
import pycom
from machine import enable_irq, disable_irq,  Pin
# test
# DHT22 sensor pins - when sensor lying down

#1 - VCC - red wire Connect to 3.3 - 5V power. Sometime 3.3V power isn't enough in which case try 5V power.
#2 - Data out - white or yellow wire
#3 - Not connected
#4 - Ground - black wire

# Note - 10 Kohm resistor between VCC and the data pin
# current draw 1.5mA - pycom advise up to 6mA per pin

class DTHResult:
    'DHT sensor result returned by DHT.read() method'

    ERR_NO_ERROR = 0
    ERR_MISSING_DATA = 1
    ERR_CRC = 2

    error_code = ERR_NO_ERROR
    temperature = -1
    humidity = -1

    def __init__(self, error_code, temperature, humidity):
        self.error_code = error_code
        self.temperature = temperature
        self.humidity = humidity

    def is_valid(self):
        return self.error_code == DTHResult.ERR_NO_ERROR

class DTH:
    'DHT sensor (dht11, dht21,dht22) reader class for Pycom'

    #__pin = Pin('P3', mode=Pin.OPEN_DRAIN)
    __dhttype = 0

    def __init__(self, pin, sensor=0):
        self.__pin = Pin(pin, mode=Pin.OPEN_DRAIN)
        self.__dhttype = sensor
        self.__pin(1)
        time.sleep(1.0)

    def read(self):
        # pull down to low
        self.__send_and_sleep(0, 0.019)
        data = pycom.pulses_get(self.__pin,100)
        self.__pin.init(Pin.OPEN_DRAIN)
        self.__pin(1)
        #print(data)
        bits = []
        for a,b in data:
        	if a ==1 and 18 <= b <= 28:
        		bits.append(0)
        	if a ==1 and 65 <= b <= 75:
        		bits.append(1)
        #print("longueur bits : %d " % len(bits))
        if len(bits) != 40:
            return DTHResult(DTHResult.ERR_MISSING_DATA, 0, 0)
        #print(bits)
        # we have the bits, calculate bytes
        the_bytes = self.__bits_to_bytes(bits)
        # calculate checksum and check
        checksum = self.__calculate_checksum(the_bytes)
        if the_bytes[4] != checksum:
            return DTHResult(DTHResult.ERR_CRC, 0, 0)
        # ok, we have valid data, return it
        [int_rh, dec_rh, int_t, dec_t, csum] = the_bytes
        if self.__dhttype==0:		#dht11
            rh = int_rh 		#dht11 20% ~ 90%
            t = int_t 	#dht11 0..50°C
        else:			#dht21,dht22
            rh = ((int_rh * 256) + dec_rh)/10
            t = (((int_t & 0x7F) * 256) + dec_t)/10
            if (int_t & 0x80) > 0:
                t *= -1
        return DTHResult(DTHResult.ERR_NO_ERROR, t, rh)

    def __send_and_sleep(self, output, mysleep):
        self.__pin(output)
        time.sleep(mysleep)

    def __bits_to_bytes(self, bits):
        the_bytes = []
        byte = 0

        for i in range(0, len(bits)):
            byte = byte << 1
            if (bits[i]):
                byte = byte | 1
            else:
                byte = byte | 0
            if ((i + 1) % 8 == 0):
                the_bytes.append(byte)
                byte = 0
        #print(the_bytes)
        return the_bytes

    def __calculate_checksum(self, the_bytes):
        return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255


lora.py

import config
import utime
import time
from network import LoRa
import socket
import binascii
import struct
import machine
# test

class LoraAU915: #Australia AU915
    # https://stackoverflow.com/questions/5690888/variable-scopes-in-python-classes
    lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915)

    def setupLora():
        #lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915)
        # Initialise LoRa in LORAWAN mode.

        # create an OTAA authentication parameters, change them to the provided credentials
        # tech-school-temp-1
        app_eui = binascii.unhexlify('APP-EUI-PLACE_HERE-FROM-TTN')
        app_key = binascii.unhexlify('APP-KEY-PLACE-HERE-FROM-TTN')

        #Limit channels for AU915
        for i in range(0,8):
            LoraAU915.lora.remove_channel(i)
        for i in range(16,65):
            LoraAU915.lora.remove_channel(i)
        for i in range(66,72):
            LoraAU915.lora.remove_channel(i)

        # join a network using OTAA (Over the Air Activation)
        #uncomment below to use LoRaWAN application provided dev_eui
        #https://docs.pycom.io/tutorials/networks/lora/nvram/
        new_connection = True
        LoraAU915.lora.nvram_restore()

        if(LoraAU915.lora.has_joined() == False):
            print("LoRa memory restored but need to join LoRa")
            LoraAU915.lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
        else:
            print("Memory restored and have rejoined LoRa with previous Dev Add")
            new_connection = False

        # wait until the module has joined the network
        while not LoraAU915.lora.has_joined():
            utime.sleep(1)
            if utime.time() > 15:
                print("possible timeout")
                machine.reset()
            pass

        print('Joined')
        # create a LoRa socket
        s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

        if new_connection:
            # set the LoRaWAN data rate
            s.setsockopt(socket.SOL_LORA, socket.SO_DR, 0)  # dr0 equiv to SF12

        # make the socket non-blocking
        s.setblocking(False)

        print("LoRa connection set up")
        utime.sleep_ms(5)
        return s

    def saveLora():
        LoraAU915.lora.nvram_save()
        print("LoRa setting saved to memory")

onewire.py

"""
OneWire library for MicroPython
"""
#test
import time
import machine

class OneWire:
    CMD_SEARCHROM = const(0xf0)
    CMD_READROM = const(0x33)
    CMD_MATCHROM = const(0x55)
    CMD_SKIPROM = const(0xcc)

    def __init__(self, pin):
        self.pin = pin
        self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)

    def reset(self):
        """
        Perform the onewire reset function.
        Returns True if a device asserted a presence pulse, False otherwise.
        """
        sleep_us = time.sleep_us
        disable_irq = machine.disable_irq
        enable_irq = machine.enable_irq
        pin = self.pin

        pin(0)
        sleep_us(480)
        i = disable_irq()
        pin(1)
        sleep_us(60)
        status = not pin()
        enable_irq(i)
        sleep_us(420)
        return status

    def read_bit(self):
        sleep_us = time.sleep_us
        enable_irq = machine.enable_irq
        pin = self.pin

        pin(1) # half of the devices don't match CRC without this line
        i = machine.disable_irq()
        pin(0)
        sleep_us(1)
        pin(1)
        sleep_us(1)
        value = pin()
        enable_irq(i)
        sleep_us(40)
        return value

    def read_byte(self):
        value = 0
        for i in range(8):
            value |= self.read_bit() << i
        return value

    def read_bytes(self, count):
        buf = bytearray(count)
        for i in range(count):
            buf[i] = self.read_byte()
        return buf

    def write_bit(self, value):
        sleep_us = time.sleep_us
        pin = self.pin

        i = machine.disable_irq()
        pin(0)
        sleep_us(1)
        pin(value)
        sleep_us(60)
        pin(1)
        sleep_us(1)
        machine.enable_irq(i)

    def write_byte(self, value):
        for i in range(8):
            self.write_bit(value & 1)
            value >>= 1

    def write_bytes(self, buf):
        for b in buf:
            self.write_byte(b)

    def select_rom(self, rom):
        """
        Select a specific device to talk to. Pass in rom as a bytearray (8 bytes).
        """
        self.reset()
        self.write_byte(CMD_MATCHROM)
        self.write_bytes(rom)

    def crc8(self, data):
        """
        Compute CRC
        """
        crc = 0
        for i in range(len(data)):
            byte = data[i]
            for b in range(8):
                fb_bit = (crc ^ byte) & 0x01
                if fb_bit == 0x01:
                    crc = crc ^ 0x18
                crc = (crc >> 1) & 0x7f
                if fb_bit == 0x01:
                    crc = crc | 0x80
                byte = byte >> 1
        return crc

    def scan(self):
        """
        Return a list of ROMs for all attached devices.
        Each ROM is returned as a bytes object of 8 bytes.
        """
        devices = []
        diff = 65
        rom = False
        for i in range(0xff):
            rom, diff = self._search_rom(rom, diff)
            if rom:
                devices += [rom]
            if diff == 0:
                break
        return devices

    def _search_rom(self, l_rom, diff):
        if not self.reset():
            return None, 0
        self.write_byte(CMD_SEARCHROM)
        if not l_rom:
            l_rom = bytearray(8)
        rom = bytearray(8)
        next_diff = 0
        i = 64
        for byte in range(8):
            r_b = 0
            for bit in range(8):
                b = self.read_bit()
                if self.read_bit():
                    if b: # there are no devices or there is an error on the bus
                        return None, 0
                else:
                    if not b: # collision, two devices with different bit meaning
                        if diff > i or ((l_rom[byte] & (1 << bit)) and diff != i):
                            b = 1
                            next_diff = i
                self.write_bit(b)
                if b:
                    r_b |= 1 << bit
                i -= 1
            rom[byte] = r_b
        return rom, next_diff

class DS18X20(object):
    def __init__(self, onewire):
        self.ow = onewire
        self.roms = [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
        self.fp = True
        try:
            1/1
        except TypeError:
            self.fp = False # floatingpoint not supported

    def isbusy(self):
        """
        Checks wether one of the DS18x20 devices on the bus is busy
        performing a temperature convertion
        """
        return not self.ow.read_bit()

    def start_conversion(self, rom=None):
        """
        Start the temp conversion on one DS18x20 device.
        Pass the 8-byte bytes object with the ROM of the specific device you want to read.
        If only one DS18x20 device is attached to the bus you may omit the rom parameter.
        """
        if (rom==None) and (len(self.roms)>0):
            rom=self.roms[0]
        if rom!=None:
            rom = rom or self.roms[0]
            ow = self.ow
            ow.reset()
            ow.select_rom(rom)
            ow.write_byte(0x44)  # Convert Temp

    def read_temp_async(self, rom=None):
        """
        Read the temperature of one DS18x20 device if the convertion is complete,
        otherwise return None.
        """
        if self.isbusy():
            return None
        if (rom==None) and (len(self.roms)>0):
            rom=self.roms[0]
        if rom==None:
            return None
        else:
            ow = self.ow
            ow.reset()
            ow.select_rom(rom)
            ow.write_byte(0xbe)  # Read scratch
            data = ow.read_bytes(9)
            return self.convert_temp(rom[0], data)

    def convert_temp(self, rom0, data):
        """
        Convert the raw temperature data into degrees celsius and return as a fixed point with 2 decimal places.
        """
        temp_lsb = data[0]
        temp_msb = data[1]
        if rom0 == 0x10:
            if temp_msb != 0:
                # convert negative number
                temp_read = temp_lsb >> 1 | 0x80  # truncate bit 0 by shifting, fill high bit with 1.
                temp_read = -((~temp_read + 1) & 0xff) # now convert from two's complement
            else:
                temp_read = temp_lsb >> 1  # truncate bit 0 by shifting
            count_remain = data[6]
            count_per_c = data[7]
            if self.fp:
                return temp_read - 25 + (count_per_c - count_remain) / count_per_c
            else:
                return 100 * temp_read - 25 + (count_per_c - count_remain) // count_per_c
        elif rom0 == 0x28:
            temp = None
            if self.fp:
                temp = (temp_msb << 8 | temp_lsb) / 16
            else:
                temp = (temp_msb << 8 | temp_lsb) * 100 // 16
            if (temp_msb & 0xf8) == 0xf8: # for negative temperature
                temp -= 0x1000
            return temp
        else:
            assert False


Node-RED

Node-RED flows do the following:



simpletime node

Simpletime node added to the flow to make it easier to add time and date to the msg.payload


simpletime library

The simpletime library needs to be added via the Manage Palette function.

mqtt in node

mqtt-broker node

function node to prepare data for dweet.io

http request node to send data to dweet.io

function node to prepare data for saving to file

file node to write data to local file

Node-RED Javascript code

  • Note that the Javascript code does not include the username and password for MQTT.
  • These details are kept separate in a secret file (not published).
[
    {
        "id": "20f2893.06e7f76",
        "type": "tab",
        "label": "techschool-temp-sensor",
        "disabled": false,
        "info": ""
    },
    {
        "id": "f679fc1d.0c851",
        "type": "mqtt in",
        "z": "20f2893.06e7f76",
        "name": "techschool-temp-sensor",
        "topic": "#",
        "qos": "0",
        "datatype": "json",
        "broker": "79bf454.bdc72bc",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 150,
        "y": 80,
        "wires": [
            [
                "af01c0ba.7bcb7"
            ]
        ]
    },
    {
        "id": "af01c0ba.7bcb7",
        "type": "simpletime",
        "z": "20f2893.06e7f76",
        "name": "",
        "mydate": true,
        "myymd": true,
        "myyear": true,
        "mymonth": true,
        "mymonthn": true,
        "mydom": true,
        "mydoy": true,
        "myday": true,
        "myhourpm": true,
        "myhour": true,
        "mytime": true,
        "mytimes": true,
        "myminute": true,
        "myminutes": true,
        "mysecond": true,
        "mymillis": true,
        "myepoch": true,
        "myrawdate": true,
        "mypm": true,
        "x": 350,
        "y": 80,
        "wires": [
            [
                "7c29bf68.43599",
                "b628ce5b.daf9f"
            ]
        ]
    },
    {
        "id": "7c29bf68.43599",
        "type": "function",
        "z": "20f2893.06e7f76",
        "name": "dweet function",
        "func": "var battery; // battery voltage data\nvar humidity; // humidity data from within the sensor housing\nvar soil_temp; // soil temperature data\nvar temperature; // temperature data from within the sensor housing\n\nbattery = msg.payload.uplink_message.decoded_payload.bat;\nhumidity = msg.payload.uplink_message.decoded_payload.humid;\nsoil_temp = msg.payload.uplink_message.decoded_payload.soil;\ntemperature = msg.payload.uplink_message.decoded_payload.temp;\n\nmsg.payload = \"bat=\" + String(battery) + \"&\"\n            + \"humid=\" + String(humidity) + \"&\"\n            + \"soil_temp=\" + String(soil_temp) + \"&\"\n            + \"temp=\" + String(temperature) + \"&\"\n            + \"time=\" + String(msg.mytimes);\n\nmsg.url = \"https://dweet.io/dweet/for/techschool-temp-sensor?\" + msg.payload;\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 200,
        "wires": [
            [
                "5a31e781.5594a8"
            ]
        ]
    },
    {
        "id": "5a31e781.5594a8",
        "type": "http request",
        "z": "20f2893.06e7f76",
        "name": "http request dweet",
        "method": "POST",
        "ret": "obj",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "authType": "",
        "x": 450,
        "y": 200,
        "wires": [
            [
                "6cae2481.d4030c"
            ]
        ]
    },
    {
        "id": "6cae2481.d4030c",
        "type": "debug",
        "z": "20f2893.06e7f76",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 650,
        "y": 200,
        "wires": []
    },
    {
        "id": "b628ce5b.daf9f",
        "type": "function",
        "z": "20f2893.06e7f76",
        "name": "Save data function",
        "func": "var battery; // battery voltage data\nvar soil_temp; // soil temperature data\nvar temperature; // temperature data from within the sensor housing\nvar humidity; // humidity data from within the sensor housing\n\nbattery = msg.payload.uplink_message.decoded_payload.bat;\nsoil_temp = msg.payload.uplink_message.decoded_payload.soil;\ntemperature = msg.payload.uplink_message.decoded_payload.temp;\nhumidity = msg.payload.uplink_message.decoded_payload.humid;\n\n\nmsg.payload = String(msg.myepoch) + \",\"\n            + String(msg.mydate) + \",\"\n            + String(msg.mytimes) + \",\"\n            + String(battery) + \",\"\n            + String(soil_temp) + \",\"\n            + String(temperature) + \",\"\n            + String(humidity);\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 300,
        "wires": [
            [
                "b69162ad.6d7d2"
            ]
        ]
    },
    {
        "id": "b69162ad.6d7d2",
        "type": "file",
        "z": "20f2893.06e7f76",
        "name": "techschool-temp-sensor-data",
        "filename": "/home/pi/sensors/techschool-temp-sensor-data.txt",
        "appendNewline": true,
        "createDir": false,
        "overwriteFile": "false",
        "encoding": "none",
        "x": 480,
        "y": 300,
        "wires": [
            [
                "efb34c3e.05648"
            ]
        ]
    },
    {
        "id": "efb34c3e.05648",
        "type": "debug",
        "z": "20f2893.06e7f76",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 710,
        "y": 300,
        "wires": []
    },
    {
        "id": "79bf454.bdc72bc",
        "type": "mqtt-broker",
        "name": "techschool-temp-sensor",
        "broker": "au1.cloud.thethings.network",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "sessionExpiry": ""
    }
]