An approach to dealing with spurious sensor data in Indigo

Spurious sensor data can wreak havoc in an otherwise finely-tuned home automation system. I use temperature data from an Aeotech Multisensor 6 to monitor the environment in our greenhouse. Living in Canada, I cannot rely solely on passive systems to maintain the temperature, particularly at night. So, using the temperature and humidity measurements transmitted back to the controller over Z-wave, I control devices inside the greenhouse that heat and humidify the environment.

But spurious temperature and humidity data mean that I often falsely trigger the heating and humidification devices. After dealing with this for several weeks, I came up with a workable solution that can be applied to other sensor data. It’s important to note that the solution I developed uses time-averaging of the data. If it’s important to react to the data quickly, then the averaging window needs to be shortened or you may need to look for a different solution.

I started by trying to ascertain exactly what the spurious temperature data were. It turns out that usually the spurious data points were 0’s. But occasionally odd non-zero data would crop up. In all cases the values were lower than the actual value and always by a lot (i.e. 40 or more degrees F difference.)

In most cases with Indigo, for simplicity, we simply trigger events based on absolute values. When spurious data are present, for whatever reason, false triggers will result. My approach takes advantage of the fact that Indigo keeps a database of sensor data. By default it logs these data points to a SQLite database. This database is at /Library/Application Support/Perceptive Automation/Indigo 7/Logs/indigo_history.sqlite. I used the application Base a GUI SQLite client on macOS to explore the structure a bit. Each device has a table named device_history_xxxxxxxx. You simply need to know the device identifier which you can easily find in the Indigo application. Exploring the table, you can see how the data are stored.

To employ a strategy of time-averaging and filtering the data, I decided to pull the last 10 values from the SQLite database. As I get data about every 30 seconds from the sensor, my averaging window is about 5 minutes. It turns out this is quite easy:

import sqlite3

SQLITE_PATH = '/Library/Application Support/Perceptive Automation/ \
Indigo 7/Logs/indigo_history.sqlite'
SQLITE_TN = 'device_history_114161618'
SQLITE_TN_ALIAS = 'gh'

conn = sqlite3.connect(SQLITE_PATH)
c = conn.cursor()
SQL = "SELECT gh.sensorvalue from {tn} as {alias} \
ORDER BY ts DESC LIMIT 10".format(tn=SQLITE_TN,alias=SQLITE_TN_ALIAS)
c.execute(SQL)
all_rows = c.fetchall()

Now all_rows contains a list of single-item tuples that we need to compact into a list. In the next step, I filter obviously spurious values and compact the list of tuples into a list of values:

tempsF = filter(lambda a: a > 1, [i[0] for i in all_rows])

But some spurious data remains. Remember that many of the errant values are 0.0 but some are just lower than the actual values. To do this, I create a list of the differences from one value to the next and search for significant deviations (5°F in this case.) Having found which value creates the large difference, I exclude it from the list.^[As I was preparing this post, I realized that it this approaches misses the possibility of a dataset having more than one spurious data point. Empirically, I did not notice any occurrence of that, but it’s possible. I have to account for that in the future.]

diffs = [abs(x[1]-x[0]) for x in zip(tempsF[1:],tempsF[:-1])]
idx = 0
for diff in diffs:
	if diff > 5:
		break;
	else:
		idx = idx+1
filtTempsF = tempsF[:idx+1] + tempsF[idx+2:]
{% endcodeblock %}

Finally, since it's a _moving average_ I need to actually average the data.

{% codeblock lang:python %}
avgTempsF = reduce(lambda x,y : x + y, filtTempsF) / len(filtTempsF)

In summary, this gives me a filtered, time-averaged dataset that excludes spurious data. For applications that are very time-sensitive, this approach won’t work as is. But for most environmental controls, it’s a workable solution to identifying and filtering wonky sensor data.

For reference, the entire script follows:

