[OBSOLETE] Raspberry Pi Temperature Sensor

I needed a simple temperature sensor and had an old Raspberry Pi laying around. Of course I wanted to have Smartthings integration as well. Adding a temperature sensor to a Pi is an old project for the Pi (posted at Adafruit’s website), and using REST endpoints, a SmartApp and a simulated temperature measurement device handler, the Pi can keep Smartthings updated. I thought someone else might be interested - it illustrates some basic Smartthings stuff for those trying to learn.

Pi Temperature Sensor with Smartthings Integration

Instructions for building a DS18B20 temperature sensor in a Raspberry Pi can be found here: [Raspberry Pi Lesson 11 DS18B20 Temperature Sensing]

I would not say this is a particularly cost effective way to get a temperature sensor if you’re only goal was to save money. However, if you happen to have a Pi laying around doing nothing (as I did), or had a Pi doing something and you wanted it to pull double duty monitoring temperature, or maybe if you can get one of those $5 Pi Zeros, then this should be cost effective for you. The delta in cost to add temperature sensing to an existing Pi is small - a couple of dollars.

The things you need:

A Raspberry Pi (of any sort - I used a Model B) with Raspbian installed
A DS18B20. I used the waterproof one, but the transistor-y looking one would work. As noted - do not use any of the TMP36 devices - those look the same but are analog devices.
A pull up resistor (4.7k. or 10k.)
A case (optional, but practical)
Some jumper wires, a little solder, and a little electrical tape

The waterproof version contains 3 leads. If you read the Adafruit lesson, you might think the lead colors are standard. They are not. And because these things aren’t very expensive (I paid US$6 for 5 of them), don’t expect much in the way of documentation. On mine, the blue wire is the data line, the red wire is the power line (VCC), and the black wire is ground. I suggest you google the colors you have. There also seem to be quite a few complaints of DOA ones, so if yours doesn’t work and you’re pretty sure you followed the instructions properly, then try another one.

What we are trying to build looks like this to those of you who read schematic:

What I did was get a bunch of jumper wires (40 for $5), and, cut 3 of them in half, and stripped the cut end. Then I twisted together the wires (or wrapped the wire around the resistor lead), and dropped a little solder on it to hold it in place (you might want to test that you’re ds18b20 works before dropping the solder).

Then I taped the resistor and wires to the top of case to keep them electrically isolated. Also, I wrapped a little electrical tape around the wire sheath - the part that will stay inside the case - as a cord strain relief.

The completed Pi with Temperature Sensor:

BTW I understand that you can connect more than one of these ds18b20’s in parallel which is kind of cool. It is suggested that you do one first, then label it with serial number of the device, so that you can tell them apart. I didn’t try this - I only needed, essentially, a single digital thermometer.

Now boot up the Pi. First, we are going to make sure that the Pi is properly setup and the DS18B20 is working.

The first thing we need to do is edit /etc/config.txt. It may be a little confusing, but we are not intending to use i2c for this device, but we need to enable 1-wire support by adding the line:

