Gå til innhold
  • Bli medlem
Støtt hjemmeautomasjon.no!

Forbruksmåler med nodemcu, neopixel og OLED-display


Anbefalte innlegg

Quote

Jeg savnet den gamle forbruksmåleren på kjøkkenveggen som viser strømforbruket. Selv om jeg kan sjekke forbruket som leses via HAN porten via telefonen/nettside, så var ikke dataene like tilgjengelige. Har derfor laget en prototype med hjelp av en nodemcu, neopixel LED-ring og en 0.96 tommers OLED skjerm.

 

Hver LED illustrer 1kW i strømtrekk. Antall watt vises på første linje. Spotpris for aktuell time blir hentet fra Tibber APIet og vises i andre linje. Estimert kostnad per time med nåværende strømtrekk inkluderer nettleiedelen på 42,61øre/kWh til BKK.

 

Oppdateringsfrekvensen er hvert 2.sekund (hver gang AMS måleren sender ut "Act_Pow_P_Q1_Q4"). Backend systemer består av følgende:

  1. Raspberry Pi leser modbus til USB adapter er koblet til HAN porten ved hjelp av test_rx / han-port-1.15 nevnt i "Lesing av HAN - The Easy Way (TM) - WIP" tråden.
  2. Output fra test_rx blir matet til MQTT ved hjelp av et python-script. Node-Red henter spotpris fra Tibber i starten av hver time og sender til MQTT.
  3. NodeMCU heter dataene fra MQTT kjører beregninger på kostnad og viser dette på OLED-skjermen og via neopixlene.

 

Todo:

  • Printe ut et nytt kabinett med høy WAF slik at fruen blir glad. Prototypen ble tegnet i en fei.
  • Lage presence detection på boksen slik at OLED skjermen ikke blir brent ut, alternativt bytte ut skjermen (stor e-paper/e-ink skjerm står på ønskelisten).
  • Forsøke å skrive om koden til hente sanntidsforbruk direkte fra Tibber APIet slik at andre kan lage sitt eget watt-o-meter ved hjelp av en Nodemcu/ESP uten noe annet enn en Tibber pulse.

Om noen vet om noe kode som henter ut sanntidsmålingene fra tibber og som kan kjøres på en esp/nodemcu så send meg gjerne i den retningen. 

watt-o-meter.jpg.429010c4b97bf871b5ef8a748eb17d3c.jpg

watt-o-meter2.jpg.70a7f01c55d290ce6baea7953bb1a7df.jpg

 

Byggetråd for forbruksmåler / display for lettere å visualisere strømforbruket i tilfelle noen har interesse av å lage noe lignende. Tips for videreutvikling og hjelp/kommentarer til kode blir satt pris på. Jeg skal få lagt opp flere bilder og beskrivelse av komponenter og koblingsdiagram.

 

 

Backend oppsettet mitt bærer litt preg av at veien blir til mens du går. Det er ikke nødvendig å bruke både python-scriptet og Node-red, det går fint an å hente spotprisen via python , eller benytte seg av node-red for å lese modbus-til-usb adapteret som enkelte i "Lesing av HAN - The easy way" tråden har gjort. Men nåværende oppsett er stabilt, så jeg kommer ikke til å skrive det om enda. 

 

image.png.67719098ba5f3e103d881b6283565f66.png

1) Node-red blir brukt til å hente spotpris fra Tibber, inject-noden er et cron basert og trigges 2 ganger i timen (hvert 30 minutter, i tilfelle første trigger feiler).   

// Cron utrykket trigger en "Post to tibber" med følgende innhold: 
msg.payload = { "query" : "{viewer {homes {currentSubscription {priceInfo {current {total startsAt }}}}}}" };
msg.headers = {
    "Authorization" : "Bearer din-tibber-token-aabbccdd-1234",
    "Content-Type" : "application/json"
};
return msg;

 

2) Funksjon "set spotpris as global variable": Setter spotprisen til en global variabel ("strompris") i node-red for bruk i en annen flow.  

// Lagrer/setter spotprisen fra Tibber i en global variabel som kan brukes i MQTT flow senere.
var newMsg = {payload: msg.payload.data.viewer.homes[0].currentSubscription.priceInfo.current.total
};

global.set("strompris",newMsg.payload);
return newMsg;

 

 

image.png.0733132bfc993fbd0047ca7dbee93c93.png

 

// Henter forbruksdata via MQTT på "ams/Act_Pow_P_Q1_Q4",
// disse dataene kommer somsagt på influx line protocol format
// hos meg.
// Eksempel: "Act_Pow_P_Q1_Q4,name=Act_Pow_P_Q1_Q4 value=2047"
//
// Jeg ønsker kun verdien, så jeg splitter på '='.
// Denne kombineres med spotprisen som blir hentet ut fra
// global "strompris" variabel i node-red og sendes ut
// til ny MQTT topic som NodeMCU/forbruksmåleren bruker.
// Jeg brukte ';' som delimiter mellom power og spotpris.