#	Update the greenhouse temperature in degrees C
#	The sensor reports values in F, so we will update
#	the value to see whenever the primary data has any change.

import sqlite3

# device and variable definitions
IDX_CURRENT_TEMP = 1822850463
IDX_FORMATTED = 1778207310
DEV_GH_TEMP = 114161618
SQLITE_PATH = '/Library/Application Support/Perceptive Automation/Indigo 7/Logs/indigo_history.sqlite'
SQLITE_TN = 'device_history_114161618'
SQLITE_TN_ALIAS = 'gh'

DEBUG_GH = True

def F2C(ctemp):
	return round((ctemp - 32) / 1.8,1)

def CDeviceTemp(deviceID):
	device = indigo.devices[deviceID]
	tempF = device.sensorValue
	return F2C(tempF)

def movingAverageF():
	conn = sqlite3.connect(SQLITE_PATH)
	c = conn.cursor()
	SQL = "SELECT gh.sensorvalue from {tn} as {alias} ORDER BY ts DESC LIMIT 10".format(tn=SQLITE_TN,alias=SQLITE_TN_ALIAS)
	c.execute(SQL)
	all_rows = c.fetchall()
	tempsF = filter(lambda a: a > 1, [i[0] for i in all_rows])
	diffs = [abs(x[1]-x[0]) for x in zip(tempsF[1:],tempsF[:-1])]
	idx = 0
	for diff in diffs:
		if diff > 5:
			break;
		else:
			idx = idx+1
	filtTempsF = tempsF[:idx+1] + tempsF[idx+2:]
	avgTempsF = reduce(lambda x,y : x + y, filtTempsF) / len(filtTempsF)
	return avgTempsF

def movingAverageC():
	return F2C(movingAverageF())

# 	compute moving average
avgC = F2C(movingAverageF())

# current greenhouse temperature in degrees C
ghTempC = F2C(indigo.devices[DEV_GH_TEMP].sensorValue)
indigo.server.log("GH temp: raw={0}F, filtered moving avg={1}C".format(ghTempC,avgC))

#	update the server variables (°C temp and formatted string)
indigo.variable.updateValue(IDX_CURRENT_TEMP,value=unicode(avgC))
indigo.variable.updateValue(IDX_FORMATTED, value="{0}°C".format(avgC))

Follow the intent.

With Trump the usual advice of “Follow the money.” doesn’t work because Congress refuses to force him to disclose his conflicts of interest. As enormous and material as those conflicts must be, I’m just going to focus on what I can see with my own eyes, the man’s apparent intent.

In his public life, Donald Trump has never done anything that did not personally and directly benefit him. Most of us, as we go through life, assemble a collection of acts that are variously self-serving and other-serving. This is the way of life. Normal life. With Trump, not so. Even his meager philanthropic acts are tainted with controversy. The man simply cannot act in sacrificial way. He is incurable.^[In a campaign event in Fort Dodge, Iowa on November 12, 2015, Trump claimed that rival Ben Carson was “pathological” and that “…if you’re pathological, there’s no cure for that, folks, okay? There’s no cure for that.” Since Trump’s own psychopathology is widely questioned, one wonders if he, too, is incurable. Given that narcissistic personality disorder is almost certainly among the potential diagnoses, he probably is incurable.]

They're just paid protesters

In an effort to strip protesters of their legitimacy, Trump and Fox News claim that protesters are simply there because they’re paid by powerful oppositional interests. Never mind that Trump has no evidence for his claim; he has no evidence for practically anything that emerges from his loud mouth. What is more interesting to me is that if money delegitimizes authenticity then presumably we can use this effect to come to additional conclusions.

@realDonaldTrump Russian Twitter bot

Someday, when I have time to burn, I’m going to write a Twitter bot that takes all of Trump’s vacuous tweets and translate them into Russian. It’ll look like this:

There’s something ludicrous about the idea of the Trump, who is distractible, impatient, and incurious being able to learn Russian, an incredibly difficult language.

