[Solved] Add temperature to image

Forum for questions and support relating to the 1.30.x releases only.
Sigge
Posts: 116
Joined: Mon May 02, 2016 8:14 am

[Solved] Add temperature to image

Post by Sigge »

I am trying to figure out if it is possible to have the readout from a USB thermometer added to the monitors.

zmtrigger has the ability to add text to the image but I do not want to trigger an alarm.

Is there a way to do this?

Thanks
Last edited by Sigge on Sun Feb 12, 2017 11:16 pm, edited 1 time in total.
Baylink
Posts: 338
Joined: Sun Jun 19, 2005 3:19 am

Re: Add temperature to image

Post by Baylink »

If I'm not mistaken, I have recently seen mention of a camera that has a built in temp sensor, and can OSD the reading for you. I can't tell you which one, alas.

If you haven't already got running cameras, that might help. :-)
User avatar
iconnor
Posts: 2879
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: Add temperature to image

Post by iconnor »

Sounds like maybe we should make zmtrigger optionally not alarm....
User avatar
knight-of-ni
Posts: 2404
Joined: Thu Oct 18, 2007 1:55 pm
Location: Shiloh, IL

Re: Add temperature to image

Post by knight-of-ni »

zmtrigger is the answer.
Set your action to "show" and it will update the text on your monitor w/o setting an alarm, provided you have %Q placeholder added to the timestamp format field.

See the documentation:
http://zoneminder.readthedocs.io/en/sta ... estamp-tab
http://zoneminder.readthedocs.io/en/sta ... igger#perl
Visit my blog for ZoneMinder related projects using the Raspberry Pi, Orange Pi, Odroid, and the ESP8266
All of these can be found at https://zoneminder.blogspot.com/
Sigge
Posts: 116
Joined: Mon May 02, 2016 8:14 am

Re: Add temperature to image

Post by Sigge »

So I am on the right track thinking a script that takes the temp reading and writes it to a file. Then somehow use the file/device interfaces in zmtrigger? Or could I call the temp binary from within the zmtrigger script?
User avatar
knight-of-ni
Posts: 2404
Joined: Thu Oct 18, 2007 1:55 pm
Location: Shiloh, IL

Re: Add temperature to image

Post by knight-of-ni »

You write a script, which reads the temp from your device, then it should write that data to one of the sockets zmtrigger creates after you enable it. When zmtrigger is started, it creates a network and a unix socket. You send it a simple ascii string, defined in the docs mentioned earlier.

Just google how to write to a socket for the programming/scripting language of your choice if you don't know how to do that. Fairly straight forward.

I was going to just point you to my blog:
http://zmtrigger.blogspot.com/p/display-real.html

But when I went to that page, it was mostly empty. Apparently, I forgot to document the project I did using this feature. It is still in my head.

What I did was wire a DHT22 temp sensor to an esp8266. This chip is basically a mini adruino with builtin wifi.

It sends the temp to my zoneminder server every 15 seconds. Since I never uploaded the sketch to my blog, I pasted it below. You can see what I sent to the zm server socket. If your sensor is on the same machine running zmtrigger, then naturally the server ip becomes localhost. You could just write to the zmtrigger.sock file instead if you want.

Code: Select all

/*
* ESP8266_zmtrigger 
*
* Date of Last Revision: Jan 14, 2015
*
* Upon activation of GPIO2, this sketch sends a user defined command to a ZoneMinder 
* server running zmtrigger on port 6802.
* 
*/

#include <Arduino.h>
#include <DHT.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>

#define GPIO0 0
#define GPIO2 2
//DHT22 Sensor
#define DHTTYPE DHT22
#define DHTPIN  2

// ThingSpeak Settings
const char tsserver[]="184.106.153.149";
const String writeapi="thingspeak_api_key_here";
const int updateThingSpeakInterval = 60 * 1000; // 60 second interval at which to update ThingSpeak
const int updateZmTriggerInterval = 15 * 1000; // 15 second interval at which to update zoneminder

// DHT22 Settings
float humidity, temp_f;  // Values read from sensor
const int interval = 2000;              // interval at which to read sensor


ESP8266WiFiMulti WiFiMulti;
WiFiClient client;
ESP8266WebServer server(80);

// Define global variables
const char zmserver[] = "192.168.1.10";
const int port = 6802;
unsigned long previousMillis = 0;
unsigned long lastZMTriggerTime = 0;
unsigned long lastThngSpkTime = 0;
int throttle = 1000; // Don't send more than 1 message per this value in milliseconds
byte netFails = 0;
String TSerrorMsg = "No Thingspeak Error Reported";
String ZMerrorMsg = "No ZoneMinder Error Reported";
String TSsuccessMsg = "No Thingspeak Success Reported";
String ZMsuccessMsg = "No ZoneMinder Success Reported";
const String htmlHead = "<html>\r\n<head>\r\n<meta http-equiv='refresh' content='61'>\r\n</head>\r\n<body>\r\n";
const String htmlTail = "\r\n</body>\r\n</html>\r\n";
byte percentQ = 0;

