<?xml version="1.0" encoding="UTF-8"?>        <rss version="2.0"
             xmlns:atom="http://www.w3.org/2005/Atom"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
             xmlns:admin="http://webns.net/mvcb/"
             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
             xmlns:content="http://purl.org/rss/1.0/modules/content/">
        <channel>
            <title>
									Liudr&#039;s Blog Forum - Recent Topics				            </title>
            <link>https://liudresllc.com/community/</link>
            <description>Liudr&#039;s Blog Discussion Board</description>
            <language>en-US</language>
            <lastBuildDate>Sat, 04 Apr 2026 04:46:44 +0000</lastBuildDate>
            <generator>wpForo</generator>
            <ttl>60</ttl>
							                    <item>
                        <title>Metergroup Hydros21 issues</title>
                        <link>https://liudresllc.com/community/support/metergroup-hydros21-issues/</link>
                        <pubDate>Mon, 09 Mar 2026 19:55:42 +0000</pubDate>
                        <description><![CDATA[The Metergroup Hydros21 isn&#039;t one of the listed officially supported sensors, but with some help of colleagues we managed to set one up and get it to report readings (Onion Omega2LTE as the ...]]></description>
                        <content:encoded><![CDATA[<p>The Metergroup Hydros21 isn't one of the listed officially supported sensors, but with some help of colleagues we managed to set one up and get it to report readings (Onion Omega2LTE as the controller).<br /><br />Most of the time it works fine, plug in the sensor, power up the board and within a minute or so our routine is running and posting results to the web (we set it up to post on thingspeak).</p>
<p>Once in a while though, something gets into a funk, and the setup stops working. Power off, power on, still the same. Without touching any of the connections at all, left it powered off for multiple days, and then on powering up again, it just starts working again. What would cause this?</p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>donaldsfei</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/support/metergroup-hydros21-issues/</guid>
                    </item>
				                    <item>
                        <title>Beginner Questions</title>
                        <link>https://liudresllc.com/community/gen/beginner-questions/</link>
                        <pubDate>Sat, 22 Nov 2025 18:40:02 +0000</pubDate>
                        <description><![CDATA[Hello LiuDR community,
I’m relatively new to the world of Arduino and microcontrollers and trying to understand how to setup several sdi12 sensors that measure stream depth (pressure transd...]]></description>
                        <content:encoded><![CDATA[<p>Hello LiuDR community,</p>
<p>I’m relatively new to the world of Arduino and microcontrollers and trying to understand how to setup several sdi12 sensors that measure stream depth (pressure transducers) and one modbus meter in a pipe. The sensor data must be reported to the web using cellular data. The system must be solar powered.in case anyone is wondering this will be used in California to monitor water diverted from a river for agriculture. This is a requirement by the state water board for surface water diversions over a size (I forget the threshold)<br /><br /></p>
<p>That’s the end goal, but I think it’s more realistic to start with a simplified version that includes a single sdi12 pressure transducers. There is a lot of information on this website, but I haven’t seen an overview of how a full setup would be assemble.</p>
<p>questions:</p>
<p>is the LiuDR SDI-12 USB adapter appropriate for this application?</p>
<p>where is the best place to start researching something like this? Is there a beginners guide in the blog somewhere?</p>
<p>Do you have a description of a similar system that I can read about?</p>
<p>Thank you!</p>
<p>in case anyone is interested in my role, I work for a nonprofit that works with agricultural water users to apply management improvements that improve ecological function without hurting production. I use a lot of pressure transducers, and soil moisture sensors. Pulling data from the sensors takes time and sometimes results in error and lost data when loggers are not redeployed properly. I’d like to move toward a fully telemetered system as old sensors need replacement.</p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>DRevel</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/gen/beginner-questions/</guid>
                    </item>
				                    <item>
                        <title>Mistakenly changed SDI-12 address to &quot;z&quot;</title>
                        <link>https://liudresllc.com/community/support/mistakenly-changed-sdi-12-address-to-z/</link>
                        <pubDate>Thu, 14 Aug 2025 16:37:14 +0000</pubDate>
                        <description><![CDATA[Hello. I mistakenly set an SDI-12 sensor (Sentek DD MTS) address to lowercase z, which is the address of the analog channel on the USB adapter. Now I can&#039;t communicate with the sensor to cha...]]></description>
                        <content:encoded><![CDATA[<p>Hello. I mistakenly set an SDI-12 sensor (Sentek DD MTS) address to lowercase z, which is the address of the analog channel on the USB adapter. Now I can't communicate with the sensor to change it back because subsequent commands to address "z" go to the analog channels instead. Is there a way to revert this? Thank you.</p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>vv-23</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/support/mistakenly-changed-sdi-12-address-to-z/</guid>
                    </item>
				                    <item>
                        <title>SDI12 Adaptator</title>
                        <link>https://liudresllc.com/community/support/sdi12-adaptator/</link>
                        <pubDate>Wed, 04 Jun 2025 05:33:44 +0000</pubDate>
                        <description><![CDATA[Hello,
I&#039;m interested in your SDI-12 adapter to connect multiple environmental sensors (turbidity, conductivity, oxygen).My goal is to build an autonomous, battery-powered system (using eit...]]></description>
                        <content:encoded><![CDATA[<p data-start="219" data-end="225">Hello,</p>
<p data-start="227" data-end="488">I'm interested in your SDI-12 adapter to connect multiple environmental sensors (turbidity, conductivity, oxygen).<br data-start="341" data-end="344" />My goal is to build an autonomous, battery-powered system (using either LiSOCl₂ or Li-Po batteries) with at least one month of autonomy.</p>
<p data-start="490" data-end="915">I’m not sure which kind of "computer" or microcontroller would be best suited for your system, since my only requirement is to store data locally on an SD card.<br data-start="654" data-end="657" />Do you have any recommendations for a compact and low-power platform?<br data-start="730" data-end="733" />I’m considering options like the Raspberry Pi Zero or a STM32-based board, but low power consumption and small size are essential since the probe will be deployed at sea.</p>
<p data-start="917" data-end="1055">Also, my sensors draw brief current peaks of 500 mA for 2 ms at 12 V.<br data-start="990" data-end="993" />Do you think your SDI-12 adapter can handle this type of load?</p>
<p data-start="1057" data-end="1118">Thank you in advance for your help — and for your great work.</p>
<p data-start="1120" data-end="1145">Best regards,<br data-start="1133" data-end="1136" />Julia</p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>Julia</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/support/sdi12-adaptator/</guid>
                    </item>
				                    <item>
                        <title>Connecting with Apogee SIL-411</title>
                        <link>https://liudresllc.com/community/support/connecting-with-apogee-sil-411/</link>
                        <pubDate>Thu, 19 Sep 2024 18:05:18 +0000</pubDate>
                        <description><![CDATA[I&#039;m posting this question here so everyone can see how it gets resolved
Hey there!I’ve got a quick question for you hoping for some guidance after purchasing your raspberry pi sdi-12 usb ad...]]></description>
                        <content:encoded><![CDATA[<p>I'm posting this question here so everyone can see how it gets resolved</p>
<p><em>Hey there!<br />I’ve got a quick question for you hoping for some guidance after purchasing your raspberry pi sdi-12 usb adapter a while back.<br /><br />I can’t seem to get the pi to recognize the sensor I’m trying to use and was wondering if you could confirm its compatibility. It may be that this sensor sends data in an unrecognized format or something. The Pi does recognize the usb device.<br /><br />My setup: RPi 5, your sdi adapter, connected with a 9V external power source to power this sensor: <a href="https://www.apogeeinstruments.com/sil-411-commercial-grade-sdi-12-digital-output-standard-field-of-view-infrared-radiometer-sensor/" target="_blank" rel="noopener" data-saferedirecturl="https://www.google.com/url?q=https://www.apogeeinstruments.com/sil-411-commercial-grade-sdi-12-digital-output-standard-field-of-view-infrared-radiometer-sensor/&amp;source=gmail&amp;ust=1726850847450000&amp;usg=AOvVaw3YOblP-d3orK87aiJWfR_0">https://www.apogeeinstruments.com/sil-411-commercial-grade-sdi-12-digital-output-standard-field-of-view-infrared-radiometer-sensor/</a><br /><br />The specs are available on that page. Thanks for your time, it’s greatly appreciated!</em></p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>liudr</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/support/connecting-with-apogee-sil-411/</guid>
                    </item>
				                    <item>
                        <title>TERROS 12 connection with Raspberry pi 4</title>
                        <link>https://liudresllc.com/community/support/terros-12-connection-with-raspberry-pi-4/</link>
                        <pubDate>Tue, 09 Apr 2024 15:57:46 +0000</pubDate>
                        <description><![CDATA[Hello,
 
I would like to connect 6 Terros 12 sensors with a Raspberry pi 4 and want to know which product is most suitable for this, and what extra parts I may need.
Also, do you have an ...]]></description>
                        <content:encoded><![CDATA[<p>Hello,</p>
<p> </p>
<p>I would like to connect 6 Terros 12 sensors with a Raspberry pi 4 and want to know which product is most suitable for this, and what extra parts I may need.</p>
<p>Also, do you have an estimate of how long the shipment may take?</p>
<p> </p>
<p>Thank you for the help and support!</p>
<p> </p>
<p>Alexis</p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>shakasaki</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/support/terros-12-connection-with-raspberry-pi-4/</guid>
                    </item>
				                    <item>
                        <title>FloraPulse stem water potential sensors</title>
                        <link>https://liudresllc.com/community/supported-sensors/florapulse-stem-water-potential-sensors/</link>
                        <pubDate>Sat, 02 Mar 2024 20:43:38 +0000</pubDate>
                        <description><![CDATA[I often get questions of &quot;Will SDI-12 sensor X work with your SDI-12 adapter?&quot;
In general, all SDI-12 sensors should work with my adapters. But there are always some variations in the imple...]]></description>
                        <content:encoded><![CDATA[<p>I often get questions of "Will SDI-12 sensor X work with your SDI-12 adapter?"</p>
<p>In general, all SDI-12 sensors should work with my adapters. But there are always some variations in the implementations of the SDI-12 standard by different vendors. Here is one brand of sensors that was recently confirmed working with my adapter. Thanks Michael!</p>
<p>According to the website below, FloraPulse sensors are "the most accurate irrigation guidance for orchards and vineyards".</p>
<p><a href="https://www.florapulse.com/" target="_blank" rel="noopener">https://www.florapulse.com/</a></p>
<p>The only slight modification that Michael found was that a delay of 1 second is needed before sending each command to the adapter. You can delay with time.sleep(1)</p>
<p>Here is a snippet courtesy of Dr. Michael Santiago:</p>
<pre contenteditable="false">#!/usr/local/opt/python-3.5.1/bin/python3.5
# Simple SDI-12 Sensor Reader Copyright Dr. John Liu
import serial.tools.list_ports
import serial
import time
import re

ser=serial.Serial(port='COM10',baudrate=9600,timeout=10)
time.sleep(2.5) # delay for arduino bootloader and the 1 second delay of the adapter.

ser.write(b'?!')
sdi_12_line=ser.readline()
sdi_12_line=sdi_12_line # remove \r and \n since $ has trouble with \r
m=re.search(b'$',sdi_12_line) # having trouble with the \r
sdi_12_address=m.group(0) # find address
print('\nSensor address:', sdi_12_address.decode('utf-8'))

time.sleep(1)
ser.write(sdi_12_address+b'I!')
sdi_12_line=ser.readline()
sdi_12_line=sdi_12_line # remove \r and \n
print('Sensor info:',sdi_12_line.decode('utf-8'))

time.sleep(1)
ser.write(sdi_12_address+b'M!')
sdi_12_line=ser.readline()
sdi_12_line=ser.readline()

time.sleep(1)
ser.write(sdi_12_address+b'D0!')
sdi_12_line=ser.readline()
sdi_12_line=sdi_12_line # remove \r and \n
print('Sensor reading:',sdi_12_line.decode('utf-8'))
print('\nFor complete data logging solution, download the free Python data logger under "data logger programs"\nhttps://liudr.wordpress.com/gadget/sdi-12-usb-adapter/')
ser.close()</pre>
<p>If you are planning to build your own data logger with free time-series database such as influxdb, this will be a good start to read data.</p>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>liudr</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/supported-sensors/florapulse-stem-water-potential-sensors/</guid>
                    </item>
				                    <item>
                        <title>Logging from Atmos14 and Terros12 + Uploading to multiple Thingspeak Channels</title>
                        <link>https://liudresllc.com/community/sharing/logging-from-atmos14-and-terros12-uploading-to-multiple-thingspeak-channels/</link>
                        <pubDate>Sat, 30 Dec 2023 11:21:17 +0000</pubDate>
                        <description><![CDATA[This revision to Dr. Liu&#039;s 1.5.7 version of the SDI-12 script sets out to accomplish two goals:

Log data from the Meter Group devices Terros12 and Atmos14; accommodating the Atmos14 need ...]]></description>
                        <content:encoded><![CDATA[<p>This revision to Dr. Liu's 1.5.7 version of the SDI-12 script sets out to accomplish two goals:</p>
<ol>
<li>Log data from the Meter Group devices Terros12 and Atmos14; accommodating the Atmos14 need for a delay between querying the device and requesting a reading.</li>
<li>Modifying the script so the Thingspeak channel is defined by the device in the user configuration to allow devices to send to independent channels. </li>
</ol>
<p>Updating the config file to point to multiple Thingspeak API destinations:</p>
<pre contenteditable="false">edit the api_key field of the config file in notepad

from  "api_key" : "some_api_key" to
 "api_key": {"sensor_address_1": api_key_for_sensor1", "sensor_address_2": api_key_for_sensor2"  }

seprate sensors with comma
e.g: 
 "api_key": {"1" : "GTOEBKK8ZQHI1V1B" , "2" : "XXXXXXXXXXXXXXXX" }</pre>
<p> </p>
<p>Example Config aka Liudrlogger.conf file when pointing to multiple channels:</p>
<pre contenteditable="false">{"wifi_ssid": "datalogger", "wifi_passwd": "logger1234", "my_timezone": "CET-1CEST", "channelID": "359964", "api_key":{"1":"GTOEBKK8ZQHI1V1B" , "2" : "SECONDAPIKEYHERE" }, "total_data_count": 1, "delay_between_pts": 30, "sdi_12_address": "12", "sdi_12_command": , "analog_inputs": "N", "time_zone_choice": 1, "ser": , "ser_ID": []}</pre>
<p> </p>
<p>And finally the revised 1.5.7 logger script:</p>
<pre contenteditable="false">import datetime  # For finding system's real time
import json  # For serializing and deserializing parameters on configuration file.
import re  # For regular expression support
import serial.tools.list_ports  # For listing available serial ports
import serial  # For serial communication
import signal  # For trapping ctrl-c or SIGINT
import sys  # For exiting program with exit code
import time  # For delaying in seconds
import urllib.parse  # For encoding data to be url safe.
import urllib.request  # send data to online server
# import os # For running command line commands
# import platform # For detecting operating system flavor
"""
SDI-12 Sensor Data Logger Copyright Dr. John Liu
2022-11-25 Revision by Grow_Deluxe to allow the script to log data from the Atmos 14/Terros 12 by Meter Group. In addition this revision writes to a .csv file per device, and uploads each device to a unique thingspeak channel api (see readme.txt for more details on this)
2021-10-25 Updated to up to 8 fields for thingspeak and updated API address
2018-07-03 Improved exception handling for the SDI-12 protocol. No response from the sensor will not trigger exception.
    Rather, the loop waits for the next iteration and try again. Time to read line 274
    Added opening port by ID feature in parameters and saving port ID feature in interactive session.
2018-04-24 Added exception handling for opening a non-existing serial port (possibly the config file has wrong port name).
    Added exception handling for http.client.BadStatusLine from urllib calls
2018-04-21 Tested recently added features such as D0, D1, and M, M1.
2018-04-19 Updated the script to issue multiple commands such as M and M1.
    Added features to collect all data using D0, D1, etc. until it collects all measurements
    No longer asks for analog sensors. Just type in address z with the other sensors and issue
    commands 0 or 1 to collect single-ended or differential analog channels from SDI-12 + Analog adapter.
    The script saves a configuration file Liudrlogger.conf. You can modify it with a text editor.
    It's very easy to understand. With the config file, the logger starts in auto-logging mode.
    If you delete the file, it starts interactive session to gather the parameters from the user.
2018-04-07 Replaced capitalize() with upper. Added urllib.error.URLError to exception handling.
    Commented out os and platform imports and unit_id.
    Decoded byte strings before converting into float to stay compatible with MicroPython.
2018-03-29 Added exception handling for urllib.request.urlopen for server internal error
2018-03-28 Replaced cURL with urllib.request for sending data to thingspeak.com server
2017-11-06 Updated telemetry code to upload to thingspeak.com from data.sparkfun.com.
2017-06-23 Added exception handling in case the SDI-12 + GPS USB adapter doesn't return any data (no GPS lock).
    Added serial port and file closing in ctrl + C handler.
2017-02-02 Added multiple-sensor support. Just type in multiple sensor addresses when asked for addresses.
    Changed sdi_12_address into regular string from byte string.
    I found out that byte strings when iterated over becomes integers.
    It's easy to cast each single character string into byte string with .encode() when needed as address.
    Removed specific analog input code and added the adapter address to the address string instead.
2016-11-12 Added support for analog inputs
2016-07-01 Added .strip() to remove \r from input files typed in windows
    Added Ctrl-C handler
    Added sort of serial port placing FTDI at item 0 if it exists
"""
rev_date = '2021-10-25'
version = '1.5.7'

config_file_name = 'Liudrlogger.conf'
# Default values of operating parameters
paras_default = dict([
    ('wifi_ssid', 'datalogger'),
    ('wifi_passwd', 'logger1234'),
    ('my_timezone', 'CET-1CEST'),
    ('channelID', '359964'),
    ('api_key', 'GTOEBKK8ZQHI1V1B'),
    ('total_data_count', 1000),
    ('delay_between_pts', 60),
    ('sdi_12_address', 'z'),
    ('sdi_12_command', ),
    ('analog_inputs', 'N'),
    ('time_zone_choice', 0),
    ('ser', []),
    ('ser_ID',[])
])


def SIGINT_handler(signal, frame):
    ser.close()
    data_file.close()
    print('Quitting program!')
    sys.exit(0)


signal.signal(signal.SIGINT, SIGINT_handler)
ser = []  # This list stores opened serial port
fnf = False  # File not found error

# This one for everyone to perform tests with
# channelID = "359964"
# api_key = "GTOEBKK8ZQHI1V1B"

# Use computer name as unit_id. For a raspberry pi, change its name from raspberrypi to something else to avoid confusion
# unit_id=platform.node()

http_request_url_format = 'https://api.thingspeak.com/update.json?api_key=%s%s'
max_upload_values = 4  # Maximal values to upload as a single data point
adapter_sdi_12_address = 'z'
# This is the flag to break out of the inner loops and continue the next data point loop in case no data is received from a sensor such as the GPS.
no_data = False
ports = []
VID_FTDI = 0x0403;


def load_paras():
# Load operating parameters from default dictionary.    
# The function returns a dictionary, which has keys for all parameters needed in the program.
    try:
        f = open(config_file_name, 'r')
        par = json.load(f)
        f.close()
        return (par, False)  # Return paras and False for file not found
    except FileNotFoundError as e:
        print('No configuration file found. Starting interactive session...')
        return (paras_default, True)  # Return default paras and True for file not found


def print_credit(pa):
    print('+-' * 40)
    print('SDI-12 Sensor and Analog Sensor Python Data Logger with Telemetry V', version)
    print(
        'Designed for Dr. Liu\'s family of SDI-12 USB adapters (standard,analog,GPS)\n\tDr. John Liu Saint Cloud MN USA',
        rev_date, '\n\t\tFree software GNU GPL V3.0')
    print('\nCompatible with PCs running Win 7/10, GNU/Linux, Mac OSX, Raspberry PI, Beagle Bone Black')
    print('\nThis program requires Python 3.4, Pyserial 3.0, and internet connector (data upload)')
    print('\nUsing config file:%s' %(config_file_name))
    print('\nData is logged to YYYYMMDD.CVS in the Python code\'s folder')
    print('\nVisit https://thingspeak.com/channels/%s to inspect or retrieve data' % (pa))
    print('\nFor assistance with customization, telemetry etc., contact Dr. Liu.')
    print('\nhttps://liudr.wordpress.com/gadget/sdi-12-usb-adapter/')
    print('+-' * 40)


def interactive_session(pa):
    # List ports for user to select
    a = serial.tools.list_ports.comports()
    for w in a:
        ports.append((w.vid, w.device, w.serial_number))

    ports.sort(key=lambda ports: ports)

    print('\nDetected the following serial ports:')
    i = 0
    for w in ports:
        print('%d)\t%s\t(USB VID=%04X)\tID#:=%s' % (i, w, w if (type(w) is int) else 0,w))
        i = i + 1
    total_ports = i  # now i= total ports

    user_port_selection = input('\nSelect port from list (0,1,2...). SDI-12 adapter has USB VID=0403:')
    if (int(user_port_selection) &gt;= total_ports):
        exit(1)  # port selection out of range

    # Open serial port
    pa.append((ports))
    ser.append(serial.Serial(port=(ports), baudrate=9600, timeout=10))
    time.sleep(2.5)  # delay for arduino bootloader and the 1 second delay of the adapter.

    pa = int(input('Total number of data points:'))
    pa = int(input('Delay between data points (second):'))
    # Enter addresses and commands for each sensor
    pa = ''
    user_sdi_12_address = input(
        'Enter all SDI-12 sensor addresses as a list, such as 123z.\nIf you have analog channels on your adapter, include address z to your list:')
    pa = user_sdi_12_address.strip()  # Remove any \r from an input file typed in windows
    pa = []  # Clear the list of commands
    print('\n\nSensor measurement commands:\nBasic sensors respond to M0 (same as M) command.')
    print(
        'More sophiscated sensors respond to more commands, such as M0 for moisture, and M1 for temperature for an HSTI soil probe.')
    print('\nTo send the basic command M0, enter 0. To send more commands such as M0, M1 and M4, enter 014.')
    for addr in pa:  # Get measurement commands
        print('\nFor SDI-12 sensor %c, which command(s) should be sent?' % (addr))
        pa.append(input('Enter command:'))

    # pa=input('Collect analog inputs (requires SDI12-USB + Analog adapter)? (Y/N)')
    # pa=(pa.strip()).upper() # Remove any \r from an input file typed in windows and capitalize answer
    print('Time stamps are generated with:\n0) GMT/UTC\n1) Local\n')
    pa = int(input('Select time zone.'))
    f = open(config_file_name, 'w')  # Save settings
    json.dump(paras, f)
    f.close()


# if len(sdi_12_address)==0:
#    sdi_12_address=adapter_sdi_12_address # Use default address


def sensor_info(pa):
    for an_address in pa:
        ser.write(an_address.encode() + b'I!')
        sdi_12_line = ser.readline()
        print('Sensor address:', an_address, ' Sensor info:', sdi_12_line.decode('utf-8').strip())


(paras, fnf) = load_paras()
print_credit(paras)

if (fnf):  # No configuration file. Start interactive session. Serial port is open in the interactive session.
    interactive_session(paras)
else:  # Open serial port
    print('\nUsing saved configuration...\n')
    try:
        ser.append(serial.Serial(port=paras, baudrate=9600, timeout=10))
        time.sleep(2.5)  # Wait for arduino bootloader
    except serial.serialutil.SerialException as err:
        print(err.__str__())
        print('Invalid serial port in config file!')
        sys.exit(1)

sensor_info(paras)

# Open data file for logging
if paras == 0:
    now = datetime.datetime.utcnow()  # use UTC time instead of local time
elif paras == 1:
    now = datetime.datetime.now()  # use local time, not recommended for multiple data loggers in different time zones

ser_ptr = 0

while True:
    
    if paras == 0:
        now = datetime.datetime.utcnow()
    elif paras == 1:
        now = datetime.datetime.now()
      # formatting date and time
    for (cmd_ptr, an_address) in enumerate(paras):
        i = 0  # This counts to max_upload_values to limit data sent to the server.
        value_str = ''  # This stores &amp;value0=xxx&amp;value1=xxx&amp;value2=xxx&amp;value3=xxx&amp;value4=xxx&amp;value5=xxx and is only reset after all sensors are read.

        output_str = "%04d/%02d/%02d %02d:%02d:%02d%s" % (now.year, now.month, now.day, now.hour, now.minute, now.second,' GMT' if paras == 0 else '')
        
        # get device info &amp; create file name with device info
        ser.write(an_address.encode() + b'I!')
        sdi_12_line = ser.readline()
        
        file_name= an_address+ (sdi_12_line.decode('utf-8').strip()).replace(" ","") + ".csv";
        
        # print("filename : " , repr(file_name))   
        
        
        data_file_name = file_name
        data_file = open(data_file_name, 'a')  # open yyyymmdd.csv for appending
        print('Saving to %s' % data_file_name)
        values = []  # clear before each sensor
        sdi_12_line_buffer = b''  # This stores all data from the same sensor address, including from M!-&gt;D0! D1!, M1!-&gt;D0!, D1! etc.
        for a_command in paras:
            try:
                if a_command == '0':
                    complete_command = an_address.encode() + b'M!'
                else:
                    complete_command = an_address.encode() + b'M' + a_command.encode() + b'!'
                ser.write(complete_command);  # start the SDI-12 sensor measurement
                # print(complete_command);  # start the SDI-12 sensor measurement
                sdi_12_line = ser.readline()
                # print(sdi_12_line)
                if sdi_12_line==b'': # Didn't get a response from the sensor? Faulty wiring?
                    print('Sensor %s failed to respond to command %s.' %(an_address,complete_command))
                    no_data = True # End the current iteration of sensors and commands on each sensor and wait for the next iteration.
                    break;
                # print("aM! response ", repr(sdi_12_line))   
                sdi_12_line = sdi_12_line  # remove \r and \n since $ has trouble with \r
                m = re.search(b'$', sdi_12_line)  # This should match a number () that appears at the end of the response ($), which is a 1-digit number of "returned values" but it is having trouble with the \r so I removed \r\n in the previous line of code.
                if (not m):  # Match evaluates into True. The response is wrong. There should be a number at the end of the response, save \r\n, but it is not in this response.
                    no_data = True # End the current iteration of sensors and commands on each sensor and wait for the next iteration.
                    break;
                total_returned_values = int(m.group(0))  # find out how many values are returned
                warm_up_time = int((sdi_12_line.decode())); #extract ttt from atttn, removing address(a) and no. of return values(n) 
                # print(total_returned_values)

                if warm_up_time &gt;0 :                    
                    # print("warmup time for sensor %s: " % (an_address) , sdi_12_line.decode() )
                    time.sleep(warm_up_time); # wait for warm-up time
                    sdi_12_line = ser.readline()  # read the service request line
                    
                    # print("&lt;cr&gt;&lt;lf&gt; : " ,repr(sdi_12_line))
                    if sdi_12_line != an_address.encode() + b'\r\n':
                        print('Sensor %s didn\'t respond with correct service request.' % (an_address))
                        no_data = True  # End the current iteration of sensors and commands on each sensor and wait for the next iteration.
                        break;
                    # Read as much data as you can with D0, D1, ... D9 until only the address and \r\n is returned
                for d_command in range(10):
                    complete_command = an_address.encode() + b'D' + str(d_command).encode() + b'!'
                    ser.write(complete_command)  # request data
                    # print(complete_command)  # request data
                    next_sdi_12_line = ser.readline()  # read the data line
                    # print(sdi_12_line)
                    if (len(next_sdi_12_line) &lt;= 3): # only 1\r\n is returned, indicating that the sensor has run out of values to return. It's time to stop asking.
                        break
                    else:
                        next_sdi_12_line = next_sdi_12_line  # remove address, \r and \n since $ has trouble with \r, stitch all responses from D0! to D9! together for later processing.
                        sdi_12_line_buffer += next_sdi_12_line  # Append results from the Dn! command to data from D0! to Dn-1!
            except serial.serialutil.SerialException as err:
                print(err.__str__())
                ser.close()
                sys.exit(1)
            # print(sdi_12_line_buffer) # Received data from one address one M command
            for iterator in range(total_returned_values):  # extract the returned values from SDI-12 sensor and append to values[]
                m = re.search(b'+', sdi_12_line_buffer)  # search a number string with preceding + or - sign and any number of digits and decimal (+).
                try:  # if values found is less than values indicated by return from M, report no data found. This is a simple solution to GPS sensors before they acquire lock. For sensors that have lots of values to return, you need to find a better solution.
                    values.append(float(m.group(0).decode()))  # convert into a number. Decode byte string into string first due to MicroPython.
                    sdi_12_line_buffer = sdi_12_line_buffer
                except AttributeError:
                    print("No data received from sensor at address %c\n" % (an_address))
                    time.sleep(paras)
                    no_data = True
                    break
            if (no_data == True):
                break;                  
        
        
        output_str = output_str + ',' + an_address

        for value_i in values:
            output_str = output_str + ",%s" % (value_i)  # Output returned values
            if (i &lt; max_upload_values):
                value_str = value_str + "&amp;field%d=%s" % (i + 1, value_i)  # format values for posting. Field starts with field1, not field0.
                i = i + 1
                
        print(output_str)
        output_str = output_str + '\n'
        data_file.write(output_str)
               
        
        if (no_data == True):
            no_data = False
            time.sleep(paras)
            continue;
        while (i &lt; max_upload_values):  # Pad with zeros in case we don't have max_upload_values fields. This is only necessary for certain servers.
            value_str = value_str + "&amp;field%d=0" % (i + 1)  # format values for posting. Field starts with field1, not field0.
            i = i + 1
        
        http_request_url = http_request_url_format % (paras, value_str)  # Format url command
        print(http_request_url)  # Debug information
        try:
            req = urllib.request.urlopen(http_request_url)
        except: # Intermittent internet connection could cause more underlying modules to through exceptions. Just catch any exception and discard.
            print("Unexpected error:", sys.exc_info())
        #except (urllib.error.HTTPError, urllib.error.URLError, http.client.BadStatusLine) as err:
            #print('Error uploading data.')
            #print(err.__str__())  # Sometimes the server returns with 500 Internal error and this error is raised and needs to be caught otherwise it breaks the script.
            # You can decide whether to send the request one or a few more times or just discard the error and move on.
        else:
            print('Sent data')       
            
        values = []  # clear values for the next iteration, 3.2.3 doesn't support clear as 3.4.3 and 3.5.1 does
        data_file.flush()  # make sure data is written to the disk so stopping the scrit with ctrl - C will not cause data loss
    
    #print(req.status)  # Send data to server and print out response. 200 means OK.

    
    time.sleep(paras)
ser.close()

</pre>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>Grow_Deluxe</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/sharing/logging-from-atmos14-and-terros12-uploading-to-multiple-thingspeak-channels/</guid>
                    </item>
				                    <item>
                        <title>Soldering practice kit</title>
                        <link>https://liudresllc.com/community/announce/soldering-practice-kit/</link>
                        <pubDate>Sun, 11 Jun 2023 00:55:47 +0000</pubDate>
                        <description><![CDATA[Since many users of my adapters are learning IoT, arduino, raspberry pi, etc. I thought that I could help them learn some basic electronics skills such as soldering. Plus, I have many printe...]]></description>
                        <content:encoded><![CDATA[<p>Since many users of my adapters are learning IoT, arduino, raspberry pi, etc. I thought that I could help them learn some basic electronics skills such as soldering. Plus, I have many printed circuit boards that I no longer use. You only need one such board and some male header pins to learn how to solder anyway and you don't really have random boards just lying around for practice. So here it is, a $2.49 investment in soldering practice:</p>
<p>1. One printed circuit board with many 0.1" spacing holes</p>
<p>1. One row of 40-oin male break-away headers</p>
<p>1. One small cut sheet of blu-tack putty to hold parts to be soldered (I used a blob in the video but I'll supply fresh ones cut from a sheet)</p>
<p>Here is a play list of how to solder on a printed circuit board:</p>
<p><a href="https://www.youtube.com/playlist?list=PLwsP5zYBAWdmDGuesC7Rmr-RW3JxSFtAs" target="_blank" rel="noopener">https://www.youtube.com/playlist?list=PLwsP5zYBAWdmDGuesC7Rmr-RW3JxSFtAs</a></p>
<p> </p>
<p>The kit is now for sale on my square store:</p>
<p><a href="https://liudr.square.site/product/soldering-practice-kit/74?cs=true&amp;cst=custom" target="_blank" rel="noopener">https://liudr.square.site/product/soldering-practice-kit/74?cs=true&amp;cst=custom</a></p>
<div id="wpfa-3585" class="wpforo-attached-file"><a class="wpforo-default-attachment" href="//liudresllc.com/wp-content/uploads/wpforo/default_attachments/1686444947-Soldering-practice-kit.png" target="_blank" title="Soldering-practice-kit.png"><i class="fas fa-paperclip"></i>&nbsp;Soldering-practice-kit.png</a></div>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>liudr</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/announce/soldering-practice-kit/</guid>
                    </item>
				                    <item>
                        <title>SDI-12 USB adapter now features a microUSB port</title>
                        <link>https://liudresllc.com/community/announce/sdi-12-usb-adapter-now-features-a-microusb-port/</link>
                        <pubDate>Thu, 11 May 2023 04:04:41 +0000</pubDate>
                        <description><![CDATA[I am happy to announce that the new SDI-12 USB adapters come with microUSB ports. This change will not affect the SDI-12 USB + Analog adapters which still come with miniUSB ports for now, un...]]></description>
                        <content:encoded><![CDATA[<p>I am happy to announce that the new SDI-12 USB adapters come with microUSB ports. This change will not affect the SDI-12 USB + Analog adapters which still come with miniUSB ports for now, until I run out of these connectors later this year.</p>
<p> </p>
<div id="wpfa-3538" class="wpforo-attached-file"><a class="wpforo-default-attachment" href="//liudresllc.com/wp-content/uploads/wpforo/default_attachments/1683777881-SDI-12-USB-adapter-2023-on-graphing-paper.jpg" target="_blank" title="SDI-12-USB-adapter-2023-on-graphing-paper.jpg"><i class="fas fa-paperclip"></i>&nbsp;SDI-12-USB-adapter-2023-on-graphing-paper.jpg</a></div>]]></content:encoded>
						                            <category domain="https://liudresllc.com/community/"></category>                        <dc:creator>liudr</dc:creator>
                        <guid isPermaLink="true">https://liudresllc.com/community/announce/sdi-12-usb-adapter-now-features-a-microusb-port/</guid>
                    </item>
							        </channel>
        </rss>
		