// Innholdet i "Combine AMS power and spotpris" funksjonen
var values = msg.payload.split('=');
var power = values[2];
var spotpris = global.get("strompris");
msg = {payload: power +";" + spotpris};

return msg;

 

Forbruket leses via HAN porten fra AMS ved hjelp av test_rx og en modbus til USB adapter. Jeg har brukt følgende script fra brukeren Berland her på forumet (Håper det går greit å legger ut modifisert versjon her). I tillegg til å sende JSON til MQTT så ønsket jeg å sende influx line protocol syntax til MQTT for enkelt å sende til influxdb ved bruk samme python script/Node-red eller Telegraf. 

 

# Modified from https://www.hjemmeautomasjon.no/forums/topic/2873-lesing-av-han-the-easy-way-tm-wip/?do=findComment&comment=39623
import subprocess
import paho.mqtt.client as mqtt
import json
import sys
import requests
from requests.exceptions import HTTPError

# If the binary is still active for some reason, kill it
subprocess.call("killall test_rx >/dev/null", shell=True)

proc = subprocess.Popen(['./test_rx','-n'], stdout=subprocess.PIPE)

def on_connect(client, userdata, flags, rc):
    pass

def on_disconnect(client, userdata, rc=0):
    sys.exit(1)

client = mqtt.Client()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.connect('mqttserver', 1883)
print "Connected"

cumulativejson = ''
cumulativeInfluxLine = ''
try:
    for line in iter(proc.stdout.readline, ''):
        line = line.rstrip()
        cumulativejson += line
        if line[-1:] == '}':
            try:
                amsJson = json.loads(cumulativejson)
                client.publish('power/ams', cumulativejson)
                for key in amsJson:
                    value = amsJson[key]
                    if isinstance(value, int) or isinstance(value, float):
                        cleanValue = str(value).replace(" ","")
                        influxLineData = '%(key)s,name=%(key)s value=%(cleanValue)s' % locals()
                        mqttTopic = 'ams/' + key
                        client.publish(mqttTopic, influxLineData)
                cumulativejson = ''
            except UnicodeDecodeError:
                # Sometimes we get something strange
                # on the serial line, just ignore it.
                cumulativejson = ''
                continue
except Exception as e:
    print(e)
    proc.terminate()
    sys.exit(1)
    # Let systemd restart us

 

Koden som kjøres på NodeMCUen kan også sees her: https://github.com/Olavae/mqtt-power-meter/blob/main/MqttPowerSpotMeter.ino

 

// 
// Power usage display with current spot price using OLED and Neopixel. 
// Uses a NodeMCU board with wifi to recieve a single MQTT message on
// the form powerUsage;spotprice using ';' as a delimiter/IFS then 
// splits the message into two variables to visualise them.
// 

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>

// Following 4 are needed for SSD1306 oled display
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define PIXELPIN D3   // Datapin for neopixel
#define NUMPIXELS 24  // Number of neopixels 
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)

//OLED display (Uses GPIO5 (D1) (SCL) and GPIO4 (D2) (SDA) as standard
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Neopixels / ws2812 compatible leds
Adafruit_NeoPixel pixels(NUMPIXELS, PIXELPIN, NEO_GRB + NEO_KHZ800);

// Update these with values suitable for your network.
const char* ssid = "Your-Wifi-Network";
const char* password = "wifi-passphrase";
const char* mqtt_server = "10.0.0.5"; // Your MQTT broker

// Set your variable for "nettleie" to your power company. 
const float nettleie = 42.61;               // BKK nettleie variabel del, Price in øre.
const char* powerPriceTopic = "strompris";  // Your MQTT topic where you publish "powerUsage;spotpris"
const char* delimiter = ";";

WiFiClient espClient;
PubSubClient client(espClient);

unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];