// Initialize DHT sensor 
// NOTE: For working with a faster than ATmega328p 16 MHz Arduino chip, like an ESP8266,
// you need to increase the threshold for cycle counts considered a 1 or 0.
// You can do this by passing a 3rd parameter for this threshold.  It's a bit
// of fiddling to find the right value, but in general the faster the CPU the
// higher the value.  The default for a 16mhz AVR is a value of 6.  For an
// Arduino Due that runs at 84mhz a value of 30 works.
// This is for the ESP8266 processor on ESP-01 
DHT dht(DHTPIN, DHTTYPE, 11); // 11 works fine for ESP8266

/* 
*  zmTrigger expects commands to be in the following format:
*  
*<id>|<action>|<score>|<cause>|<text>|<showtext>
*
*<id>
*  is the id number or name of the ZM monitor.
*  
*<action>
*  Valid actions are 'on', 'off', 'cancel' or 'show' where
*  'on' forces an alarm condition on;
*  'off' forces an alarm condition off;
*  'cancel' negates the previous 'on' or 'off'.
*  The 'show' action merely updates some auxiliary text which can optionally
*  be displayed in the images captured by the monitor. Ordinarily you would
*  use 'on' and 'cancel', 'off' would tend to be used to suppress motion
*  based events. Additionally 'on' and 'off' can take an additional time
*  offset, e.g. on+20 which automatically 'cancel's the previous action
*  after that number of seconds.
*  
*<score>
*  is the score given to the alarm, usually to indicate it's
*  importance. For 'on' triggers it should be non-zero, otherwise it should
*  be zero.
*  
*<cause>
*  is a 32 char max string indicating the reason for, or source of
*  the alarm e.g. 'Relay 1 open'. This is saved in the 'Cause' field of the
*  event. Ignored for 'off' or 'cancel' messages.
*  
*<text>
*  is a 256 char max additional info field, which is saved in the
*  'Description' field of an event. Ignored for 'off' or 'cancel' messages.
*  
*<showtext>
*  is up to 32 characters of text that can be displayed in the
*  timestamp that is added to images. The 'show' action is designed to
*  update this text without affecting alarms but the text is updated, if
*  present, for any of the actions. This is designed to allow external input
*  to appear on the images captured, for instance temperature or personnel
*  identity etc.
*/

String zmtriggerShowText = "15|show||||";

void handle_root()
{
  String Msg = "Hello from the ESP8266!<br><br>This server responds to the following urls:<br>";
  Msg += "<a href=\"/gpio0\">/gpio0</a><br>";
  Msg += "<a href=\"/gpio2\">/gpio2</a><br>";
  Msg += "<a href=\"/temp\">/temp</a><br>";
  Msg += "<a href=\"/temp_val\">/temp_val</a><br>";
  Msg += "<a href=\"/humidity\">/humidity</a><br>";
  Msg += "<a href=\"/humidity_val\">/humidity_val</a><br>";
  Msg += "<a href=\"/error\">/error</a><br>";
  Msg += "<a href=\"/success\">/success</a><br>";
  Msg += "<a href=\"/status\">/status</a><br>";
  server.send(200, "text/html", htmlHead+Msg+htmlTail);
  delay(100);
}

bool gettemperature() {
  // Wait at least 2 seconds seconds between measurements.
  // if the difference between the current time and last time you read
  // the sensor is bigger than the interval you set, read the sensor
  // Works better than delay for things happening elsewhere also
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis >= interval) {
    // save the last time you read the sensor 
    previousMillis = currentMillis;   

    // Reading temperature for humidity takes about 250 milliseconds!
    // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
    humidity = dht.readHumidity();          // Read humidity (percent)
    temp_f = dht.readTemperature(true);     // Read temperature as Fahrenheit
  }
  return( isnan(humidity) || isnan(temp_f) );
}

void updateThingSpeak(String tsData) {
  // Create the client object here so it is destroyed after each update attempt
  WiFiClient client;
  
  if (client.connect(tsserver, 80)) {
    client.print("POST /update HTTP/1.1\n");
    client.print("Host: api.thingspeak.com\n");
    client.print("Connection: close\n");
    client.print("X-THINGSPEAKAPIKEY: " + writeapi + "\n");
    client.print("Content-Type: application/x-www-form-urlencoded\n");
    client.print("Content-Length: ");
    client.print(tsData.length());
    client.print("\n\n");
    client.print(tsData);

    if (client.connected()) { // We got a response from the server but we don't yet know what kind of response

      String Msg = client.readString();
      if ( Msg.substring(0,Msg.indexOf("\r\n")).indexOf(" 200 ") > -1 ) { // Check the message for an http resposne code of 200
        Serial.println();
        Serial.println("HTTP 200 Response received. Data Sent to ThingSpeak!");
        TSsuccessMsg = Msg;
      } else {
        Serial.println();
        Serial.println("ThingSpeak server returned an HTTP error code. Check HTTP://"+String(WiFi.localIP())+"/error for the response message.");
        TSerrorMsg = Msg;
      }     
      netFails = 0;
    } else {
      TSerrorMsg = "Timed out waiting for a response from Thingspeak server!";
      Serial.println();
      Serial.println(TSerrorMsg);
    }
  } else {
    TSerrorMsg = "Connection refused when connecting to Thingspeak!";
    Serial.println();
    Serial.println(TSerrorMsg);
    netFails++;
  }
  //Serial.println(client.readString());
  client.stop();
}

void WiFiStatus() {
    
    if (WiFi.RSSI() <= -100) {
      percentQ = 0;
    } else if (WiFi.RSSI() >= -50) {
      percentQ = 100;
    } else {
      percentQ = 2 * (WiFi.RSSI() + 100);
    }

    Serial.println("");
    Serial.println("WiFi Status");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.print("MAC address: ");
    Serial.println(WiFi.macAddress());
    Serial.print("SSID: ");
    Serial.println(WiFi.SSID());
    Serial.print("Signal Strength (%): ");
    Serial.println(percentQ);
}

void ChipInfo() {
  Serial.println();
  Serial.print("Flash Chip ID: ");
  Serial.println(ESP.getFlashChipId());
  Serial.print("Size of ESP8266 flash chip: ");
  Serial.println(ESP.getFlashChipRealSize());
  Serial.print("Flash Chip Speed: ");
  Serial.println(ESP.getFlashChipSpeed());
  Serial.print("Flash Chip Mode: ");
  Serial.println(ESP.getFlashChipMode());
  Serial.println();
}

void updateZMTrigger(String Cmd) {
  if (client.connect(zmserver, port)) {
    client.println(Cmd);

    if (client.connected()) {
      ZMsuccessMsg = "Zmtrigger ShowText Sent to ZoneMinder";
      Serial.println();
      Serial.println(ZMsuccessMsg);
      Serial.println(Cmd);
      netFails = 0;
    } else {
      ZMerrorMsg = "Timed out connecting to ZoneMinder server";
      Serial.println();
      Serial.println(ZMerrorMsg);
    }
  } else {
    ZMerrorMsg="Connection refused connecting to ZoneMinder server";
    Serial.println();
    Serial.println(ZMerrorMsg);
    netFails++;
  }
}

void setup() {
    // Configure our GPIO's as inputs
    pinMode(GPIO0, INPUT_PULLUP);
    pinMode(GPIO2, INPUT);
    
    Serial.begin(115200);
   // Serial.setDebugOutput(true);

    Serial.println("");

    for(uint8_t t = 4; t > 0; t--) {
        Serial.printf("[SETUP] WAIT %d...\n", t);
        Serial.flush();
        delay(1000);
    }

    WiFiMulti.addAP("ssid1", "password1");
    WiFiMulti.addAP("ssid2", "password2");

    ChipInfo();
    
    // Wait for connection
    Serial.print("Waiting for WiFi to Connect...");
    while (WiFiMulti.run() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.println("Connected!");
    server.on("/", handle_root);
  
    server.on("/gpio0", [](){  // url to display the current value of GPIO0
      server.send(200, "text/html", htmlHead+"Current value of GPIO0: "+String(digitalRead(GPIO0))+htmlTail);
    });

    server.on("/gpio2", [](){  // url to display the current value of GPIO2
      server.send(200, "text/html", htmlHead+"Current value of GPIO2: "+String(digitalRead(GPIO2))+htmlTail);
    });

    server.on("/temp", [](){  // url to display the temperature with html formatting
      server.send(200, "text/html", htmlHead+"Temperature (deg. F): "+String(temp_f,2)+htmlTail);
    });

    server.on("/temp_val", [](){  // url to display the raw temperature value only
      server.send(200, "text/plain", String(temp_f,2));
    });

    server.on("/humidity", [](){  // url to display the humidity with html formatting
      server.send(200, "text/html", htmlHead+"Humidity (%): "+String(humidity,1)+htmlTail);
    });

    server.on("/humidity_val", [](){  // url to display the raw humidity calue only
      server.send(200, "text/plain", String(humidity,1));
    });
    
    server.on("/error", [](){  // url to display the last reported error message with html formatting
      TSerrorMsg.replace("\r\n","<br>"); // replace new lines with html <br> for readability
      server.send(200, "text/html", htmlHead+"Last reported ZoneMinder error message:<br><br>"+ZMerrorMsg+"<br><br>Last reported ThingSpeak error message:<br><br>"+TSerrorMsg+htmlTail);
    });

    server.on("/success", [](){  // url to display the last reported success message with html formatting
      TSsuccessMsg.replace("\r\n","<br>"); // replace new lines with html <br> for readability
      server.send(200, "text/html", htmlHead+"Last reported ZoneMinder success message:<br><br>"+ZMsuccessMsg+"<br><br>Last reported ThingSpeak success message:<br><br>"+TSsuccessMsg+htmlTail);
    });

    server.on("/status", [](){  // url to display esp8266 status
      server.send(200, "text/html", htmlHead+"IP Address: "+WiFi.localIP()+"<br>"+
                                             "MAC address: "+WiFi.macAddress()+"<br>"+
                                             "SSID: "+WiFi.SSID()+"<br>"+
                                             "Signal Strength (%): "+percentQ+"<br>"+
                                             "Number of Consecutive Network Errors: "+netFails+"<br>"+
                                              htmlTail);
    });

    gettemperature();
    server.begin();
    Serial.println("");
    Serial.println("HTTP server started");
}

void loop() {
  
    server.handleClient();
    
    // Reset previousMillis when millis overflows back to zero
    if ( millis() - previousMillis < 0 ) {
      previousMillis = 0;
    }

    // Reset lastZMTriggerTime when millis overflows back to zero
    if ( millis() - lastZMTriggerTime < 0 ) {
      lastZMTriggerTime = 0;
    }

    // Reset lastThngSpkTime when millis overflows back to zero
    if (millis() - lastThngSpkTime < 0 ) {
      lastThngSpkTime = 0;
    }

    // Update ThingSpeak
    if (millis() - lastThngSpkTime > updateThingSpeakInterval) {
        if (!gettemperature()) {
            updateThingSpeak("field1="+String(temp_f,2)+"&field2="+String(humidity,1)+"&field3="+String(WiFi.RSSI()));
            Serial.println("Temperature (deg. F): "+String(temp_f,2));
            Serial.println("Humidity (%): "+String(humidity,1));
        } else {
            TSerrorMsg="Temperature or Humidity read failed. Skipping Thingspeak update.";
            Serial.println();
            Serial.println(TSerrorMsg);
        }
        lastThngSpkTime = millis();
    }

    // Update zmtrigger
    if (millis() - lastZMTriggerTime > updateZmTriggerInterval) {
        WiFiStatus();
        updateZMTrigger(zmtriggerShowText+" - "+String(temp_f,2)+" Deg. F");
        lastZMTriggerTime = millis();
    }

    if ( netFails >= 15 ) {
      Serial.println("Too many network failures. Restarting WiFi....");
      //delay(3000);
      WiFi.reconnect();
      netFails = 0;
      // ESP.reset();
      delay(3000);
    }

}
Visit my blog for ZoneMinder related projects using the Raspberry Pi, Orange Pi, Odroid, and the ESP8266
All of these can be found at https://zoneminder.blogspot.com/
rockedge
Posts: 1173
Joined: Fri Apr 04, 2014 1:46 pm
Location: Connecticut,USA

Re: Add temperature to image

Post by rockedge »

I use a script that polls a weather service server for my local temperatures. Then using zmTrigger.pl as described above using the %Q place holder in the Timestamp and using "show" in the command string for zmTrigger.pl to display it. I also use this method to indicate when an external remote motion detector triggers an event in the Timestamp.

When using the get the temperature script which is in PERL I set up a CRON job that 1x per hour it checks the temperature and updates the display.

I wanted to try out displaying the current water temp from a large aquarium that cameras where trained on with a usb thermometer.
rockedge
Posts: 1173
Joined: Fri Apr 04, 2014 1:46 pm
Location: Connecticut,USA

Re: Add temperature to image

Post by rockedge »

here is an example of 2 scripts that work together but of course they could be done completely in PERL or PHP and stand alone.

Code: Select all

<?php
// monck-usb-temp3.php //

   $monitor = $_REQUEST['mon'];
   $extTrigger = $_REQUEST['ext'];
   $json_string =  file_get_contents_curl("http://api.wunderground.com/api/xxxxxxxxxxx/geolookup/conditions/astronomy/q/CT/Shelton.json");
   $parsed_json = json_decode($json_string);
   $temperature_string = $parsed_json->{'current_observation'}->{'temperature_string'};
     
//   $cfgServer = "someZM.domain";
//   $cfgServer = "192.168.0.9";
   $cfgServer = "localhost";
   $cfgPort = 6802;
  

switch ($extTrigger) {
	case "on":
	 $extTriggerx = "$monitor|on+120|255|motion sensor|FrontDoor|motion sensor";
	  break;
	case "off":
	 $extTriggerx = "$monitor|off|0|||";
	  break;
	case "show":
	 $extTriggerx = "$monitor|show|255|current temp|troutcams|$temperature_string";
      break;
}
$conn_handle = fsockopen($cfgServer, $cfgPort);
 if(!$conn_handle) {
        echo "Connection failed ";
   exit();
    }else{
      //  echo "Connected <br>";
        fputs($conn_handle, $extTriggerx);
    }  
fclose($conn_handle);

   
function file_get_contents_curl($url) {
    $ch = curl_init();
     
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //Set curl to return the data instead of printing it to the browser.
    curl_setopt($ch, CURLOPT_URL, $url);
     
    $data = curl_exec($ch);
    curl_close($ch);
     
    return $data;

}
?>
PERL script ->

Code: Select all

#!/usr/bin/perl -w

use LWP::Curl;
        my $referer = 'http://192.168.0.14/zm';
        my $get_url = 'http://192.168.0.14/zm/monck-usb-temp3.php?mon=3&ext=show';
        my $get_url_two = 'http://192.168.0.9/zm/monck-usb-temp3.php?mon=2&ext=show';
    my $lwpcurl = LWP::Curl->new();
    
        my $content = $lwpcurl->get($get_url, $referer); 
        my $contenttwo = $lwpcurl->get($get_url_two, $referer);
Sigge
Posts: 116
Joined: Mon May 02, 2016 8:14 am

Re: Add temperature to image

Post by Sigge »

Thanks, I did follow your build of an external trigger. Interesting might try and build one myself.
knnniggett wrote:You write a script, which reads the temp from your device, then it should write that data to one of the sockets zmtrigger creates after you enable it. When zmtrigger is started, it creates a network and a unix socket. You send it a simple ascii string, defined in the docs mentioned earlier.

Just google how to write to a socket for the programming/scripting language of your choice if you don't know how to do that. Fairly straight forward.

I was going to just point you to my blog:
http://zmtrigger.blogspot.com/p/display-real.html

But when I went to that page, it was mostly empty. Apparently, I forgot to document the project I did using this feature. It is still in my head.

What I did was wire a DHT22 temp sensor to an esp8266. This chip is basically a mini adruino with builtin wifi.

It sends the temp to my zoneminder server every 15 seconds. Since I never uploaded the sketch to my blog, I pasted it below. You can see what I sent to the zm server socket. If your sensor is on the same machine running zmtrigger, then naturally the server ip becomes localhost. You could just write to the zmtrigger.sock file instead if you want.

Code: Select all

/*
* ESP8266_zmtrigger 
*
* Date of Last Revision: Jan 14, 2015
*
* Upon activation of GPIO2, this sketch sends a user defined command to a ZoneMinder 
* server running zmtrigger on port 6802.
* 
*/

#include <Arduino.h>
#include <DHT.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>

#define GPIO0 0
#define GPIO2 2
//DHT22 Sensor
#define DHTTYPE DHT22
#define DHTPIN  2

// ThingSpeak Settings
const char tsserver[]="184.106.153.149";
const String writeapi="thingspeak_api_key_here";
const int updateThingSpeakInterval = 60 * 1000; // 60 second interval at which to update ThingSpeak
const int updateZmTriggerInterval = 15 * 1000; // 15 second interval at which to update zoneminder

// DHT22 Settings
float humidity, temp_f;  // Values read from sensor
const int interval = 2000;              // interval at which to read sensor


ESP8266WiFiMulti WiFiMulti;
WiFiClient client;
ESP8266WebServer server(80);

// Define global variables
const char zmserver[] = "192.168.1.10";
const int port = 6802;
unsigned long previousMillis = 0;
unsigned long lastZMTriggerTime = 0;
unsigned long lastThngSpkTime = 0;
int throttle = 1000; // Don't send more than 1 message per this value in milliseconds
byte netFails = 0;
String TSerrorMsg = "No Thingspeak Error Reported";
String ZMerrorMsg = "No ZoneMinder Error Reported";
String TSsuccessMsg = "No Thingspeak Success Reported";
String ZMsuccessMsg = "No ZoneMinder Success Reported";
const String htmlHead = "<html>\r\n<head>\r\n<meta http-equiv='refresh' content='61'>\r\n</head>\r\n<body>\r\n";
const String htmlTail = "\r\n</body>\r\n</html>\r\n";
byte percentQ = 0;

// Initialize DHT sensor 
// NOTE: For working with a faster than ATmega328p 16 MHz Arduino chip, like an ESP8266,
// you need to increase the threshold for cycle counts considered a 1 or 0.
// You can do this by passing a 3rd parameter for this threshold.  It's a bit
// of fiddling to find the right value, but in general the faster the CPU the
// higher the value.  The default for a 16mhz AVR is a value of 6.  For an
// Arduino Due that runs at 84mhz a value of 30 works.
// This is for the ESP8266 processor on ESP-01 
DHT dht(DHTPIN, DHTTYPE, 11); // 11 works fine for ESP8266

/* 
*  zmTrigger expects commands to be in the following format:
*  
*<id>|<action>|<score>|<cause>|<text>|<showtext>
*
*<id>
*  is the id number or name of the ZM monitor.
*  
*<action>
*  Valid actions are 'on', 'off', 'cancel' or 'show' where
*  'on' forces an alarm condition on;
*  'off' forces an alarm condition off;
*  'cancel' negates the previous 'on' or 'off'.
*  The 'show' action merely updates some auxiliary text which can optionally
*  be displayed in the images captured by the monitor. Ordinarily you would
*  use 'on' and 'cancel', 'off' would tend to be used to suppress motion
*  based events. Additionally 'on' and 'off' can take an additional time
*  offset, e.g. on+20 which automatically 'cancel's the previous action
*  after that number of seconds.
*  
*<score>
*  is the score given to the alarm, usually to indicate it's
*  importance. For 'on' triggers it should be non-zero, otherwise it should
*  be zero.
*  
*<cause>
*  is a 32 char max string indicating the reason for, or source of
*  the alarm e.g. 'Relay 1 open'. This is saved in the 'Cause' field of the
*  event. Ignored for 'off' or 'cancel' messages.
*  
*<text>
*  is a 256 char max additional info field, which is saved in the
*  'Description' field of an event. Ignored for 'off' or 'cancel' messages.
*  
*<showtext>
*  is up to 32 characters of text that can be displayed in the
*  timestamp that is added to images. The 'show' action is designed to
*  update this text without affecting alarms but the text is updated, if
*  present, for any of the actions. This is designed to allow external input
*  to appear on the images captured, for instance temperature or personnel
*  identity etc.
*/

String zmtriggerShowText = "15|show||||";

void handle_root()
{
  String Msg = "Hello from the ESP8266!<br><br>This server responds to the following urls:<br>";
  Msg += "<a href=\"/gpio0\">/gpio0</a><br>";
  Msg += "<a href=\"/gpio2\">/gpio2</a><br>";
  Msg += "<a href=\"/temp\">/temp</a><br>";
  Msg += "<a href=\"/temp_val\">/temp_val</a><br>";
  Msg += "<a href=\"/humidity\">/humidity</a><br>";
  Msg += "<a href=\"/humidity_val\">/humidity_val</a><br>";
  Msg += "<a href=\"/error\">/error</a><br>";
  Msg += "<a href=\"/success\">/success</a><br>";
  Msg += "<a href=\"/status\">/status</a><br>";
  server.send(200, "text/html", htmlHead+Msg+htmlTail);
  delay(100);
}

bool gettemperature() {
  // Wait at least 2 seconds seconds between measurements.
  // if the difference between the current time and last time you read
  // the sensor is bigger than the interval you set, read the sensor
  // Works better than delay for things happening elsewhere also
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis >= interval) {
    // save the last time you read the sensor 
    previousMillis = currentMillis;   

    // Reading temperature for humidity takes about 250 milliseconds!
    // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
    humidity = dht.readHumidity();          // Read humidity (percent)
    temp_f = dht.readTemperature(true);     // Read temperature as Fahrenheit
  }
  return( isnan(humidity) || isnan(temp_f) );
}

void updateThingSpeak(String tsData) {
  // Create the client object here so it is destroyed after each update attempt
  WiFiClient client;
  
  if (client.connect(tsserver, 80)) {
    client.print("POST /update HTTP/1.1\n");
    client.print("Host: api.thingspeak.com\n");
    client.print("Connection: close\n");
    client.print("X-THINGSPEAKAPIKEY: " + writeapi + "\n");
    client.print("Content-Type: application/x-www-form-urlencoded\n");
    client.print("Content-Length: ");
    client.print(tsData.length());
    client.print("\n\n");
    client.print(tsData);

    if (client.connected()) { // We got a response from the server but we don't yet know what kind of response

      String Msg = client.readString();
      if ( Msg.substring(0,Msg.indexOf("\r\n")).indexOf(" 200 ") > -1 ) { // Check the message for an http resposne code of 200
        Serial.println();
        Serial.println("HTTP 200 Response received. Data Sent to ThingSpeak!");
        TSsuccessMsg = Msg;
      } else {
        Serial.println();
        Serial.println("ThingSpeak server returned an HTTP error code. Check HTTP://"+String(WiFi.localIP())+"/error for the response message.");
        TSerrorMsg = Msg;
      }     
      netFails = 0;
    } else {
      TSerrorMsg = "Timed out waiting for a response from Thingspeak server!";
      Serial.println();
      Serial.println(TSerrorMsg);
    }
  } else {
    TSerrorMsg = "Connection refused when connecting to Thingspeak!";
    Serial.println();
    Serial.println(TSerrorMsg);
    netFails++;
  }
  //Serial.println(client.readString());
  client.stop();
}

void WiFiStatus() {
    
    if (WiFi.RSSI() <= -100) {
      percentQ = 0;
    } else if (WiFi.RSSI() >= -50) {
      percentQ = 100;
    } else {
      percentQ = 2 * (WiFi.RSSI() + 100);
    }

    Serial.println("");
    Serial.println("WiFi Status");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.print("MAC address: ");
    Serial.println(WiFi.macAddress());
    Serial.print("SSID: ");
    Serial.println(WiFi.SSID());
    Serial.print("Signal Strength (%): ");
    Serial.println(percentQ);
}

void ChipInfo() {
  Serial.println();
  Serial.print("Flash Chip ID: ");
  Serial.println(ESP.getFlashChipId());
  Serial.print("Size of ESP8266 flash chip: ");
  Serial.println(ESP.getFlashChipRealSize());
  Serial.print("Flash Chip Speed: ");
  Serial.println(ESP.getFlashChipSpeed());
  Serial.print("Flash Chip Mode: ");
  Serial.println(ESP.getFlashChipMode());
  Serial.println();
}

void updateZMTrigger(String Cmd) {
  if (client.connect(zmserver, port)) {
    client.println(Cmd);

    if (client.connected()) {
      ZMsuccessMsg = "Zmtrigger ShowText Sent to ZoneMinder";
      Serial.println();
      Serial.println(ZMsuccessMsg);
      Serial.println(Cmd);
      netFails = 0;
    } else {
      ZMerrorMsg = "Timed out connecting to ZoneMinder server";
      Serial.println();
      Serial.println(ZMerrorMsg);
    }
  } else {
    ZMerrorMsg="Connection refused connecting to ZoneMinder server";
    Serial.println();
    Serial.println(ZMerrorMsg);
    netFails++;
  }
}

