Category Archives: IOT / Domoticz

A sms gateway

Sms form

Made a generic sms sender, for check_mk monitoring + sending password of accounts.
You can send sms by filling in a form, or using a url like:

http://smsgateway.local:8080/sms/0612341234/Message%20met%20spaties

It uses a Raspberry and a sim800L module.

Fritzing schematic
At work in a corner near a window (3d printed case)

Remove pin from simcard fix:

sudo minicom -b 115000 -o -D /dev/serial0

 AT+CPIN?
+CPIN: SIM PIN

AT+CPIN=0000
OK

AT+CLCK=”SC”,0,”0000″
OK

AT+CPIN?
+CPIN: READY 

Cron

@reboot sh /home/pi/launcher.sh 

Launcher

cat /home/pi/launcher.sh
#!/bin/bash
cd /home/pi
while true; do
/usr/bin/python newapi.py
done

newapi.py (uses flask)

import serial
import RPi.GPIO as GPIO     
import os, time
import sys

from flask import Flask, render_template, request
app = Flask(__name__)

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField
from wtforms.validators import DataRequired

from flask import render_template

app.config['SECRET_KEY'] = 'you-will-never-guess'

class FormForm(FlaskForm):
    telnumber = StringField('telnumber', validators=[DataRequired()])
    messagepart = TextAreaField('Text', render_kw={"rows": 5, "cols": 20})
    submit = SubmitField('Send Sms')
 
@app.route("/sms/<number>/<message>")
def action(number, message):
        num = number.encode() 
        mes = message.encode() 
	GPIO.setmode(GPIO.BOARD)   
	# Enable Serial Communication
	port = serial.Serial("/dev/serial0", baudrate=9600, timeout=1)

	# Transmitting AT Commands to the Modem
	# '\r\n' indicates the Enter key
	port.write('AT'+'\r\n')
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	#port.write('ATE0'+'\r\n')      # Disable the Echo
	#rcv = port.read(10)
	#print rcv
	#time.sleep(1)
	port.write('AT+CMGF=1'+'\r\n')  # Select Message format as Text mode
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	port.write('AT+CNMI=2,1,0,0,0'+'\r\n')   # New SMS Message Indications
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	port.write('AT+CSCS="GSM"'+'\r\n')   
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	# Sending a message to a particular Number
	port.write('AT+CMGS="'+num+'"\r\n')
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	port.write(mes+'\r\n')  # Message
	rcv = port.read(10)
	print rcv
	port.write("\x1A") # Enable to send SMS
	for i in range(10):
	    rcv = port.read(10)
	    print rcv
        return 'OK'
@app.route("/form")
def form():
    form = FormForm()
    return render_template('web.html', title='Web Sms', form=form)
@app.route('/data', methods = ['POST', 'GET'])
def data():
    if request.method == 'GET':
        return "The URL /data is accessed directly. Try going to '/form' to submit form"
    if request.method == 'POST':
  
        telnumber = request.form['telnumber']
        messagepart = request.form['messagepart']
        num = telnumber.encode() 
        mes = messagepart.encode()
	GPIO.setmode(GPIO.BOARD)   
	# Enable Serial Communication
	port = serial.Serial("/dev/serial0", baudrate=9600, timeout=1)
	# Transmitting AT Commands to the Modem
	# '\r\n' indicates the Enter key
	port.write('AT'+'\r\n')
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	#port.write('ATE0'+'\r\n')      # Disable the Echo
	#rcv = port.read(10)
	#print rcv
	#time.sleep(1)
	port.write('AT+CMGF=1'+'\r\n')  # Select Message format as Text mode
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	port.write('AT+CNMI=2,1,0,0,0'+'\r\n')   # New SMS Message Indications
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	port.write('AT+CSCS="GSM"'+'\r\n')   
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	# Sending a message to a particular Number
	port.write('AT+CMGS="'+num+'"\r\n')
	rcv = port.read(10)
	print rcv
	time.sleep(1)
	port.write(mes+'\r\n')  # Message
	rcv = port.read(10)
	print rcv
	port.write("\x1A") # Enable to send SMS
	for i in range(10):
	    rcv = port.read(10)
	    print rcv
       
        return '<a href="/form">Nog een SMS sturen</a>'
@app.route("/checkmk")
def checkmk():
        # Enable Serial Communication
        port = serial.Serial("/dev/serial0", baudrate=9600, timeout=1)
   
        port.write('AT'+'\r\n')
        rcv1 = port.read(30)
        time.sleep(1)

        port.write('AT+CPAS'+'\r\n')
        rcv2 = port.read(30)        
        time.sleep(1)
    
        port.write('AT+CGREG?'+'\r\n')
        rcv3 = port.read(30)
        time.sleep(1)
    
        port.write('AT+CGATT?'+'\r\n')
        rcv4 = port.read(30)
        time.sleep(1)
    
        port.write('AT+CSQ'+'\r\n')
        rcv5 = port.read(30)
        time.sleep(1)

        return 'OK of niet' + rcv1 + rcv2 + rcv3 + rcv4 + rcv5
if __name__ == "__main__":
   app.run(host='0.0.0.0', port=8080, debug=True)

Bluetooth Macro Keyboard for Photo Management

Arduino Bluetooth Photo view marco keyboard
  • Cursor pad on the left
  • 1 till 5 (see below)
  • star + 1-5, rates 1 till 5 stars
  • outline star – removes rating
  • bookmark + 1-5, color marks image
  • Triangle – start slideshow
  • zoom-in, rotate CCW, reset zoom, rotate CW, zoom-out
  • previous image, open image, fullscreen, exit fullscreen to manager and next image

I’ve used a esp32 with 18650 battery holder.

I still have to 3d print a case 🙂

Code:

#include <BleConnectionStatus.h>
#include <BleKeyboard.h>
#include <KeyboardOutputCallbacks.h>

#define DEBUG 0micro joystick
#define STAR 16
#define FLAG 17
#define COL1 18
#define COL2 19
#define COL3 21
#define COL4 22
#define COL5 23
#define COL6 25
#define ROW1 26
#define ROW2 27
#define ROW3 32
#define ROW4 33

int flagstate = 0;
int starstate = 0;
int row1state = 0;
int row2state = 0;
int row3state = 0;
int row4state = 0;
int col1state = 0;
int col2state = 0;
int col3state = 0;
int col4state = 0;
int col5state = 0;
int col6state = 0;

int colstate = 1;

BleKeyboard bleKeyboard;

void setup() {
#ifdef DEBUG
  Serial.begin(9600);
#endif
  bleKeyboard.begin();
  pinMode(STAR, INPUT_PULLUP);
  pinMode(FLAG, INPUT_PULLUP);
  pinMode(ROW1, INPUT_PULLUP);
  pinMode(ROW2, INPUT_PULLUP);
  pinMode(ROW3, INPUT_PULLUP);
  pinMode(ROW4, INPUT_PULLUP);
  pinMode(COL1, OUTPUT);
  pinMode(COL2, OUTPUT);
  pinMode(COL3, OUTPUT);
  pinMode(COL4, OUTPUT);
  pinMode(COL5, OUTPUT);
  pinMode(COL6, OUTPUT);
}
void loop() {
#ifdef DEBUG
  Serial.print("Colstate : ");
  Serial.print(colstate);
  Serial.print('\n');
#endif

  if (colstate == 1) {
    digitalWrite(COL1, LOW);
    digitalWrite(COL2, HIGH);
    digitalWrite(COL3, HIGH);
    digitalWrite(COL4, HIGH);
    digitalWrite(COL5, HIGH);
    digitalWrite(COL6, HIGH);
  }
  if (colstate == 2) {
    digitalWrite(COL1, HIGH);
    digitalWrite(COL2, LOW);
    digitalWrite(COL3, HIGH);
    digitalWrite(COL4, HIGH);
    digitalWrite(COL5, HIGH);
    digitalWrite(COL6, HIGH);
  }
  if (colstate == 3) {
    digitalWrite(COL1, HIGH);
    digitalWrite(COL2, HIGH);
    digitalWrite(COL3, LOW);
    digitalWrite(COL4, HIGH);
    digitalWrite(COL5, HIGH);
    digitalWrite(COL6, HIGH);
  }
  if (colstate == 4) {
    digitalWrite(COL1, HIGH);
    digitalWrite(COL2, HIGH);
    digitalWrite(COL3, HIGH);
    digitalWrite(COL4, LOW);
    digitalWrite(COL5, HIGH);
    digitalWrite(COL6, HIGH);
  }
  if (colstate == 5) {
    digitalWrite(COL1, HIGH);
    digitalWrite(COL2, HIGH);
    digitalWrite(COL3, HIGH);
    digitalWrite(COL4, HIGH);
    digitalWrite(COL5, LOW);
    digitalWrite(COL6, HIGH);
  }
  if (colstate == 6) {
    digitalWrite(COL1, HIGH);
    digitalWrite(COL2, HIGH);
    digitalWrite(COL3, HIGH);
    digitalWrite(COL4, HIGH);
    digitalWrite(COL5, HIGH);
    digitalWrite(COL6, LOW);
  }
  delay (100);
  flagstate = digitalRead(FLAG);
  starstate = digitalRead(STAR);
  row1state = digitalRead(ROW1);
  row2state = digitalRead(ROW2);
  row3state = digitalRead(ROW3);
  row4state = digitalRead(ROW4);
#ifdef DEBUG
  Serial.print("Rowstates : ");
  Serial.print(row1state);
  Serial.print(row2state);
  Serial.print(row3state);
  Serial.print(row4state);
  Serial.print('\n');
#endif
  // ROW1 = UP,DOWN,LEFT,RIGHT
  if (bleKeyboard.isConnected() && colstate == 1) {
    // UP
#ifdef DEBUG
    Serial.print("Up Pressed ");
    Serial.print('\n');
#endif

    if (row1state == 0) {
      bleKeyboard.press(KEY_UP_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
    // DOWN
    if (row2state == 0) {
      bleKeyboard.press(KEY_DOWN_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
    // LEFT
    if (row3state == 0) {
      bleKeyboard.press(KEY_LEFT_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
    // RIGHT
    if (row4state == 0) {
      bleKeyboard.press(KEY_RIGHT_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
  }
  // ROW2 = (1),(star),ZOOMIN,PREVIOUS
  if (bleKeyboard.isConnected() && colstate == 2) {
    // 1 - star
    if (row1state == 0 && starstate == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('1');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // 1 - flag
    if (row1state == 0 && flagstate == 0) {
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press('1');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // NO ROWSTATE2

    // zoom in
    if (row3state == 0) {
      bleKeyboard.press('+');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // PREVIOUS
    if (row4state == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press(KEY_LEFT_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
  }
  // ROW3 = (2),unstar,CCWrotate,open
  if (bleKeyboard.isConnected() && colstate == 3) {
    // 2 - star
    if (row1state == 0 && starstate == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('2');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // 2 - flag
    if (row1state == 0 && flagstate == 0) {
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press('2');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // unstar
    if (row2state == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('0');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // CCW rotate
    if (row3state == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press(KEY_LEFT_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
    // open
    if (row4state == 0) {
      bleKeyboard.press(KEY_RETURN);
      delay (100);
      bleKeyboard.releaseAll();
    }
  }

  // ROW4 = (3),(flag),zoom,fullscreen
  if (bleKeyboard.isConnected() && colstate == 4) {
    // 3 - star
    if (row1state == 0 && starstate == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('3');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // 3 - flag
    if (row1state == 0 && flagstate == 0) {
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press('3');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // NO ROWSTATE2

    // zoom reset
    if (row3state == 0) {
      bleKeyboard.press('*');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // fullscreen
    if (row4state == 0) {
      bleKeyboard.press('f');
      delay (100);
      bleKeyboard.releaseAll();
    }
  }

  // ROW5 = (4),unflag,CWrotate,exit
  if (bleKeyboard.isConnected() && colstate == 5) {
    // 4 - star
    if (row1state == 0 && starstate == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('4');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // 4 - flag
    if (row1state == 0 && flagstate == 0) {
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press('4');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // unflag
    if (row2state == 0) {
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press('0');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // CW rotate
    if (row3state == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press(KEY_RIGHT_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
    // exit
    if (row4state == 0) {
      bleKeyboard.press(KEY_ESC);
      delay (100);
      bleKeyboard.releaseAll();
    }
  }

  // ROW6 = (5),slideshow,zoomout,next
  if (bleKeyboard.isConnected() && colstate == 6) {
    // 5 - star
    if (row1state == 0 && starstate == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('5');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // 5 - flag
    if (row1state == 0 && flagstate == 0) {
      bleKeyboard.press(KEY_LEFT_ALT);
      bleKeyboard.press('5');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // slideshow
    if (row2state == 0) {
      bleKeyboard.press(KEY_ESC);
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press('s');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // zoom out
    if (row3state == 0) {
      bleKeyboard.press('-');
      delay (100);
      bleKeyboard.releaseAll();
    }
    // next
    if (row4state == 0) {
      bleKeyboard.press(KEY_LEFT_CTRL);
      bleKeyboard.press(KEY_RIGHT_ARROW);
      delay (100);
      bleKeyboard.releaseAll();
    }
  }
  colstate++;
  if (colstate == 7) {
    colstate = 1;
  }
}
Keyboard layout

Hex Dimmer

Control a dimmer using a hex wireless box.

Parts

  • Wemos Mini
  • MPU6050 – Gyroscope Module
  • 10k Resistor
  • TP4056 – Battery Charger Module
  • Mini Battery
  • Wireless Charger

Put the box flat on the table to switch off.
When you put it on one side, it will controll your lights brightness.
20,40,60,80 and 100%, just by rotating and putting it down on its sides.

3D printed case

Schematics (without the wireless charging part)

Wireless part

Node-Red Controll part (source below)

Nice to have’s :
Arduino-sleep mode, wakeup with a movement sensor.

Arduino Code

#include <Wire.h>
//#include <SPI.h>
#include <PubSubClient.h>
//#include <string.h>
//#include <stdio.h>
#include <ESP8266WiFi.h>

// Wifi settings
const char* ssid = "xxxxxx";
const char* password = "xxxxxxxxxx";
const char* mqtt_server = "10.1.0.17";

// I2C address of the MPU-6050 - 0x68 or 0x69 if AD0 is pulled HIGH
const int MPU = 0x68;
int16_t AcX, AcY, AcZ, GyX, GyY, GyZ;
float gForceX, gForceY, gForceZ, rotX, rotY, rotZ;

// Wifi MAC address
byte mac[]= {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };

WiFiClient espClient;
IPAddress ip;
PubSubClient mqttClient(espClient);

// IP address of your MQTT server
const char* server = "10.1.0.17";
//const char* outTopic = "test/";
//const char* server = "iot.eclipse.org";

void dataReceiver(){
  Wire.beginTransmission(MPU);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU,14,true);  // request a total of 14 registers
  AcX = Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
  AcY = Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  GyX = Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY = Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ = Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
  processData();
}

void processData(){
  gForceX = AcX / 16384.0;
  gForceY = AcY / 16384.0; 
  gForceZ = AcZ / 16384.0;
  
  rotX = GyX / 131.0;
  rotY = GyY / 131.0; 
  rotZ = GyZ / 131.0;
}

void debugFunction(int16_t AcX, int16_t AcY, int16_t AcZ, int16_t GyX, int16_t GyY, int16_t GyZ){
  // Print the MPU values to the serial monitor
  Serial.print("Accelerometer: ");
  Serial.print("X="); Serial.print(gForceX);
  Serial.print("|Y="); Serial.print(gForceY);
  Serial.print("|Z="); Serial.println(gForceZ);  
  Serial.print("Gyroscope:");
  Serial.print("X="); Serial.print(rotX);
  Serial.print("|Y="); Serial.print(rotY);
  Serial.print("|Z="); Serial.println(rotZ);
}

void reconnect() {
  // Loop until we're reconnected
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (mqttClient.connect("arduinoClient")){
      Serial.println("connected");
    } 
    else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
//      Wait 5 seconds before retrying
      delay(1000);
    }
  }
}

void setup(){
  Serial.begin(9600);

    setup_wifi();
  
  Wire.begin(0,2);
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

  mqttClient.setServer(server, 1883);
  
//  Ethernet.begin(mac);
//  ip = Ethernet.localIP();
  
  Serial.println(ip);  
  Serial.println(server);
  //delay(1500);
}

char* init(float val){
  
  char buff[100];

  for (int i = 0; i < 100; i++) {
      dtostrf(val, 4, 2, buff);  //4 is mininum width, 6 is precision
  }
   return buff;

}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void dataAcc(){

  char mpu6050X[100]= "";   
  strcat(mpu6050X,init(gForceX));

  char mpu6050Y[100]= "";   
  strcat(mpu6050Y,init(gForceY));

  char mpu6050Z[100]= "";   
  strcat(mpu6050Z,init(gForceZ));

  // accelerometer - "topic, mpu6050"
  mqttClient.publish("AcX/", mpu6050X);
  mqttClient.publish("AcY/", mpu6050Y);
  mqttClient.publish("AcZ/", mpu6050Z);
//  mqttClient.publish(outTopic, "text to send via mqtt");
}


void dataGy(){

  char mpu6050X[100]= "";
  strcat(mpu6050X,init(rotX));

  char mpu6050Y[100]= "";
  strcat(mpu6050Y,init(rotY));

  char mpu6050Z[100]= "";
  strcat(mpu6050Z,init(rotZ));
  
  // gyroscope - "topic, mpu6050"
  mqttClient.publish("GyX/", mpu6050X);
  mqttClient.publish("GyY/", mpu6050Y);
  mqttClient.publish("GyZ/", mpu6050Z);
//  mqttClient.publish(outTopic, "text to send via mqtt");
}

void loop(){
  dataReceiver();
  debugFunction(AcX,AcY,AcZ,GyX,GyY,GyZ);

  if (!mqttClient.connected()) {
    reconnect();
  }

  mqttClient.loop(); 

  dataAcc();
  dataGy();

  delay(2000);
}

Nodered Flow

[
    {
        "id": "7550958a.b29dec",
        "type": "mqtt in",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "topic": "hex/x",
        "qos": "2",
        "broker": "8c74c5f6.9a7a48",
        "x": 270,
        "y": 100,
        "wires": [
            [
                "d251dd79.5700d"
            ]
        ]
    },
    {
        "id": "e84b0a1.18096f8",
        "type": "mqtt in",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "topic": "hex/y",
        "qos": "2",
        "broker": "8c74c5f6.9a7a48",
        "x": 270,
        "y": 180,
        "wires": [
            [
                "9c27bc8f.b62dd"
            ]
        ]
    },
    {
        "id": "6a1a0d8d.b3e754",
        "type": "mqtt in",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "topic": "hex/z",
        "qos": "2",
        "broker": "8c74c5f6.9a7a48",
        "x": 270,
        "y": 260,
        "wires": [
            []
        ]
    },
    {
        "id": "2d2a911a.6af3fe",
        "type": "ui_gauge",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "group": "d43a9f25.6c874",
        "order": 23,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "gauge",
        "label": "units",
        "format": "{{value}}",
        "min": "0",
        "max": "100",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "x": 1010,
        "y": 120,
        "wires": []
    },
    {
        "id": "d251dd79.5700d",
        "type": "function",
        "z": "a0126a6a.9c70b8",
        "name": "Get level from box",
        "func": "var my=msg.payload;\nmsg.payload = {};\nif (my == 0.85){\n    msg.payload=20;\n    return msg;\n}\nelse if (my == 0.86){\n    msg.payload=20;\n    return msg;\n}\nelse if (my == 0.87){\n    msg.payload=20;\n    return msg;\n}\n\nelse if (my == 0.03){\n    msg.payload=40;\n    return msg;\n}\nelse if (my == 0.02){\n    msg.payload=40;\n    return msg;\n}\n\nelse if (my == 3.17){\n    msg.payload=60;\n    return msg;\n}\nelse if (my == 3.18){\n    msg.payload=60;\n    return msg;\n}\n\nelse if (my == 0.04){\n    msg.payload=80;\n    return msg;\n}\nelse if (my == 0.05){\n    msg.payload=80;\n    return msg;\n}\n\nelse if (my == 3.95){\n    msg.payload=100;\n    return msg;\n}\nelse if (my == 3.96){\n    msg.payload=100;\n    return msg;\n}\nelse {\n    return msg;\n    \n}\n",
        "outputs": 1,
        "noerr": 0,
        "x": 510,
        "y": 120,
        "wires": [
            [
                "ecd746cc.fce348",
                "8721e902.45d8b8",
                "39c8f1ac.86affe"
            ]
        ]
    },
    {
        "id": "39c8f1ac.86affe",
        "type": "function",
        "z": "a0126a6a.9c70b8",
        "name": "Set Living spots level (idx 5)",
        "func": "var level = Number(msg.payload);\nmsg.payload = {};\nmsg.payload.idx = 5;\nmsg.payload.switchcmd = (\"Set Level\");\nmsg.payload.command = \"switchlight\";\nmsg.payload.level = level;\nreturn msg;  ",
        "outputs": 1,
        "noerr": 0,
        "x": 820,
        "y": 260,
        "wires": [
            [
                "bc0d6507.1d7748"
            ]
        ]
    },
    {
        "id": "bc0d6507.1d7748",
        "type": "mqtt out",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "topic": "domoticz/in",
        "qos": "",
        "retain": "",
        "broker": "8c74c5f6.9a7a48",
        "x": 1080,
        "y": 260,
        "wires": []
    },
    {
        "id": "9c27bc8f.b62dd",
        "type": "function",
        "z": "a0126a6a.9c70b8",
        "name": "Flat or standing up",
        "func": "var mya=msg.payload;\nmsg.payload = {};\nif (mya < -3.80){\n    flow.set(\"levely\",1);\n    msg.payload  = \"plat\";\n        }\nelse {\n    flow.set(\"levely\",2);\n    msg.payload  = \"rechtop\";\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 450,
        "y": 200,
        "wires": [
            [
                "ecd746cc.fce348"
            ]
        ]
    },
    {
        "id": "ecd746cc.fce348",
        "type": "debug",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 640,
        "y": 340,
        "wires": []
    },
    {
        "id": "8721e902.45d8b8",
        "type": "function",
        "z": "a0126a6a.9c70b8",
        "name": "Gate for level ",
        "func": "\nvar x = msg.payload;\ny = flow.get(msg.payload);\nvar y = flow.get('levely') || 0;\n\nif (y == 1){\n    msg.payload = {};\n        msg.payload = 0;\n} else {\n    msg.payload = x;\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 810,
        "y": 120,
        "wires": [
            [
                "2d2a911a.6af3fe",
                "da72437e.88376"
            ]
        ]
    },
    {
        "id": "da72437e.88376",
        "type": "debug",
        "z": "a0126a6a.9c70b8",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "x": 890,
        "y": 200,
        "wires": []
    },
    {
        "id": "8c74c5f6.9a7a48",
        "type": "mqtt-broker",
        "z": "",
        "name": "10.1.0.17",
        "broker": "10.1.0.17",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "15",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    },
    {
        "id": "d43a9f25.6c874",
        "type": "ui_group",
        "z": "",
        "name": "Control",
        "tab": "739541e2.18396",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false
    },
    {
        "id": "739541e2.18396",
        "type": "ui_tab",
        "z": "",
        "name": "7inch",
        "icon": "dashboard",
        "order": 1,
        "disabled": false,
        "hidden": false
    }
]

Mattermost Mqtt Flag Integration

UPDATE 20231020 – Via USB Serial (also schematic for below integration)
https://www.henriaanstoot.nl/2023/10/20/thunderbird-mail-notification-flag-via-usb/

Using a servo which is MQTT controlled, and a slash command in Mattermost, i can be notified by friends and colleages.

Flag, and monitor-stand are 3D printed.

ESP is a simple wemos mini.

Note the slash command, M5 stick flow is unrelated!

Hacking a hikvision doorcam

I don’t like cloud enabled devices, except if it is my own cloud!

I bought a Hikvision doorcam, when i researched the diverse doorcams where was a hikvision which could use a existing doorchime.

When i got mine in, it did not have this feature.
Damn what to do!

Where where several solutions i could think off:

  • Use as is .. a no-go for me
  • Offload SSL on a proxy and try to reverse engineer the https communication
  • Or a hardware solution

I looked at the electronics and tried to find out how things where connected and where things where you could make use of.
I soldered some wires to a little print where the pushbutton was located.
Now i could read the button press, video was easily captured by using a RTSP port.

Does it work? … yes, but i’m not happy about it

Video playback in Zoneminder or VLC

rtsp://admin:secretpassword@hikvisionip:554/Streaming/Channels/101

A solution i used before for a generic doorbell

Just read the gpio pin with Arduino or raspberry.
A generic optocoupler will work, just check your resistor.

Domotemp – domoticz temperature floorplan

For my home automation i’m using Home Assistant and Domoticz.
All 433Mhz Temperature/Humidity are connected to a RFXcom device on two domoticx instances. (Master slave construction)

I’ve made a php script and a bash script to draw all sensors on a floorplan in realtime.

There is also a cron running which takes a snapshot of the generated image every 5 minutes.
These images are being converted to MP4 and animated GIF to have a timelapse with all temperatures displayed on a floorplan.

An obfuscated view of the floorplan

The circles are where sensors are placed.
Colors are from blue till red, representing the heat.
In the center is the measured temperature value.

The (shortened) PHP script: (index.php)

<?php
header('Content-type: image/png');
// This is the floorplan empty ..
$png_image = imagecreatefrompng('plattegrondenmerge.png');
$white = ImageColorAllocate($png_image, 0, 0, 0);

$max = 40;
$min = -10;

// living
// getstate is a bash script (see below which gets the values from domoticz using curl) 
// 840 is the domoticz idx
$temp840 = shell_exec('./getstate 840');
// A gray circle will be drawn if the temperature age is > 500 seconds
$age = shell_exec('./new.sh 18 840 500 >/dev/null || echo gray');
// location of circle
$start_x = 950;
$start_y = 760;
$line = $temp840 + 10;
// get x-th line from colors
$colorfromlist = shell_exec("tail --lines=$line ./colors2 | head -1");
if(strpos($age, "gray") !== false){
 $colorfromlist = "128,128,128";
};
$colors = explode(",", $colorfromlist);
$color = imagecolorallocatealpha($png_image, $colors[0], $colors[1], $colors[2], 50);
// draw circle
imagefilledellipse ($png_image, $start_x, $start_y, 175, 175, $color);
$start_x = $start_x - 70;
$start_y = $start_y + 15;
// add text
imagettftext($png_image, 24, 0, $start_x, $start_y, $white, './verdana.ttf', $temp840);

// winecellar
$temp840 = shell_exec('./getstate 839');
$age = shell_exec('./new.sh 18 839 700 >/dev/null || echo gray');
$start_x = 560;
$start_y = 840;
$line = $temp840 + 10;
$colorfromlist = shell_exec("tail --lines=$line ./colors2 | head -1");
if(strpos($age, "gray") !== false){
 $colorfromlist = "128,128,128";
};
$colors = explode(",", $colorfromlist);
$color = imagecolorallocatealpha($png_image, $colors[0], $colors[1], $colors[2], 50);
imagefilledellipse ($png_image, $start_x, $start_y, 175, 175, $color);
$start_x = $start_x - 70;
$start_y = $start_y + 15;
imagettftext($png_image, 24, 0, $start_x, $start_y, $white, './verdana.ttf', $temp840);

// ETC ETC

imagesavealpha($png_image, TRUE); 

  imagepng($png_image);
  imagedestroy($png_image);
?>


getstate bash script
(gets the temperature from domoticz instance1 given an idx)

#!/bin/bash
curl -s --connect-timeout 2 --max-time 5 "http://ip-domoticz1:8080/json.htm?type=devices&rid=$1" | egrep "Temp|Humid" | awk '{print $3 }' | cut -f1 -d\. | grep -v \" | tr -d "\n\r" | sed s/,/%\ /g | awk '{ print $2"° "$1 }'

new.sh script gets the age of the reading from
domoticz1 or domoticz1
Usage: ./new.sh <domoticz-last-numer-ip> <idx> <maxageinseconds>

#!/bin/bash
## server idx time
now=$(date +%s)
lastupdate=$(curl -s -i -H "Accept: application/json" "http://192.168.1.$1:8080/json.htm?type=devices&rid=$2" |  grep LastUpdate | cut -f4 -d\" )
#echo $lastupdate
seen=$(date -d "$lastupdate" +%s)
#echo $seen
#echo "$(( $now - $seen))"
difftime="$(( $now - $seen))"
if [ $difftime -gt $3 ] ; then
  echo "WARN : too old - $difftime seconds"
  exit 1
else
  echo "OK : $difftime seconds"
  exit 0
fi

colors2 – a list of colors representing the temperature
red -> green -> blue

255,0,0
255,10,0
255,20,0
255,30,0
255,40,0
255,60,0
255,70,0
255,80,0
255,90,0
255,100,0
255,120,0
255,130,0
255,140,0
255,150,0
255,160,0
255,180,0
255,190,0
255,200,0
255,210,0
255,220,0
255,240,0
255,250,0
253,255,0
215,255,0
176,255,0
101,255,0
62,255,0
23,255,0
0,255,16
0,255,54
0,255,131
0,255,168
0,255,208
0,255,244
0,228,255
0,196,255
0,180,255
0,164,255
0,148,255
0,132,255
0,100,255
0,84,255
0,68,255
0,50,255
0,34,255
0,2,255
0,0,255
1,0,255
2,0,255
3,0,255
5,0,255

Crontab and gif/mp4 generators

# crontab
5 * * * * root /scripts/domotemp/crontemp.sh

# crontemp.sh
# stores an image with a date.
cd /www/webdir/domotemp
wget https://mydomoticzweb/domotemp/ -O $(date +%Y%m%d%H).png >/dev/null 2>/dev/null 1>/dev/null


# rest scripts
mkdir -p embed
# below adds the time to the image
ls 2020*png | sort -n -k1 | while read ; do hour=$(echo $REPLY | cut -c9,10) ; convert -pointsize 80 -fill black -draw 'text 1650 100 '\"$hour:00\"'' -resize 960x540 $REPLY  embed/$REPLY ;done
# convert to gif
convert $(ls embed/2020*png | sort -n -k1) animation.gif
# convert to mp4
ffmpeg -f image2 -r 24 -pattern_type glob -i '*.png'   -vcodec libx264 -profile:v high444 -refs 16 -crf 0 -preset ultrafast -vf scale=1920:1080 domotemp.mp4

Colleage at work brings a Freecom Internet Radio

Big mistake 😉 It a Linux ARM system with Busybox

Less do some searching on the web.
Ah .. enable telnet

Open in a browser:

http://192.168.1.x/admin/cgi-bin/debug.cgi

I was controlling my colleage’s music from the next room

Enable telnet:

Connect and install Pong:

telnet 192.168.1.x (username root)
# wget http://agilo.acjs.net/files/mppong/setup.sh
# chmod 755 setup.sh
# ./setup.sh
# ./start.sh
# ./start.sh musicpal # For the normal player

Links i have to look into:
https://www.qemu.org/docs/master/system/arm/musicpal.html

Garbage display

I wanted to know when trash is being collected. So i made a dual led thingy.

Version without lid .. just to test

When we lived in Utrecht, the website which mentions when, what is collected was easy .. like a API

We live in Hilversum now, so i had to write a scraper.

UPDATE : Script not working anymore. Site has been changed, and I’m not willing to run a headless browser (phantomjs) to scrape the info

See below my new solution

Script to set leds

#!/bin/bash
set -x

if [[ $# -eq 0 ]] ; then
curl  http://10.1.0.46/control?cmd=NeoPixel,1,0,0,0
curl  http://10.1.0.46/control?cmd=NeoPixel,2,0,0,0
exit 0
fi


set -x
if [ $1 = "GFT" ] ;then led1="150,0,0" ;fi
if [ $1 = "Rest" ] ;then led1="150,150,150" ;fi
if [ $1 = "Paper" ] ;then led1="0,0,150" ;fi
if [ $1 = "Plastic" ] ;then led1="75,191,0" ;fi
if [ $1 = "Textiel" ] ;then led1="191,191,191" ;fi
if [ $1 = "Kerstbomen" ] ;then led1="191,0,0" ;fi

if [ "w$2" = "wgft" ] ;then led2="255,0,0" ;fi
if [ "w$2" = "wRest" ] ;then led2="150,150,150" ;fi
if [ "w$2" = "wpapier" ] ;then led2="0,0,255" ;fi
if [ "w$2" = "wplastic" ] ;then led2="100,255,0" ;fi
if [ "w$2" = "wTextiel" ] ;then led2="191,191,191" ;fi
if [ "w$2" = "wKerstbomen" ] ;then led2="191,0,0" ;fi

if [[ $# -eq 1 ]] ; then
led2=$led1
fi
curl  http://10.1.0.46/control?cmd=NeoPixel,1,$led1
curl  http://10.1.0.46/control?cmd=NeoPixel,2,$led2

Scraper ( calls setleds and mqtt )

#!/bin/bash
cd /home/pi/
./afvalleds
curl -s https://inzamelkalender.gad.nl/adres/1222HP:39 > /tmp/afvalcurl
today=$(date +%F)
tomorrow=$(date --date="next day" +%F)
for f in petfles-blik-drankpak-pmd.svg appel-gft.svg kliko-grijs-rest.svg doos-karton-papier.svg shirt-textiel.svg kerstboom.svg ; do
p1=$(cat /tmp/afvalcurl | grep -i  -A2 "$f" | grep class | cut -f2 -d\> | cut -f1 -d\< | cut -f2- -d" ")
p2=$(echo $p1 | sed s/mei/may/g | sed s/okt/oct/g )
p3=$(date --date="$p2" +%F)
dater=$p3
now=$(date -d $(date --date="1 days ago" +%F) +%s)
p3epoch=$(date --date="$p3" +%s)
diff=$(expr $p3epoch - $now)
if [ $diff -lt 0 ] ; then
                dater=$(date -d "$p3 1 year" +%F)
fi
whatdate=$(echo $f | sed s/petfles-blik-drankpak-pmd.svg/Plastic/g |sed s/appel-gft.svg/GFT/g | sed s/kliko-grijs-rest.svg/Rest/g | sed s/doos-karton-papier.svg/Paper/g | sed s/shirt-textiel.svg/Textiel/g | sed s/kerstboom.svg/Kerstbomen/g)
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/$whatdate" -m "$dater"
if [ "w$today" == "w$dater" ]; then
alltoday="$alltoday $whatdate"
fi
if [ "w$tomorrow" == "w$dater" ]; then
alltomorrow="$alltomorrow $whatdate"
fi
done
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/Today" -m "$alltoday"
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/Tomorrow" -m "$alltomorrow"
./afvalleds $alltomorrow

Utrecht scraper

#!/bin/bash
set -x
curl -s "http://json.mijnafvalwijzer.nl/?method=postcodecheck&postcode=3543eh&huisnummer=72&" > /tmp/afv
cat /tmp/afv | python -mjson.tool > /tmp/afv.json
today=$(cat /tmp/afv.json | grep -A1 $(date +%Y-%m-%d) | grep nameType | cut -f4 -d\" | paste -sd "/" -)
today=$(echo $today | sed s/\ /,/g)

tomorrow=$(cat /tmp/afv.json | grep -A1 $(date -d "+1 days" +%Y-%m-%d) | grep nameType | cut -f4 -d\" | paste -sd "/" -)
tomorrow=$(echo  $tomorrow" | sed s/\ /,/g)

cat /tmp/afv.json | grep -B1 nameType | grep -B1 gft | cut -f4 -d\" | grep 202 > /tmp/gft.afv
cat /tmp/afv.json | grep -B1 nameType | grep -B1 papier | cut -f4 -d\" | grep 202 > /tmp/papier.afv
cat /tmp/afv.json | grep -B1 nameType | grep -B1 plastic | cut -f4 -d\" | grep 202 > /tmp/plastic.afv
cat /tmp/afv.json | grep -B1 nameType | grep -B1 kerst | cut -f4 -d\" | grep 202 > /tmp/kerst.afv
(
(
for f in $(seq 1 31) ; do
 dater=$(date -d "+$f days" +%Y-%m-%d)
 grep $dater /tmp/gft.afv > /dev/null &&  echo "$dater GFT" | head -1
done ) | head -1

(
for f in $(seq 1 31) ; do
 dater=$(date -d "+$f days" +%Y-%m-%d)
 grep $dater /tmp/papier.afv > /dev/null && echo "$dater Papier"  | head -1
done ) | head -1

(
for f in $(seq 1 31) ; do
 dater=$(date -d "+$f days" +%Y-%m-%d)
 grep $dater /tmp/plastic.afv > /dev/null && echo "$dater Plastic"  | head -1
done ) | head -1

(
for f in $(seq 1 31) ; do
 dater=$(date -d "+$f days" +%Y-%m-%d)
 grep $dater /tmp/kerst.afv > /dev/null && echo "$dater Kerstboom"  | head -1
done ) | head -1

) | sort -n -k1 | sed s/\ /,/g > /tmp/afv.sorted
gft=$(cat /tmp/afv.sorted | grep GFT | cut -f1 -d,)
papier=$(cat /tmp/afv.sorted | grep Papier | cut -f1 -d,)
plastic=$(cat /tmp/afv.sorted | grep Plastic | cut -f1 -d,)

mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/Today" -m "$today " 
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/Tomorrow" -m "$tomorrow " 
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/Papier" -m "$papier" 
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/GFT" -m "$gft" 
mosquitto_pub -h 127.0.0.1 -p 1883 -t "afvalwijzer/Plastic" -m "$plastic" 

Update new solution

My home assistant has a hacs addon installed which does the heavy lifting for me.
Why not use this info?

I wrote about NR and HA here

So getting the info for NR to parse:

I only need to use the lightblue node bottom right!

It is the current state node, inject a timestamp every day to get new values.
Parse this and send using Mqtt to my Led display thingy

Action LSC Alarm/Siren

Update 20221208 – removed internals

I’ve modded several LSC devices, most of them i could flash with esp-easy or tasmota.
Why mod it? Because it uses the cloud .. i’d like to keep control myself.
Just connect/solder a USB to TTL Converter UART Module like below.
(See other posts)

But this alarm was different, i ended up removing the chip and replace it with a ESP12.

Warning .. loud! .. Yes almost xmas
A WR3 is almost like a ESP-12

So now i had to figure out which GPIO pins and how to control them.

#1/bin/bash
# Flashed ESP Easy on this one
# When i did this, 2019, you needed version 2.1-beta1 
# GPIO 4 controls LED
sleep 10
curl http://10.1.1.251/control?cmd=GPIO,4,1
sleep 1
curl http://10.1.1.251/control?cmd=GPIO,4,0
sleep 1
curl http://10.1.1.251/control?cmd=GPIO,4,1
sleep 2
curl http://10.1.1.251/control?cmd=GPIO,4,0
sleep 5
# Sending rtttl ringtone
curl "http://10.1.1.251/control?cmd=rtttl,5:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6"



alarm sound
 curl "http://10.1.1.251/control?cmd=rtttl,5:d=4,o=5,b=160:2g,2c6,2g,2c6,2g,2c6,2g,2c6"
pager
 curl "http://10.1.1.251/control?cmd=rtttl,5:d=4,o=5,b=160:8d6,16p,2d6,16p,8d6,16p,2d6,16p,8d6,16p,2d6"

Update 20221208 – removed internals

Removed my old hack and replaced it with a Wemos D1.
Added a LED
Next to do .. add a amplifier using a LM356/358