dtoverlay=w1-gpio
Reboot the Pi. When its back up and you’re logged in again, load the drivers in a terminal window as shown below. When you are in the ‘devices’ directory, the directory starting ‘28-’ will have a different name (the xxxx is the serial number of the device, so cd to the name of whatever directory is there. If you don’t see it, then something’s wrong.

sudo modprobe w1-gpio
sudo modprobe w1-therm
cd /sys/bus/w1/devices
ls
cd 28-xxxx (change this to match what serial number pops up)
cat w1_slave
You should see something like this:

09 01 4b 46 7f ff 0c 10 9c : crc=9c YES
09 01 4b 46 7f ff 0c 10 9c t=16562
There is some unreliability, but the YES at the end of the first line tells you that the temperature is valid. The temperature is the t=16562 at the end of the second line, in other words, 16.562 °C. At this point you know you have the sensor working properly.

Now we want to integrate it with Smartthings. We do this by creating a Smartapp with a REST endpoint that the Pi can call into. Then we create a simulated temperature sensor device that the Smartapp can update.

When we create the Smarttapp, we need to enable OAuth. The Smartapp itself is pretty simple, but obtaining the authorization code that the Pi uses to call into Smartthings is a bit cumbersome (at least at the time of writing).

Create the Smartapp from this code, remembering to enable OAuth:

/**
 *  Pi Temperature Sensor Endpoints
 *
 *  Copyright 2016 Paul Cifarelli
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
definition(
    name: "Pi Temperature Sensor",
    namespace: "pcifarelli",
    author: "Paul Cifarelli",
    description: "REST endpoint for Pi to update a Simulated Temperature Sensor",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    oauth: [displayName: "Cifarelli.net", displayLink: ""])
 
preferences {
    section("Allow Endpoint to Control This Thing") {
        input "tdevice", "capability.temperatureMeasurement", title: "Which Simulated Temperature Sensor?", multiple: false
    }
}
  
mappings {
    path("/update/:temp/:units") {
        action: [
            PUT: "updateTemp"
        ]
    }
}
 
def installed() {
    log.debug "Installed with settings: ${settings}"
 
    initialize()
}
 
def updated() {
    log.debug "Updated with settings: ${settings}"
    
    unsubscribe()
    initialize()
}
 
def initialize() {
    // TODO: subscribe to attributes, devices, locations, etc.
}
 
// implement event handlers
void updateTemp() {
    update(tdevice)
}
 
private void update(device) {
     log.debug "update, request: params: ${params['temp']} ${params['units']} ${device.name}"
     def t = 0
     
     if (location.temperatureScale == params['units']) {
        log.debug "yes, the temperatureScale is the same (${params['units']})"
        t = Double.parseDouble(params['temp'])
     } else if (params['units'] == "F") {
        // so location is set for C
        t = 5 * ( Double.parseDouble(params['temp']) - 32 ) / 9
     } else if (params['units'] == "C") {
        // so location is set for F
        t = 9 * Double.parseDouble(params['temp']) / 5 + 32
     }
     def x = Math.round(t * 100) / 100
     tdevice.setTemperature(x)
}

The device handler you need to create depends on whether you want °F or °C. The Smartapp is able to use your location settings to determine the preferred temperature scale, but I couldn’t find a good way to do this in the device handler, since the value tiles are statically defined. But the device handler is really simple, so having 2 of them isn’t too big a deal:

For °F:

 /**
 *  Copyright 2016 Paul Cifarelli
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
metadata {
    definition (name: "Pi Simulated Temperature Sensor °F", namespace: "pcifarelli", author: "Paul Cifarelli") {
        capability "Temperature Measurement"
        capability "Sensor"
 
        command "setTemperature", ["number"]
    }
 
    // UI tile definition
    tiles(scale:2) {
        valueTile("temperature", "device.temperature", height: 4, width: 6, canChangeIcon: true) {
            state("temperature", label:'${currentValue}°', unit:"F",
                backgroundColors:[
                    [value: 31, color: "#153591"],
                    [value: 44, color: "#1e9cbb"],
                    [value: 59, color: "#90d2a7"],
                    [value: 74, color: "#44b621"],
                    [value: 84, color: "#f1d801"],
                    [value: 95, color: "#d04e00"],
                    [value: 96, color: "#bc2323"]
                ]
            )
        }
        main "temperature"
        details("temperature")
    }
}
 
// Parse incoming device messages to generate events
def parse(String description) {
    def pair = description.split(":")
    createEvent(name: pair[0].trim(), value: pair[1].trim())
}
 
def setTemperature(value) {
    sendEvent(name:"temperature", value: value)
}

And for °C:

 /**
 *  Copyright 2016 Paul Cifarelli
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
metadata {
    definition (name: "Pi Simulated Temperature Sensor °C", namespace: "pcifarelli", author: "Paul Cifarelli") {
        capability "Temperature Measurement"
        capability "Sensor"
 
        command "setTemperature", ["number"]
    }
 
    // UI tile definitions
    tiles(scale:2) {
        valueTile("temperature", "device.temperature", height: 4, width: 6, canChangeIcon: true) {
            state("temperature", label:'${currentValue}°', unit:"C",
                backgroundColors:[
                    [value: 0, color: "#153591"],
                    [value: 7, color: "#1e9cbb"],
                    [value: 15, color: "#90d2a7"],
                    [value: 23, color: "#44b621"],
                    [value: 29, color: "#f1d801"],
                    [value: 35, color: "#d04e00"],
                    [value: 36, color: "#bc2323"]
                ]
            )
        }
        main "temperature"
        details("temperature")
    }
}
 
// Parse incoming device messages to generate events
def parse(String description) {
    def pair = description.split(":")
    createEvent(name: pair[0].trim(), value: pair[1].trim())
}
 
def setTemperature(value) {
    sendEvent(name:"temperature", value: value)
}

Once one of these is created, you have to create a simulated device. Do this in the web developer pages, under “My Devices”, click “+ New Device”. Give it a name and a device id (which can be anything, as long as it’s unique). From the Type dropdown select the Pi Temperature Sensor device handler you just created (your custom device handlers are at the bottom of the list). Finally, select your location and hub and click “Create”.

Now you’ll need the client id and the client secret from the App Settings OAuth section. You use this to get an authorization token. Do not share these with anyone (which should go without saying, but…)

Smartthings uses OAuth2 - which is gives an auth token that will expire, the time it will expire, and a refresh token to use to refresh it when it does expire. Currently, Smartthings issues auth tokens that expire in 50 years, so we can ignore, for now, the fact that they expire.

I don’t know any way of getting an auth token without having a webpage in a browser, since you need to be redirected to a Smartthings page to select the hub and device on that hub to authorize. I do know someone set up a public website to do this, but I didn’t want to use it because I didn’t know how safe/secure it was (don’t share your client secret with anyone…). In our case, we want to give access to the Pi Simulated Temperature Sensor °F (or the Pi Simulated Temperature Sensor °C if you prefer). The page below redirects you to the Smartthings graph, allowing you to select the Hub and Device. Then it redirects back to your page with the access token.

One thing to point out (that I learned the hard way) is that your Smartapp must not have errors or throw exceptions when you are trying to authorize it. If it does, you will get a cryptic message saying that it cannot authorize you at this time, try later or contact support. The Smartapp doesn’t actually have to do anything though, just be free of errors.

Perhaps someone will know a better way to do this, but in the meantime you can use this PHP page I created (well, found/copied/modified) to return the auth token:

<?php
   //client id and client secret
   $client = '<put your client id from the App Settings here>';
   $secret = '<put your client secret from the App Settings here>';
 
   // the full url to redirect to this file
   $url = get_this_url();
 
   $f = array( 'code' => FILTER_SANITIZE_STRING, 'access_token' => FILTER_SANITIZE_STRING );
   $request = filter_input_array(INPUT_GET, $f);
 
   //STEP 1 - Get Access Code
   if(!isset($request['code']) && !isset($request['access_token']))
   {
     header( 
      "Location: https://graph.api.smartthings.com/oauth/authorize?response_type=code&client_id=$client&redirect_uri=".$url."&scope=app" 
      );
   }
   //STEP 2 - Use Access Code to claim Access Token
   else if(isset($request['code']))
   {
       $code = $request['code'];
      $base = "https://graph.api.smartthings.com/oauth/token";
      $page = 
       "$base?grant_type=authorization_code&client_id=".$client."&client_secret=".$secret."&redirect_uri=".$url."&code=".$code."&scope=app";
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL,            $page );
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
      curl_setopt($ch, CURLOPT_POST,           0 );
      curl_setopt($ch, CURLOPT_HTTPHEADER,     array('Content-Type: application/json')); 
      $response =  json_decode(curl_exec($ch),true);
      curl_close($ch);
      if(isset($response['access_token']))
      {
         //Redirect to self with access token for step 3 for ease of bookmarking
         header( "Location: ?access_token=".$response['access_token'] ) ;
      }
      else
      {
         print "error requesting access token...";
         print_r($response);
      }
   }
   //Step 3 - Lookup Endpoint and write out urls
   else if(isset($request['access_token']))
   {
      $url = "https://graph.api.smartthings.com/api/smartapps/endpoints/$client?access_token=".$request['access_token'];
      $json = implode('', file($url));
      $theEndpoints = json_decode($json,true);
      print "<html><head><style>h3{margin-left:10px;}a:hover{background-color:#c4c4c4;} a{border:1px solid black; padding:5px; margin:5px;text-decoration:none;color:black;border-radius:5px;background-color:#dcdcdc}</style></head><body>";
      print "<i>Save the above URL (access_token) for future reference.</i>";
      print " <i>Right Click on buttons to copy link address.</i>";
   }
 
   function safe_server_var( $var, $op = FILTER_SANITIZE_STRING )
   {
      if (filter_has_var(INPUT_SERVER, $var))
      {
         $ret_val = filter_input(INPUT_SERVER, $var, $op, FILTER_NULL_ON_FALURE);
      }
      else
      {
         $ret_val = "";
      }
      return $ret_val;
   }
 
   function get_server_home($server_port = 0)
   {
      $sname = safe_server_var("SERVER_NAME");
      $addr = safe_server_var("SERVER_ADDR");
      return ( make_url($sname != "" ? $sname : $addr, $server_port) );
   }
 
   function make_url($server_addr, $server_port = 0)
   {
      $proto = "http://";
      $sproto = safe_server_var("HTTPS", FILTER_UNSAFE_RAW);
      if ($sproto != "")
      {
         $proto = "https://";
      }
      $sport = safe_server_var("SERVER_PORT");
      $port = $server_port == 0 ? $sport : $server_port;
      if (($proto == "http://" && $port == 80) || ($proto == "https://" && $port == 443))
      {
         return ( $proto . $server_addr );
      }
      else
      {
         return ( $proto . $server_addr . ":" . $port);
      }
   }
 
   function get_this_url()
   {
      $page = safe_server_var("PHP_SELF");
      return get_server_home() . htmlspecialchars($page);
   }
 
?>

Now that you have the access token, we can go back to the Pi and setup the update script (which we will write in python, but feel free to do it however you want).

The script runs ‘niters’ times, updating Smartthings once at the start and then only if the temperature changes. It also checks the temperature every ‘interval’ seconds. The idea is to run this in a cron job - this way the temp sensor checks in at least every so often, and we know it’s still working. I have it setup to run for 15 minutes, checking the temperature every 15 seconds (niter=60, interval=15). This is run in a cron job scheduled to run every 15 minutes - this way we have continued monitoring of the temperature while also checking in to Smartthings at least every 15 minutes.

First though, make sure that you have python3-requests installed:

sudo apt-get install python3-requests

I also upgraded to curl4, but it’s probably not necessary:

sudo apt-get install libcurl4-openssl-dev

Here’s the code for ‘updateFromPi.py’:

#!/usr/bin/python3
  
 import sys
 import glob
 import re
 from io import BytesIO
 import json
 import pprint
 import requests
 import time
 import subprocess
  
 #client id and client secret
 client = '<put client id here>'
 access_token='<put access token here>'
  
 base_dir = '/sys/bus/w1/devices/'
 device_folder = glob.glob(base_dir + '28*')[0]
 device_file = device_folder + '/w1_slave'
  
 niters = 60
 interval = 15
  
 def read_temp_raw_old():
     f = open(device_file, 'r')
     lines = f.readlines()
     f.close()
     return lines
  
 def read_temp_raw():
     catdata = subprocess.Popen(['cat',device_file],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
     out,err = catdata.communicate()
     out_decode = out.decode('utf-8')
     lines = out_decode.split('\n')
     return lines
  
 def read_temp():
     lines = read_temp_raw()
     while lines[0].strip()[-3:] != 'YES':
         time.sleep(0.2)
         lines = read_temp_raw()
     equals_pos = lines[1].find('t=')
     if equals_pos != -1:
         temp_string = lines[1][equals_pos+2:]
         temp_c = float(temp_string) / 1000.0
         temp_f = temp_c * 9.0 / 5.0 + 32.0
         return temp_c, temp_f
  
 def main():
    old_temp = -999999999.99
    endpoints_url = "https://graph.api.smartthings.com/api/smartapps/endpoints/%s?access_token=%s" % (client, access_token)
    r = requests.get(endpoints_url)
    if (r.status_code != 200):
       print("Error: " + r.status_code)
    else:
       theendpoints = json.loads( r.text )
       for counter in range(niters, 0, -1):
          (temp_c, temp_f) = read_temp()
          for endp in theendpoints:
             uri = endp['uri']
             temp_url = uri + ("/update/%.2f/F" % temp_f)
             headers = { 'Authorization' : 'Bearer ' + access_token }
             if ( round(temp_f, 2) != round(old_temp, 2) ):
                r = requests.put(temp_url, headers=headers)
          old_temp = temp_f
          time.sleep(interval)
  
 main()

Now, you can run this from the command line, and you should to test. Go to the Smartthings App and show your device. It should show the temperature updating:


5 Likes

Code for this project is found here: pcifarelli

4 Likes

Hi Paul

This is a great project. I have 8 of these temperature sensors across 2 Pi’s. I easily got one sensor reporting into Smartthings by using your code and following your guide. Now I would like to replicate the devices to get the remaining probes reporting in.

Would be really grateful if you would be able give me some pointers as to how I would go about achieving this.

Thanks

Jim

It depends a bit on how you want to model it - 8 separate ST devices, 2 ST devices (one for each pi with 4 temps each), or 1 ST device with 8 temps.

On each pi you need to run the python script, modified to accommodate the 4 devices on each pi. How you modify the script to update STs will depend on how you want to model it.

  1. 8 separate devices: the loop below for endp in theendpoints: - that’s a loop over all the devices that have this smartapp attached to it. So for my example it would only loop once - but for yours it will loop 8 times. Since you have these spread over 2 different pis, you’ll have to have a strategy for keeping them separate. One way is to make 2 separate smartapps using the same code, each with it’s own OAuth2 creds. Then it would loop only 4 times for each pi.

  2. 2 devices with 4 separate temps: in this case you’ll need to modify the pi temperature sensor smartapp to include an endpoint for each temperature. In other words, you need something like path("/update/:dev/:temp/:units")

  3. 1 device with 8 temps: Each pi updates the same device, but one updates the first 4 temps and 1 updates the last 4 temps. You have to come up with a strategy for which updates which so that you can tell them all apart. You’ll also need to do (2) so that you can update 8 temps.

I’m assuming you know python.You’ll notice this line in the script:

device_folder = glob.glob(base_dir + '28*')[0]

That’s returning the first device folder. If you remove the [0] it will return an array (a ‘list’ in python terms) of all of them.

Then you need to modify this line:

device_file = device_folder + '/w1_slave'

The easiest thing to do is make that an array too. Something like

for (i in range(0,3)):
   device_file.append(device_folder[i]/w1_slave)

Then the first device would be device_file[0], the 2nd device_file[1], etc. You’ll need to do the same for old_temp, which just saves the last value so you don’t update ST unless the temp changes.

Modify read_temp and read_temp_raw to take a integer parameter that is the index of the device.

Then just loop over the devices to update the ST device:

  for (i in range(0,3)):
     (temp_c, temp_f) = read_temp(i)
     for endp in theendpoints:
     ...
     (this part depends on your ST device strategy)
     ... 
     old_temp[i] = temp_f
     time.sleep(interval)

I hope that helps…

Paul

1 Like

Hi Paul

It certainly does help. I am working on sorting it out now.

Thanks

Jim

Hi Paul

Hoping I can trouble you a bit more.

I am using the 1st model you mentioned (an ST device for each probe).

I have the script looping through the sensors (3 at the moment) and sending the temperatures to ST.

What I can’t do is work out how to differentiate the feeds into ST. At present all of my ST temperature devices cycle through the 3 feeds from the Pi. The temp_url links generated in the for endp in the endpoints loop are as below with the x’d part of the link being the same for all 3:

https://graph-eu01-euwest1.api.smartthings.com:443/api/smartapps/installations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/update/69.91/F
https://graph-eu01-euwest1.api.smartthings.com:443/api/smartapps/installations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/update/98.83/F
https://graph-eu01-euwest1.api.smartthings.com:443/api/smartapps/installations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/update/73.06/F

I changed the smartapp preferences line to multiple: true as below

input “tdevice”, “capability.temperatureMeasurement”, title: “Which Simulated Temperature Sensor?”, multiple: true

but other than that I am stuck.

Hope you can help.

Thanks

Jim

What you’ve got so far is a device handler for the temperature device, plus the smartapp that implements the RESTful endpoint to update the temperature. In the Smartthings web ide you created your 3 devices, and each one is given a different id,and label. Now when you install the smartapp you need to select all 3 of the devices you created - and you’ll get in the smart app 3 different simulated devices in a list called tdevice. I think that’s where you are.

Now, the only thing that distinguishes those temp devices so far is that each one has a different bus id when you ls -l /sys/bus/w1/devices. They each start with 28-, and then there is a number. Assuming you’ve labeled them so that you know which bus id goes to which probe, all you need to do is relate the bus id to the device in STs.

There are a number of ways to do this, but all of them start with having the python script tell the smartapp which of the bus ids you are updating. Since I did the project originally with only 1 dev, I didn’t need to worry about this - but you do.

This means you either need to create a separate smartapp (with OAuth enabled) for each device (I dont recommend this), or modify the smart app to accept the bus id. You can do this as described above:

path("/update/:dev/:temp/:units")

But now you have to modify the python script to extract the bus id and add it to the endpoint after the “update” and before the temperature value.

Once you have that, you need to relate the bus id to only one of the ST devices in the tdevice list. One simple way to do this is to use the bus id as the Device Network Id for the device (when you create it in the ST Web IDE). Then in the smartapp, loop through tdevice looking for the one with the same id that you got in “dev” from the endpoint post.

I think that should work no matter which pi you’re updating from.

Hope that helps
Paul

1 Like
This means you either need to create a separate smartapp (with OAuth enabled) for each device (I dont recommend this), or modify the smart app to accept the bus id. You can do this as described above:

loving this thread… fyi I hit this exact same problem but wasn’t as perceptive as @paulc2 so I created a separate smartapp for each of my 3 temp sensors. works great now, but setup (getting disctinct client IDs for OAuth,etc) was a pain.

Thanks for the suggestion on modifying the smartapp/python code to accept the bus id. Brilliant.

Hoping you can give me some pointers on this last part. So far I’ve updated the endpoint to accept the busid, configured the app to allow for multiple temp sensors, changed my python code to send with the new endpoint format, but I can’t figure out the last piece to match up the temp to the device in the smart app.

Right now the code is throwing an error in the simulator error groovy.lang.MissingPropertyException: No such property: name for class: java.lang.String @ line 71

But I’m not even sure I did the device matching correctly. Thanks in advance!

/**
 *  Pi Temperature Sensor Endpoints
 *
 *  Copyright 2016 John Hill
 *    Based on code by Paul Cifarelli & Kristopher Kubicki
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
definition(
    name: "HTTP PiPool - Pool Return Temp",
    namespace: "pcifarelli",
    author: "Paul Cifarelli",
    description: "REST endpoint for Pi to update a Simulated Temperature Sensor",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
)

preferences {
    section("Allow Endpoint to Control This Thing") {
        input "tdevices", "capability.temperatureMeasurement", title: "Which Simulated Temperature Sensor?", multiple: true
    }
}

mappings {
//    path("/update/:temp/:units") {
    path("/update/:dev/:temp/:units") {
        action: [
            PUT: "updateTemp"
        ]
    }
}

def installed() {
    log.debug "Installed with settings: ${settings}"

//  initialize()
}

def updated() {
    log.debug "Updated with settings: ${settings}"
    
    unsubscribe()
//  initialize()
}

def initialize() {
    // TODO: subscribe to attributes, devices, locations, etc.
}

// implement event handlers

void updateTemp() {
	log.debug "received update request"
	settings.tdevices.findAll({it.name == params['dev']}).each{
    	log.debug "Found matching device: ${it.label}"
        update(it.label)
	}
}

private void update(device) {
     log.debug "update, request: params: ${params['temp']} ${params['units']} ${device.name}"
     def t = 0
     
     if (location.temperatureScale == params['units']) {
        log.debug "yes, the temperatureScale is the same (${params['units']})"
        t = Double.parseDouble(params['temp'])
     } else if (params['units'] == "F") {
        // so location is set for C
        t = 5 * ( Double.parseDouble(params['temp']) - 32 ) / 9
     } else if (params['units'] == "C") {
        // so location is set for F
        t = 9 * Double.parseDouble(params['temp']) / 5 + 32
     }
     def x = Math.round(t * 100) / 100

	 device.setTemperature(x)
     
}

edit: line 71, which is throwing the error is: log.debug "update, request: params: ${params['temp']} ${params['units']} ${device.name}" def t = 0

I think you just have a syntax error in you findAll closure. So it should be something more like:

def list_of_devices = settings.tdevices.findAll { it.name == params[‘dev’] }
list_of_devices.each { update(it.label) }

Since you made the devices all unique (or that was the goal at least), you don’t need findAll - you can just use find:

def a_device = settings.tdevices.find { it.name == params[‘dev’]
update(a_device)

make sure update(…) can take a null though.

1 Like

Thanks for the quick response… if I can bug you one more time. I’m close but certainly just running in a syntax issue.

I changed my updateTemp routine as you suggested and it’s passing the correct (found) device to the update routine:

def a_device = settings.tdevices.find { it.name == params[‘dev’]
update(a_device)

But now the device.setTemperature(x) line produces the following error:
groovy.lang.MissingMethodException: No signature of method: java.util.ArrayList.setTemperature() is applicable for argument types: (java.math.BigDecimal) values: [91.19] @ line 89

I’ve tried a few variations of the line - just stabbing at the syntax based on google searches:

settings.device.setTemperature(x)
settings."${device.id}".setTemperature(x)
settings."${device.name}".setTemperature(x)
settings."${device.label}".setTemperature(x)
settings.tdevices.device.setTemperature(x)

They either produce the BigDecimal error above or something like:
java.lang.IllegalArgumentException: Property '[3a08fa1d-334d-4d42-af5f-9354c1fe9138]' is not supported for devices @ line 89

Hoping it’s a SMH obvious thing you can see at a glance. You’ve already helped a ton.

In your old code you had called update like this:

update(it.label)

In your last reply you cut-and-pasted what I showed you (i can tell by the missing ‘}’)

So, the question is, did you call update(a_device) or update(a_device.label) ? The later would produce the error you are getting.

It’s just a guess as its difficult to diagnose without seeing the actual code.

Regards
Paul

Apologies… poor communication on my part. I am sending the device to the update routine - update(a_device). Here is my code as it stands now:

preferences {
    section("Allow Endpoint to Control This Thing") {
        input "tdevices", "capability.temperatureMeasurement", title: "Which Simulated Temperature Sensor?", multiple: true
    }
}

mappings {
//    path("/update/:temp/:units") {
    path("/update/:dev/:temp/:units") {
        action: [
            PUT: "updateTemp"
        ]
    }
}

def installed() {
    log.debug "Installed with settings: ${settings}"

//  initialize()
}

def updated() {
    log.debug "Updated with settings: ${settings}"
    
    unsubscribe()
//  initialize()
}

def initialize() {
    // TODO: subscribe to attributes, devices, locations, etc.
}

// implement event handlers

void updateTemp() {
//	log.debug "received update request"
/*  settings.tdevices.findAll({it.name == params['dev']}).each{
    	log.debug "Found matching device: ${it.label}"
        update(it.label)
	}
*/
    def a_device = settings.tdevices.findAll { it.name == params['dev'] }
    update(a_device)
}