marking time


marking time,
eyes glazed, pupils constricted
to the head of a pin
from facing the blue white sterile light
for too long
a zombie tribe
numbering in the millions
if not more
waits.

this throng, agitated
in a subdued anesthetized
way,
crowns one of its own
a clown of sorts
knowing little of the past
less of the present
and practically nothing
of the future.
“why not? it could be worse."

in a strange unreality
a vaudeville show becomes
its own rehearsal,
a dreamish state from which
only an atomic flash
can awaken a person.

13 Random thoughts about Canada after living here for a year.

On January 1, 2016 we packed up all our earthly goods and headed south to Canada. (Yes, it’s true. When you live in Minnesota, it’s possible to move south to Canada. Look at the map!) Having lived here for a little over a year, here are some thoughts about living here, in no particular order:

  1. “Sorry” is more of a greeting than just an apology.
  2. Canadians really are polite; but put them behind the wheel of a car and all bets are off.
  3. Universal healthcare works. Americans love to go on and on about socialized medicine; but I’m here to tell you: it works.
  4. Bumper stickers are rare here.
  5. People don’t really talk politics. Well, they talk about U.S. politics.
  6. Left turn arrows on traffic lights are rare. It makes for interesting moments when the light changes.
  7. The electric utility is called “hydro”, which given the Greek origin of the word makes little sense until you realize that it stands for “hydroelectric.”
  8. Youth music is well-supported - both through private and public funding.
  9. State-church separation is fuzzier. For example, the Catholic school system is tax-payer funded. But only the Catholic schools. It has something to do with the Canadian Charter (a.k.a Constitution.) It was apparently some sort of historical compromise in the 1800’s.
  10. Don’t order iced tea in Canada. It’s way too sweet.
  11. As a practical matter, you can’t be elected Prime Minister unless you speak both English and French fluently. This is a really good thing.^[How many languages does Donald Trump speak fluently, for example?]
  12. Speaking of politics, campaigns are time-limited to 6 weeks before an election. How cool is that?
  13. Poutine sounds horrible, but it’s actually pretty good.

Serious audio processing on the command line

I’ve written previously about extracting and processing mp3 files from web pages. The use case that I described, obtaining Russian word pronunciations for Anki cards is basically the same although I’m now obtaining many of my words from Forvo. However, Forvo doesn’t seem to apply any audio dynamic range processing or normalization to the audio files. While many of the pronunciation mp3’s are excellent as-is, some need post-processing chiefly because the amplitude is too low. However, being lazy by nature, I set out to find a way of improving the audio quality automatically before I insert the mp3 file into my new vocabulary cards.

Foreign language education in the U.S.: a neglected cure for xenophobia

Xenophobia has deep roots in the U.S. For all of its “global melting pot” rhetoric, the reality has been much more complex. It begins with the maltreatment of the native peoples of America with its attendant extinction of their language and culture. But even as recently as 1923, the U.S. Supreme Court ruled that persons from India were not eligible for U.S. citizenship because only “free white persons” were permitted. This case was one of the most egregious in the history of the United States and is a poignant example of how the Supreme Court reflects, rather than transcends, prevailing cultural values. In the United States v. Bhagat Singh Thind, 261 U.S. 204, the Supreme Court unanimously decided that an Indian Sikh man could not be granted citizenship because he was not white. Even today, the suspicion with which many English-speaking Americans view Spanish speakers undoubtedly contributes to their xenophobic calls to build a wall along the border with Mexico.

Trump and the arts

I’ve long suspected that Trump regards the arts as an unnecessary nuisance for losers and suckers. High art for this hollow man is the vacuous reality television that made him famous. Now in his Federal budget, Trump offers proof. He proposes the elimination of the National Endowment for the Arts and the National Endowment for the Humanities.

This is a man with no soul.

But in the closing lines of her lyric poem “Renascence”, Edna St. Vincent Millay has a warning: