Go Down

Topic: EHZ Zähler auslesen (Read 3574 times) previous topic - next topic

Nobi

Hallo Zusammen,

ich habe es geschafft mit dem Lesekopf von Volkszähler.org meinen EMH EHZ Zähler auszulesen. Leider schaffe ich es nicht die Zahlen zu formatieren, die Ausgabe ist im Moment wie folgt:
10693877
3928
sollte aber 1069.3877 kWh und 392.8W sein. Wenn jemand mehr Ahnung hat als ich, wäre ich auch für eine effizientere Umwandlung von Hex nach INT bzw. Float dankbar.

Vielen Dank im voraus!

Gruss
Norbert

Code: [Select]
/**
* Copyright (c) 2012 Volker Wegert <ehzy@volker-wegert.de>
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   Volker Wegert - initial implementation
*
*/
#include <stdarg.h>
#include <stdio.h>
#include <avr/pgmspace.h>
/***************************************************************************************************
* CONSTANTS
***************************************************************************************************/

/**
* size of the SML message buffer = meximum number of bytes that can be received
*/
#define SML_MSG_BUFFER_SIZE    500

/**
* maximum time to wait for the end of a data packet received via IR
*/
#define SERIAL_READ_TIMEOUT_MS 100

/***************************************************************************************************
* MESSAGES
***************************************************************************************************/

/**
* maximum length of a message (entire text after variable substitution!)
*/
#define MAX_MESSAGE_LENGTH        50

/**
* message numbers
*/
#define MSG_PROGRAM_STOPPED        0
#define MSG_NEXT_FILENAME          1
#define MSG_NO_FILE_NAMES_LEFT     2
#define MSG_INIT_HARDWARE          3
#define MSG_INIT_SD_CARD           4
#define MSG_INIT_SD_CARD_ERROR     5
#define MSG_BYTE_READ              6
#define MSG_BUFFER_OVERFLOW        7
#define MSG_SERIAL_OVERFLOW        8 
#define MSG_INVALID_HEADER         9
#define MSG_FILE_OPEN_FAILED      10
#define MSG_FILE_WRITTEN          11
#define MSG_FREE_MEMORY           12
#define MSG_NUM_BYTES_READ        13

/**
* actual message texts - caution, adapt MAX_MESSAGE_LENGTH if required!
*                               ....+....1....+....2....+....3....+....4....+....5
*/
prog_char msgText00[] PROGMEM = "Program stopped.";
prog_char msgText01[] PROGMEM = "Next output file name is '%s'";
prog_char msgText02[] PROGMEM = "No more file names left";
prog_char msgText03[] PROGMEM = "Initializing Hardware...";
prog_char msgText04[] PROGMEM = "Initializing SD Card...";
prog_char msgText05[] PROGMEM = "SD Card initialization failed";
prog_char msgText06[] PROGMEM = "Read byte %02hhu from IR receiver";
prog_char msgText07[] PROGMEM = "Message buffer overflow";
prog_char msgText08[] PROGMEM = "Serial buffer overflow";
prog_char msgText09[] PROGMEM = "Invalid escape sequence";
prog_char msgText10[] PROGMEM = "Unable to open output file";
prog_char msgText11[] PROGMEM = "%u bytes of data written to file '%s'";
prog_char msgText12[] PROGMEM = "%u bytes of memory available";
prog_char msgText13[] PROGMEM = "%u bytes read";

/**
* table for easier access to the message texts
*/
PROGMEM const char *msgTextTable[] = {   
  msgText00,
  msgText01,
  msgText02,
  msgText03,
  msgText04,
  msgText05,
  msgText06,
  msgText07,
  msgText08,
  msgText09,
  msgText10,
  msgText11,
  msgText12,
  msgText13
};

/***************************************************************************************************
* GLOBAL VARIABLES (YUCK!)
***************************************************************************************************/
/**
* the global buffer to store the SML message currently being read
*/
unsigned char buffer[SML_MSG_BUFFER_SIZE];
unsigned char Aktuell[4];
float aktuell;

/***************************************************************************************************
* SUBROUTINES
***************************************************************************************************/

/**
* printMessage - reads a message text from the PROGMEM, performs variable substitution and
*                writes the resulting text to the serial console. Use MSG_* constants for
*                messageNumber.
*/
void printMessage(int messageNumber, ...) {
  va_list args;
  char format[MAX_MESSAGE_LENGTH];
  char buffer[MAX_MESSAGE_LENGTH];
  va_start(args, messageNumber);
  strncpy_P(format, (char*)pgm_read_word(&(msgTextTable[messageNumber])), MAX_MESSAGE_LENGTH);
  vsnprintf(buffer, MAX_MESSAGE_LENGTH, format, args);
  Serial.println(buffer);
  va_end(args);
}

void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes
{
  char tmp[16];
  for (int i=0; i<length; i++) {
    sprintf(tmp, "0x%.2X",data[i]);
    Serial.print(tmp);
    Serial.print(" ");
  }
}