private void update(device) {
     log.debug "update, request: params: ${params['temp']} ${params['units']} ${device.label}"
     def t = 0
     
     if (location.temperatureScale == params['units']) {
        log.debug "yes, the temperatureScale is the same (${params['units']})"
        t = Double.parseDouble(params['temp'])
     } else if (params['units'] == "F") {
        // so location is set for C
        t = 5 * ( Double.parseDouble(params['temp']) - 32 ) / 9
     } else if (params['units'] == "C") {
        // so location is set for F
        t = 9 * Double.parseDouble(params['temp']) / 5 + 32
     }
     def x = Math.round(t * 100) / 100

	 tdevices.device.setTemperature(x)
     
}

This is producing the following error:

9:47:19 AM: error groovy.lang.MissingMethodException: No signature of method: java.util.ArrayList.setTemperature() is applicable for argument types: (java.math.BigDecimal) values: [91.19] @ line 89

Line 89 is tdevices.device.setTemperature(x)

Ok, in the future, note that comments are counted for line numbers, so if you dont include them then giving a line number doesnt help. However, as you did give the code, your problem is, well, exactly what the error is telling you.

somewhere along the line you changed devices.setTemperature(x) to be tdevices.device.setTemperature(x). That’s the mistake.

tdevices is a list - one that came from the declaration of your preference section.

device was passed into the call - we went through some trouble to find the right device and sent it to the update(…) function - it should be used directly.

the line should be device.setTemperature(x)

(not devices.setTemperature… that was another type)

device.setTemperature(x) produces the same error. I’ll keep trying…

It’s working… device is now a list (granted a list of 1), so I had to change the call to update to reference a single device:

void updateTemp() {
    def a_device = settings.tdevices.findAll { it.name == params['dev'] }
    update(a_device[0])
}

Is there any chance you could share your working code for multiple temperature probes? I have the original script set up which pulls temperature from a single probe fine, but am struggling to get it working with multiple probes (I have 4 connected). Thanks.

I’ve been trying to set up a pair of sensors (1 pi with 2 physical sensors, 1 device handler, 2 virtual sensors, 1 smartapp) and running into issues during the update process. I pass the deviceId in from python to match with the virtual sensor networkDeviceId. That part seems to work ok, but I get multiple updates and the first sensor’s value gets overwritten by the second.

Debug log:

9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:04 PM: debug yes, the temperatureScale is the same (F)
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:04 PM: debug update, request: params: 70.03 F Outdoor Temperature Sensor
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:04 PM: debug found device: Outdoor Temperature Sensor
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:04 PM: debug SettingsTDevice: [Outdoor Temperature Sensor]
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:04 PM: debug passed param dev: 28-0517c1ea54ff
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:03 PM: debug yes, the temperatureScale is the same (F)
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:03 PM: debug update, request: params: 70.03 F Outdoor Temperature Sensor
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:03 PM: debug found device: Outdoor Temperature Sensor
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:03 PM: debug SettingsTDevice: [Outdoor Temperature Sensor, Shed Temperature Sensor]
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:03 PM: debug passed param dev: 28-0517c1ea54ff
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:01 PM: debug found device: null
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:01 PM: debug SettingsTDevice: [Outdoor Temperature Sensor]
9d97695a-4f00-4c7e-b420-84fe7d2eb505  3:19:01 PM: debug passed param dev: 28-0517c1f437ff
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug yes, the temperatureScale is the same (F)
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug update, request: params: 71.94 F Shed Temperature Sensor
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug found device: Shed Temperature Sensor
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug SettingsTDevice: [Outdoor Temperature Sensor, Shed Temperature Sensor]
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug passed param dev: 28-0517c1f437ff
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug SettingsTDevice: [Outdoor Temperature Sensor, Shed Temperature Sensor]
95966712-a1b7-4ff7-8d0c-fd1d3a1e7f37  3:19:00 PM: debug passed param dev: 28-0517c1f437ff




I’m calling the python with cron every 15 mins and this is the code which loops over the 2 endpoints and seems to send it to the smartapp twice, but that should be covered by the null check in the smartapp during the update.

smartthings-temp-multi.py

base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')
device_file = []
for i in range(0,2): 
 device_file.append(device_folder[i]+'/w1_slave')
#outdoor_sensor = '/sys/bus/w1/devices/28-0517c1ea54ff/w1_slave'
#shed_sensor = '/sys/bus/w1/devices/28-0517c1f437ff/w1_slave'
output_log = '/home/jclifford/tempsensor/SToutput.log'

 
def read_temp_raw(i):
    catdata = subprocess.Popen(['cat',device_file[i]],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    out,err = catdata.communicate()
    out_decode = out.decode('utf-8')
    lines = out_decode.split('\n')
    return lines
 
def read_temp(i):
    lines = read_temp_raw(i)
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.2)
        lines = read_temp_raw()
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c = float(temp_string) / 1000.0
        temp_f = temp_c * 9.0 / 5.0 + 32.0
        return temp_f
        
def getDeviceId(i):
    deviceId = os.path.basename(os.path.normpath(device_folder[i]))
    return deviceId
 
def main():
    endpoints_url = "https://graph.api.smartthings.com/api/smartapps/endpoints/%s?access_token=%s" % (client, access_token)
    r = requests.get(endpoints_url)
    if (r.status_code != 200):
       print("Error: " + r.status_code)
    else:
       theendpoints = json.loads( r.text )       
       for i in range(0,2):
         temp_f = read_temp(i)
         deviceId = getDeviceId(i)
         print("deviceId: " + str(deviceId)+ " TempF: " + str(temp_f))
         for endp in theendpoints:
            uri = endp['uri']
            temp_url = uri + ("/update/" + str(deviceId)+ "/%.2f/F" % temp_f)
            headers = { 'Authorization' : 'Bearer ' + access_token }
            #print(temp_url)
            r = requests.put(temp_url, headers=headers)

       quit()
 
main()

Snippet of the updating code from the smartapp:

void updateTemp() {
	log.debug "passed param dev: ${params['dev']}"
	log.debug "SettingsTDevice: ${settings.tdevice}"

    def a_device = settings.tdevice.find { it.deviceNetworkId == params['dev'] }

    log.debug "found device: ${a_device}"

    if(a_device != null){ update(a_device) }    
}

Any help is much appreciated.

Just found this thread and I need some help here. I have a Raspian Pi3. I have 3 temp sensors on it and they are connected and reporting temperatures. I installed the DHT and the ST app and also updated Python and Curl like the instructions say. When I run the python script I get a:
Traceback (most recent call last):
File “updateFromPi.py”, line 68, in
main()
File “updateFromPi.py”, line 54, in main
print("Error: " + r.status_code)
TypeError: cannot concatenate ‘str’ and ‘int’ objects

Error.

I do not know how to fix this. Can someone help? I eventually want to get all the sensors working, I will eventually have more than three.

Thank you for any help