void setup_wifi() {

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

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

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

  randomSeed(micros());

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

void callback(char* topic, byte* payload, unsigned int length) {
  char incomming[length];
  
  for (int i=0; i<length; i++){
    incomming[i] = (char)payload[i];  
  }

  // Split incomming message into Power and spot price with delimiter
  // First token (Power)
  char* incToken = strtok(incomming,delimiter);
  char* incommingPower = incToken;

  // Second token (Spot price)
  incToken = strtok(NULL,delimiter);
  char* incommingSpot = incToken;

  // Convert into easier to manage data types.
  long power = atol(incommingPower);
  float kronepris = atof(incommingSpot);
  
  int pris = round((kronepris*100)); // Go from Krone to øre.

  // Calculate price per hour based on current power usage
  float timepris = (((kronepris*100) + nettleie)*power)/100000;

  // Number of LEDS which will light up, round to nearest kW.
  int powerInt = (int)(round((float)(power/1000.0)));

  // Light up Neopixel, colours suits my normal power usage.
  // Green is OK ( power < 5kW )
  // Yellow is hmm ( 5kW < power < 10kW )
  // Red gets expensive fast ( Power > 10kW )
  pixels.clear();
  for(int i=0;i<powerInt;i++) {
    if (i<=4) {
      pixels.setPixelColor(i, pixels.Color(0,50,0));
    } else if ((i >= 5) && (i <= 9)) {
      pixels.setPixelColor(i, pixels.Color(50,50,0));
    } else if (i >= 10) {
      pixels.setPixelColor(i, pixels.Color(50,0,0));  
    }
  }
  pixels.show(); // Light up pixels

  // OLED display, 3 lines with medium/large text. 
  //  --------
  // | Power  |
  // | Spot   |
  // | perHour|
  //  --------
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,10);

  display.print(power);
  display.println(" W");
  
  display.print(pris);
  display.println(" 0re");  // Fake a norwegian Ø, haven't gotten Adafruit_gfx to display non ascii chars.

  display.print(timepris);
  display.println(" kr/t");
  
  display.display();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // ... and resubscribe
      client.subscribe(powerPriceTopic); // Topic where you publish your meter values
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  Serial.begin(115200);

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64 (Default address) can also be 0x3D
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  
  display.display();
  delay(300); // Wait and show adafruit splashscreen, they provided a nice library
  // Clear the buffer
  display.clearDisplay();
  
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {

   if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

 

Komponenter

  • NodeMCU kompatibelt utviklerkort, mitt er basert på ESP8266, har ikke testet med ESP32 versjoner. NodeMCU bruker 3.3V, men gir deg 5V på VIN-pin om du benytter deg av USB-kontakten for å gi kortet strøm. Neopixel krever 5V. OLED skjerm bruker 3.3V. 
  • Neopixel WS2812b kompatibel ring. https://www.kjell.com/no/produkter/elektro-og-verktoy/arduino/tilbehor/luxorparts-adresserbar-rgb-led-ring-24x-led-p87933
  • SSD1306 0.96 tommers OLED display 128x64. 
  • 470 ohm motstand mellom NodeMCU og neopixel datapin. (Ligger loddet under krympestrømpen på bildene nedenfor).
  • 7 koblingskabler.
  • Kabinettboks (Ikke tegnet enda, kan legge med STL-fil for 3dprinting når jeg er fornøyd).
     

fritzing-forbruksmaaler.thumb.png.a172bef568e7e14ab82d2443f65dd19d.png

 

Jeg var utålmodig og fant et sett fra Kjell & co som inneholdt OLED-skjermen og 24-leds-neopixel, mostander og koblingskabler. Det er rimeligere å kjøpe fra andre steder. Merk at settet ikke inneholder NodeMCU kortet som trengs.  https://www.kjell.com/no/produkter/elektro-og-verktoy/arduino/arduino-pakke/playknowlogy-startpakke-for-arduino-eksperiment-p88211

 

Jeg har startet på arbeidet med å skrive om koden for å bruke websockets, (testet ulike bibliotek, men denne her virker mest lovende til nå. https://github.com/gilmaimon/ArduinoWebsockets) for å hente forbruket direkte fra Tibber APIet i håp om å lage et stand-alone display for dem som bruker Tibber pulse, men som ikke ønsker å kjøre noe backend systemer hjemme med MQTT oppsett.

 

IMG_1362.thumb.JPEG.260d2c9b84205e38e66e1d7d1fcaca04.JPEGIMG_1364.thumb.JPEG.ccdfac30b8bbca1c2f2e425c4a29b9ec.JPEG

PS: Jord-kabel mellom SSD1306/OLED display og NodeMCU er brun, jeg slurvet her, men den er tegnet inn i rett farge (svart) i koblingsdiagrammet.  

Endret av Olav E
La til mer info. Skrivefeil
  • Like 5
  • Thanks 2
Lenke til kommentar
Del på andre sider

Bli med i samtalen

Du kan publisere innhold nå og registrere deg senere. Hvis du har en konto, logg inn nå for å poste med kontoen din.

Gjest
Skriv svar til emnet...

×   Du har limt inn tekst med formatering.   Lim inn uten formatering i stedet

  Du kan kun bruke opp til 75 smilefjes.

×   Lenken din har blitt bygget inn på siden automatisk.   Vis som en ordinær lenke i stedet

×   Tidligere tekst har blitt gjenopprettet.   Tøm tekstverktøy

×   Du kan ikke lime inn bilder direkte. Last opp eller legg inn bilder fra URL.

×
×
  • Opprett ny...

Viktig informasjon

Vi har plassert informasjonskapsler/cookies på din enhet for å gjøre denne siden bedre. Du kan justere dine innstillinger for informasjonskapsler, ellers vil vi anta at dette er ok for deg.