/**
* stop - stops program execution in a controlled fashion (endless loop).
*/
void stop() {
  printMessage(MSG_PROGRAM_STOPPED);
  while(1);
}


/**
* isValidHeader - returns true if the global message buffer begins with a valid SML escape sequence.
*/
inline boolean isValidHeader() {
  return ((buffer[0] == 0x1b) ||
    (buffer[1] == 0x1b) ||
    (buffer[2] == 0x1b) ||
    (buffer[3] == 0x1b) ||
    (buffer[4] == 0x01) ||
    (buffer[5] == 0x01) ||
    (buffer[6] == 0x01) ||
    (buffer[7] == 0x01));
}

/***************************************************************************************************
* MAIN ROUTINES
***************************************************************************************************/

/**
* setup - initialization routine run once after the device is powered up.
*/
void setup() {
  // set the serial console speed
  Serial.begin(9600);
  // set the pin configuration
  printMessage(MSG_INIT_HARDWARE);
}
/**
* loop - main program, run in an endless loop
*/
void loop() {
  unsigned int nextBufferPosition = 0;
  unsigned long lastReadTime = 0;

  // clear the message buffer
  memset(buffer, 0, sizeof(buffer));
  // wait until actual data is available
  while (!Serial.available());

  // keep reading data until either the message buffer is filled or no more data was
  // received for SERIAL_READ_TIMEOUT_MS ms
  lastReadTime = millis();
  while (millis() - lastReadTime < SERIAL_READ_TIMEOUT_MS) {
    if (Serial.available()) {
      buffer[nextBufferPosition] = Serial.read();
      lastReadTime = millis();
      if (nextBufferPosition >= SML_MSG_BUFFER_SIZE) {
        printMessage(MSG_BUFFER_OVERFLOW);
        return;
      }
      nextBufferPosition += 1;
    }
  }

  // check the header
  printMessage(MSG_NUM_BYTES_READ, nextBufferPosition + 1);
  if (!isValidHeader()) {
    // not a valid header - notify the user...
    printMessage(MSG_INVALID_HEADER);
    // ...and empty the receiver buffer (wait for the end of the current data stream
    while (Serial.available() > 0) {
      Serial.read();
    }
  }
  else {
    unsigned char Stand[4];
    //PrintHex8(buffer,313);
    Serial.println();
    for(int i=0;i<4;i++){
      Stand[i]=(buffer[143+i]);
      Aktuell[i]=buffer[205+i];
    }
    PrintHex8(Stand,4);
    Serial.println();
    unsigned long stand =(int(buffer[144])*65536);
    unsigned long stand2=(int(buffer[145])*256);
    unsigned long stand3=(int(buffer[146]));
    unsigned long summe=stand+stand2+stand3;
    Serial.println(summe);

    unsigned long Verb =(int(Aktuell[1])*65536);
    unsigned long Verb2=(int(Aktuell[2])*256);
    unsigned long Verb3=(int(Aktuell[3]));
    unsigned long verbrauch=Verb+Verb2+Verb3;
    Serial.println(verbrauch);
    //    Serial.println(stand);
    //    aktuell=*((float*)Aktuell);
    //    Serial.println(aktuell,DEC);
    //    for(int i=0;i<4;i++){
    //      Serial.print(buffer[143+i],HEX);
    //    }
    //  Serial.println();
    //     for(int i=0;i<5;i++){
    //      Serial.print(buffer[205+i],HEX);
    //     }
    //    Serial.println();
    // PrintHex8(Aktuell,4);
    //    Serial.println();

  }
}

Udo Klein

Ich würde die Zahlen mit "/" bzw. "%" in einen Vor- und einen Nachkommateil zerlegen und dann den Dezimalpunkt dazwischen ausgeben.
Check out my experiments http://blog.blinkenlight.net

Nobi

Auf die Idee mit Teilen war ich auch schon gekommen, aber ich hatte ganz vergessen, dass ich die Nachkommastellen wieder hinzufügen muss  :smiley-roll:.
Hat jemand noch eine Idee zu der HEX-> Int Umwandlung, ich bin mir sicher, dass man das noch optimieren kann?!

Gruss

Udo Klein

Klar gibt es da Ideen. Aber schreib doch bitte zuerst einmal genau was Du von einer HEX->INT Konvertierung erwartest. Also wie sieht der Input aus und wie der gewünschte Output? Und vor allem: warum ist Dir Deine aktuelle Lösung nicht gut genug?

Und bitte keien Spec der Art: "so wie ich es jetzt implementiert" habe. Das zwingt jeden Leser Deinen Code zu verstehen obwohl Du selber damit nicht zufrieden bist.
Check out my experiments http://blog.blinkenlight.net

Nobi