void setup() {
    // Configure our GPIO's as inputs
    pinMode(GPIO0, INPUT_PULLUP);
    pinMode(GPIO2, INPUT);
    
    Serial.begin(115200);
   // Serial.setDebugOutput(true);

    Serial.println("");

    for(uint8_t t = 4; t > 0; t--) {
        Serial.printf("[SETUP] WAIT %d...\n", t);
        Serial.flush();
        delay(1000);
    }

    WiFiMulti.addAP("ssid1", "password1");
    WiFiMulti.addAP("ssid2", "password2");

    ChipInfo();
    
    // Wait for connection
    Serial.print("Waiting for WiFi to Connect...");
    while (WiFiMulti.run() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.println("Connected!");
    server.on("/", handle_root);
  
    server.on("/gpio0", [](){  // url to display the current value of GPIO0
      server.send(200, "text/html", htmlHead+"Current value of GPIO0: "+String(digitalRead(GPIO0))+htmlTail);
    });

    server.on("/gpio2", [](){  // url to display the current value of GPIO2
      server.send(200, "text/html", htmlHead+"Current value of GPIO2: "+String(digitalRead(GPIO2))+htmlTail);
    });

    server.on("/temp", [](){  // url to display the temperature with html formatting
      server.send(200, "text/html", htmlHead+"Temperature (deg. F): "+String(temp_f,2)+htmlTail);
    });

    server.on("/temp_val", [](){  // url to display the raw temperature value only
      server.send(200, "text/plain", String(temp_f,2));
    });

    server.on("/humidity", [](){  // url to display the humidity with html formatting
      server.send(200, "text/html", htmlHead+"Humidity (%): "+String(humidity,1)+htmlTail);
    });

    server.on("/humidity_val", [](){  // url to display the raw humidity calue only
      server.send(200, "text/plain", String(humidity,1));
    });
    
    server.on("/error", [](){  // url to display the last reported error message with html formatting
      TSerrorMsg.replace("\r\n","<br>"); // replace new lines with html <br> for readability
      server.send(200, "text/html", htmlHead+"Last reported ZoneMinder error message:<br><br>"+ZMerrorMsg+"<br><br>Last reported ThingSpeak error message:<br><br>"+TSerrorMsg+htmlTail);
    });

    server.on("/success", [](){  // url to display the last reported success message with html formatting
      TSsuccessMsg.replace("\r\n","<br>"); // replace new lines with html <br> for readability
      server.send(200, "text/html", htmlHead+"Last reported ZoneMinder success message:<br><br>"+ZMsuccessMsg+"<br><br>Last reported ThingSpeak success message:<br><br>"+TSsuccessMsg+htmlTail);
    });

    server.on("/status", [](){  // url to display esp8266 status
      server.send(200, "text/html", htmlHead+"IP Address: "+WiFi.localIP()+"<br>"+
                                             "MAC address: "+WiFi.macAddress()+"<br>"+
                                             "SSID: "+WiFi.SSID()+"<br>"+
                                             "Signal Strength (%): "+percentQ+"<br>"+
                                             "Number of Consecutive Network Errors: "+netFails+"<br>"+
                                              htmlTail);
    });

    gettemperature();
    server.begin();
    Serial.println("");
    Serial.println("HTTP server started");
}

void loop() {
  
    server.handleClient();
    
    // Reset previousMillis when millis overflows back to zero
    if ( millis() - previousMillis < 0 ) {
      previousMillis = 0;
    }

    // Reset lastZMTriggerTime when millis overflows back to zero
    if ( millis() - lastZMTriggerTime < 0 ) {
      lastZMTriggerTime = 0;
    }

    // Reset lastThngSpkTime when millis overflows back to zero
    if (millis() - lastThngSpkTime < 0 ) {
      lastThngSpkTime = 0;
    }

    // Update ThingSpeak
    if (millis() - lastThngSpkTime > updateThingSpeakInterval) {
        if (!gettemperature()) {
            updateThingSpeak("field1="+String(temp_f,2)+"&field2="+String(humidity,1)+"&field3="+String(WiFi.RSSI()));
            Serial.println("Temperature (deg. F): "+String(temp_f,2));
            Serial.println("Humidity (%): "+String(humidity,1));
        } else {
            TSerrorMsg="Temperature or Humidity read failed. Skipping Thingspeak update.";
            Serial.println();
            Serial.println(TSerrorMsg);
        }
        lastThngSpkTime = millis();
    }

    // Update zmtrigger
    if (millis() - lastZMTriggerTime > updateZmTriggerInterval) {
        WiFiStatus();
        updateZMTrigger(zmtriggerShowText+" - "+String(temp_f,2)+" Deg. F");
        lastZMTriggerTime = millis();
    }

    if ( netFails >= 15 ) {
      Serial.println("Too many network failures. Restarting WiFi....");
      //delay(3000);
      WiFi.reconnect();
      netFails = 0;
      // ESP.reset();
      delay(3000);
    }

}
Sigge
Posts: 116
Joined: Mon May 02, 2016 8:14 am

Re: Add temperature to image

Post by Sigge »

Thanks! So I might substitute your weblink with a call to my thermometer binary?

You are not using a network socket?

It´s been 20 years since I last touched any code. Will have to read up on php.

The Perl part, is is complete? Is that what runs as zmtrigger? Or is is it creating a soccet to it?

Thanks again!
rockedge wrote:here is an example of 2 scripts that work together but of course they could be done completely in PERL or PHP and stand alone.

Code: Select all

<?php
// monck-usb-temp3.php //

   $monitor = $_REQUEST['mon'];
   $extTrigger = $_REQUEST['ext'];
   $json_string =  file_get_contents_curl("http://api.wunderground.com/api/xxxxxxxxxxx/geolookup/conditions/astronomy/q/CT/Shelton.json");
   $parsed_json = json_decode($json_string);
   $temperature_string = $parsed_json->{'current_observation'}->{'temperature_string'};
     
//   $cfgServer = "someZM.domain";
//   $cfgServer = "192.168.0.9";
   $cfgServer = "localhost";
   $cfgPort = 6802;
  

switch ($extTrigger) {
	case "on":
	 $extTriggerx = "$monitor|on+120|255|motion sensor|FrontDoor|motion sensor";
	  break;
	case "off":
	 $extTriggerx = "$monitor|off|0|||";
	  break;
	case "show":
	 $extTriggerx = "$monitor|show|255|current temp|troutcams|$temperature_string";
      break;
}
$conn_handle = fsockopen($cfgServer, $cfgPort);
 if(!$conn_handle) {
        echo "Connection failed ";
   exit();
    }else{
      //  echo "Connected <br>";
        fputs($conn_handle, $extTriggerx);
    }  
fclose($conn_handle);

   
function file_get_contents_curl($url) {
    $ch = curl_init();
     
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //Set curl to return the data instead of printing it to the browser.
    curl_setopt($ch, CURLOPT_URL, $url);
     
    $data = curl_exec($ch);
    curl_close($ch);
     
    return $data;

}
?>
PERL script ->

Code: Select all

#!/usr/bin/perl -w

use LWP::Curl;
        my $referer = 'http://192.168.0.14/zm';
        my $get_url = 'http://192.168.0.14/zm/monck-usb-temp3.php?mon=3&ext=show';
        my $get_url_two = 'http://192.168.0.9/zm/monck-usb-temp3.php?mon=2&ext=show';
    my $lwpcurl = LWP::Curl->new();
    
        my $content = $lwpcurl->get($get_url, $referer); 
        my $contenttwo = $lwpcurl->get($get_url_two, $referer);
rockedge
Posts: 1173
Joined: Fri Apr 04, 2014 1:46 pm
Location: Connecticut,USA

Re: Add temperature to image

Post by rockedge »

the php script communicates with the socket zmTrigger.pl creates. Also do not forget to check the options in ZoneMinder that in Options->Systems->OPT_TRIGGERS is checked and turned on. The PERL part was for the CRON job set up. There are several different ways to do it. The PERL script could be modified to call the weather server and send a command string through the socket to zmTrigger all by it self. The PHP code was added at the time for experimentation with remote motion detectors.
Sigge
Posts: 116
Joined: Mon May 02, 2016 8:14 am

Re: Add temperature to image

Post by Sigge »

Thanks again for all the help!

How do I identify my monitor? Getting this:
2017-01-16 23:41:27.051850 zmtrigger 4554 WAR Can't find monitor '2' for message '2|show|255|||22.9°C 73.2°F' zmtrigger.pl
rockedge
Posts: 1173
Joined: Fri Apr 04, 2014 1:46 pm
Location: Connecticut,USA

Re: Add temperature to image

Post by rockedge »

click on the monitor to open a view window, and look in the URL of the monitor as in the example:

Code: Select all

http://somedomain.us.to/zm/index.php?view=watch&mid=2
mid=2 is the monitor number in this example. So this would be monitor 2
check yours and replace the 2 with your monitor number in the PERL script where {mon=}
User avatar
knight-of-ni
Posts: 2404
Joined: Thu Oct 18, 2007 1:55 pm
Location: Shiloh, IL

Re: Add temperature to image

Post by knight-of-ni »

You can also hover over each monitor in the web console, w/o clicking, then look at the url that appears in the bottom, left corner of your browser
Visit my blog for ZoneMinder related projects using the Raspberry Pi, Orange Pi, Odroid, and the ESP8266
All of these can be found at https://zoneminder.blogspot.com/
Sigge
Posts: 116
Joined: Mon May 02, 2016 8:14 am

Re: Add temperature to image

Post by Sigge »

Thanks,

I'm still in php. Haven't come around to the cron job yet.

The monitor is 2. I have even renamed it "2".

I used argv() to get the number but now setting 2 directly in php. Still getting:

Can't find monitor '2' for message '2|show|255|||22.8 °C 73.0 °F'

Code: Select all

<?php
// monck-usb-temp3.php //

//   $monitor = $_REQUEST['mon'];
//   $extTrigger = $_REQUEST['ext'];
//   $json_string =  file_get_contents_curl("http://api.wunderground.com/api/xxxxxxxxxxx/geolookup/conditions/astronomy/q/CT/Shelton.json");
//   $parsed_json = json_decode($json_string);
//   $temperature_string = $parsed_json->{'current_observation'}->{'temperature_string'};


//$monitor = $argv[1];
//$extTrigger = $argv[2];
$temperature_string = system('temper-poll');
$temperature_string_array = explode(" ",$temperature_string);
unset($temperature_string_array[0]);
unset($temperature_string_array[1]);
$temperature_string_dirty = implode(" ",$temperature_string_array);
$temperature_string = str_replace(chr(194)," ",$temperature_string_dirty);

//   $cfgServer = "someZM.domain";
//   $cfgServer = "192.168.0.9";
   $cfgServer = "localhost";
   $cfgPort = 6802;


$extTriggerx = "2|show|255|||$temperature_string"; //was "$monitor
      echo "$temperature_string \n";
      echo "$extTriggerx \n";

$conn_handle = fsockopen($cfgServer, $cfgPort);
 if(!$conn_handle) {
        echo "Connection failed \n";
   exit();
    }else{
      //  echo "Connected <br>";
        fputs($conn_handle, $extTriggerx);
    }
fclose($conn_handle);

?>
Locked