oh poison ivy!
oh poison ivy!
let me explain,
how much I’m in pain
you grow so well,
but you create a hell
your oil is attracted to my skin,
and then works to dissolve me again
oh poison ivy!
oh poison ivy!
let me explain,
you are making me insane!
oh poison ivy!
oh poison ivy!
let me explain,
how much I’m in pain
you grow so well,
but you create a hell
your oil is attracted to my skin,
and then works to dissolve me again
oh poison ivy!
oh poison ivy!
let me explain,
you are making me insane!
When I started searching for how to transfer data between 2 computers using MQQT, I saw articles talking about Node-RED, but I dismissed it as being some sort of toy for children. That was probably a mistake because once I installed it, I realized it was both genius and a powerful tool. It lets you do amazing, complicated things without having to get into the weeds in every little detail.
In the screenshot below, the NR session from my Raspberry Pi is on top. The first block monitors a file to see if it changed. When it sees a change, it fires the next 2 blocks, which read the data I am looking for from a file. That data is passed on to the 3rd set of blocks (in purple) which publish the data via an MQQT broker running on the same computer. (mosquitto) The green block is just a debug block and is not needed.
The NR session on the bottom is running on my desktop. A single block (MQQT in) connects to the broker running on the Raspberry Pi and reads to values.
Here is python code running on the Raspberry Pi that retrieves the values and stores them in a pseudo-file. (the /var directory in a *nix machine is actually a location in RAM, not on the HDD or SD card)
#!/usr/bin/env python3
########################################################################
# Filename : myProg2.py
# Description : write pi ADC values to file
# Author : bg
# modification: 20210504
########################################################################
from time import sleep
import myADC
mydata = [0,0,0,0,0,0,0,0] #create python list
def loop():
while(True):
for i in range(8):
mydata[i] = myADC.getValue(i) #bring ADC values into list
filename = '/var/adc/'+str(i) #create the filename
ramdisk = open(filename,'w') #open file in writeover mode
ramdisk.write(str(mydata[i])) #write data to file
ramdisk.close()
sleep(5)
if __name__ == '__main__':
print ('Program is starting ... ')
# print(myADC.getValue(0))
# print(myADC.getValue(1))
try:
loop()
except KeyboardInterrupt:
#destroy()
pass
A Raspberry Pi 3B is configured to bring in sensor data through a ADC (analog to digital converter) connected to the pi’s I2C (Inter-Integrated Circuit) serial interface. (I’m using the ADS7830 module that came with a kit that contained all kinds of fun project goodies). I have 2 potentiometers wired to the ADC that I can turn by hand to give me 2 real voltage values. The pi has internet access through wifi.
I followed the kit‘s instructions for the python language to
I wrote a function so I can get the ADC values from other programs:
#!/usr/bin/env python3
########################################################################
# Filename : myADC.py
# Description : Use ADC module to read the voltage values
# Author : sparkygeek.com
# modification: 2020/04/26
########################################################################
from ADCDevice import *
adc = ADS7830()
def getValue(chan = 0):
value = adc.analogRead(chan) # read the ADC value of channel 0
voltage = value / 255.0 * 3.3 # calculate the voltage value
print ('Channel %d ADC Value : %d, Voltage : %.2f'%(chan,value,voltage))
return voltage
Follow the directions for getting the Google Python Quickstart project working. (Enable Google Cloud Platform Project API, Create credentials, download your credential file, and install the needed Google client libraries) The quickstart code uses the spreadsheets.values.get method, but we are going to use the spreadsheets.values.append method to add new values to our spreadsheet. Here is the code I’m using to do this:
from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import myADC
from time import sleep
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
# The ID and range of a sample spreadsheet.
SPREADSHEET_ID = 'xxxxxxxxxxRb6BXNBORw72feWN3iiaz_xxxxxx'
RANGE_NAME = 'Sheet1'
def main():
"""Shows basic usage of the Sheets API.
Prints values from a sample spreadsheet.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('sheets', 'v4', credentials=creds)
# Call the Sheets API
sheet = service.spreadsheets()
newData = [myADC.getValue(0),myADC.getValue(1)] # get data from ADC module
body = {
"values": [
newData
]
}
result = sheet.values().append(
spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME,
valueInputOption='USER_ENTERED', body=body).execute()
print('{0} cells appended.'.format(result \
.get('updates') \
.get('updatedCells')))
if __name__ == '__main__':
while(True):
main()
sleep(2.5)
If you want a data-entry pop-up in Inductive Automation’s Ignition, you just call the function to open an input box. This seems simple to computer programmers, but it is different than how typical industrial automation systems accomplish the task. Ignition marries the two worlds, which is one of the things that makes it awesome.
My political opinions lean more and more to Anarchy… The most improper job of any man, even saints, is bossing other men. Not one in a million is fit for it, and least of all those who seek the opportunity. -JRR Tolkien
I wanted to learn how API’s work, so I figured bringing in some of my weather station data to an Inductive Automation’s Ignition project would be a good task to learn on. I wasted a lot of time trying to use the python helper functions that were recommended in the docs at https://ambientweather.docs.apiary.io/. The problem with that route was that Ignition uses it’s own instance of python (actually jython)and it’s an older version. Note: if you do need to import libraries, I finally learned you can use the pip install command which takes care of all the dependencies for you if you follow the directions at https://forum.inductiveautomation.com/t/procedure-for-installing-python-libraries-from-source/26022/2. I almost had it working, but ran into a problem with a codec, which I didn’t understand and didn’t want to figure out.
I wound up using Ignition’s system.net.httpClient() function to make the call to the API. Once I figured out how to format the API call, the hard part was figuring out how to pick through the JSON document it returned and update tag values within Ignition with those values. I couldn’t get any of the built-in functions that are supposed to help with this to work correctly. The documentation is pretty sparse on those functions and it seemed to get hung up on the fact the date values contained colons. My workaround was to convert it to a big string value, then use python’s string commands to find the parts I wanted.
Below is the script I use to do the work. The script runs every 5 seconds. One of the software developers at Ignition had a much more elegant script started, but the get_data() method had a problem and I just went with a simpler solution.
#get data from ambient weather API
client = system.net.httpClient()
response = client.get("https://api.ambientweather.net/v1/devices/00:0E:00:00:00:00", params={"apiKey":"xxx","applicationKey":"yyy","limit":"1" })
#comes in as a list
weatherdata = response.json
#convert to a string to deal with problem of time having colons
weatherdataString = ' '.join(map(str, weatherdata))
#change searchterm for each element you want
searchterm = "tempf"
offset = len(searchterm)+3
startpoint = weatherdataString.find(searchterm)
endpoint = weatherdataString.find("'",startpoint+offset)
system.tag.write("[default]wxstation/temperature",(weatherdataString[startpoint+offset:endpoint-2]))
#change searchterm for each element you want
searchterm = "yearlyrainin"
offset = len(searchterm)+3
startpoint = weatherdataString.find(searchterm)
endpoint = weatherdataString.find("'",startpoint+offset)
system.tag.write("[default]wxstation/yearlyrain",(weatherdataString[startpoint+offset:endpoint-2]))
#change searchterm for each element you want
searchterm = "dailyrainin"
offset = len(searchterm)+3
startpoint = weatherdataString.find(searchterm)
endpoint = weatherdataString.find("'",startpoint+offset)
system.tag.write("[default]wxstation/dailyrain",(weatherdataString[startpoint+offset:endpoint-2]))
I built a ladder out of 2×4 and 1×4 lumber. It has 9 rungs spaced 12-3/4″ apart and is set to be used at a 70ยบ angle and have 9′-1″ of elevation.
If your modern high-efficiency* dishwasher is anything like mine, then it is a source of constant frustration. Even with pre-washing the dishes don’t come out very clean. I think I solved the problem, here is what we do:
The extra steps are necessary to wash away all the soap. There are 2 things you are fighting**: 1) the soaps no longer have the ingredient in them that allow for easy rinsing, and 2) a typical cycle only uses 3 gallons of water, instead of the traditional 15 gallons.
*water usage efficiency, not time efficiency
**both of the changes are due to US law