Da hast du recht, wenn man selber weiß was man reinbekommt und raus haben will, übersieht man leicht, dass die anderen Nutzer da nicht so drin sind.
Ich bekomme jetzt serielle Hexwerte (char Array) in folgendem Format rein:
62 1E Unit 1E = Wh
52 FF Scale
56 000014FDEF Zählerstand, berechnet mit Scaler und Unit (14FDEF = 137572,7 kWh!)
die Umwandlung mache ich ja durch Multiplikation:
    unsigned long stand =(int(buffer[144])*65536);
    unsigned long stand2=(int(buffer[145])*256);
    unsigned long stand3=(int(buffer[146]));
    unsigned long summe=stand+stand2+stand3;
das hochwertigste Byte lasse ich schon weg, da die Multiplikation ja einen Overflow geben würde.

Was ich suche wäre eine direkte Umwandlung von den Hexwerten in den Kommawert, ohne die Zwischenvariablen.

Gruss
Norbert

Udo Klein

Ich würde diese Methode nehmen: http://www.willemer.de/informatik/cpp/union.htm. Nur solltest Du für ein unsigned long eben 4 Bytes benutzen, also etwa so:

Code: [Select]

union {
   struct {
       unsigned char b0;
       unsigned char b1;
       unsigned char b2;
       unsigned char b3;
   } part;
   unsigned long value;
} converter;

  converter.part.b3 = 0;  
  converter.part.b2 = buffer[144];
  converter.part.b1 = buffer[145];
  converter.part.b0 = buffer[146];

 unsigned long result = converter.value;
}


Wenn die Werte andersrum im Speicher stehen würden, also niedrigstes Byte in buffer[144] und weiterhin eine 0 in buffer[147], dann könntest Du den Wert auch direkt auslesen, also etwas so:
Code: [Select]

unsigned long result = *((unsigned long *)(& buffer[144]));


gelesen werden. Falls Du das hochwertigstes Byte mitbenutzt gibt es bei der Methode garantiert keinen Overflow ;)
Check out my experiments http://blog.blinkenlight.net

Nobi

Guten morgen,

genau sowas hab ich gesucht, ich hab es jetzt so gemacht:
Code: [Select]
    for(int i=3;i>=0;i--){
      Stand[3-i]=(buffer[143+i]);
      Aktuell[3-i]=buffer[204+i];
    }
unsigned long result = *((unsigned long *)(& Stand[0]));
Serial.println(result);
double Result = result/10000;
Serial.println(Result);
int Rest = int(result);
Rest = Rest%10000;
Serial.println(Rest);


Mein Problem ist jetzt nur noch die Ausgabe mit Modulo, weil da immer Werte rauskommen, die ich mir nicht erklären kann. Könntest du mir da noch einen Tip geben? Es ist mir auch wichtig den Wert wirklich als Kommazahl zu haben, da dies die Nachverarbeitung erheblich vereinfacht.

Gruss und schonmal vielen Dank

Norbert

MaFu

Quote from: Nobi link=topic=108865.msg819336#msg819336 date=1339137563

Code: [Select]
...
int Rest = int(result);
Rest = Rest%10000;
Serial.println(Rest);


Mein Problem ist jetzt nur noch die Ausgabe mit Modulo, weil da immer Werte rauskommen, die ich mir nicht erklären kann.


Wundert Dich das, wenn Du ein unsigned long in ein vorzeichenbehaftetes int zwängst?  ;)
_______
Manfred

Nobi

aber der Wert ist doch so klein, dass er in dem INT noch Platz finden sollte, oder?  :~

MaFu

Auf dem ATmega ist ein int nur 16 Bit groß und geht somit nur bis ca. 32000.
_______
Manfred

Nobi

aber der Rest ist doch <10000, damit müsste er doch passen

MaFu

Code: [Select]
int Rest = int(result);
Rest = Rest%10000;


Du kopierst aber vorher den Wert von result nach Rest und machst erst danach die Division.
Probiers mal so:
Code: [Select]
int Rest = unsigned long(result)%10000;
_______
Manfred

Nobi

Jetzt läuft's so wie ich es mir gewünscht hab, der Arduino ließt die Daten ein und stellt sie per HTML Seite zur Verfügung.
Ich hab's mal hier reingestellt, das können bestimmt auch andere noch gebrauchen.
Vielen Dank für die Unterstützung!

Gruss
Norbert

shh30_83

Hallo zusammen,

ich will das Projekt nun auch mal angehen.
Habe das script geladen und die IP angepasst, leider gibt mein Mega nichts aus, webseite kann nicht gefunden werden.
Des Weiteren habe ich noch die Frage an welcher stelle im Script die Daten eingelesen werden?
Ich habe einen Seriellen Lesekopf!

Danke und Grüße
Simon

Nobi

Hi,

fangen wir mal vorne an, welchen Zähler hast Du, welchen Lesekopf?
Bekommst du etwas angezeigt, wenn du nur eine einfache HTML Seite ausgibst (siehe Beispiele)?
Ist der Arduino pingbar?

Gruss
Norbert

Go Up