Power/ Usage sensor - multi channel - local display


  • Hero Member

    It is a power meter device which displays: current power, daily usage and total usage of upto 12 S0 meters and sends all the information through the MySensors network. The current sketch displays power used and generated from the different channels and uses a rotary encoder to browse the displays and adjust the (total values).upload-47e80693-5e06-4b90-9e6b-c435212fc038 (also added message communication to the display from the controller (V_TEXT).
    With reference to my earlier project I reworked the whole thing to be more reliable and added functionality. The basic concept is the same:

    1 Arduino doing the time critical measurement and counting of the pulses and time, sending the measurements via a one-wire serial in JSON
    upload-e7fcad43-2ae0-4762-8c25-7d1bc3c71e2b
    including "patch panel" for connection to the S0 meters.
    upload-d07ff26c-a941-4923-9324-7b7842627af2

    Another arduino reading the JSON string and performing the tasks like display and sending the values over the MySensors network.
    upload-bbac86e4-758d-4794-8740-05e22b54be8d
    The grey and white wire connect to the "measurement" Arduino.

    some displays:
    upload-762b5e1e-dada-4314-ae95-7d6fa6c1b2aa
    adjusting the values

    upload-b1afc999-309b-4b5a-9170-c4998e2c7627
    reset the daily values


  • Hero Member

    the code for the measurement device::

    //---------------------------------------------------------------------------------------------
    // Arduino Pulse Counting Sketch for counting pulses from up to 12 pulse output meters.
    // uses direct port manipulation to read from each register of 6 digital inputs simultaneously
    //
    // Licence: GNU GPL
    // part of the openenergymonitor.org project
    //
    // Author: Trystan Lea
    // AWI: adapted to produce JSON and error checking at client side.
    //---------------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------------
    // Pulse Counting Class - could be placed in seperate library...
    //---------------------------------------------------------------------------------------------
    class PulseOutput
    {
    public:                                 		 //AWI: access to all
      boolean pulse(int,int,unsigned long);                  //Detects pulses, in pulseLib.ino
      unsigned long rate( unsigned long );                   //Calculates rate 
    
      unsigned long count;                                   //pulse count accumulator
      unsigned long countAccum;                              //pulse count total accumulator for extended error checking (only resets at startup)
      unsigned long prate;                                   //pulse width in time 
      unsigned long prateAccum;                              //pulse rate accumulator for calculating mean.
    
    private:
      boolean ld,d;                                          //used to determine pulse edge
      unsigned long lastTime,time;                           //used to calculate rate
    };
    
    //---------------------------------------------------------------------------------------------
    // Variable declaration
    //---------------------------------------------------------------------------------------------
    
    //CHANGE THIS TO VARY RATE AT WHICH PULSE COUNTING ARDUINO SPITS OUT PULSE COUNT+RATE DATA
    //time in seconds;
    const unsigned long printTime = 1000000;	// delay between serial outputs in us (one meter at a time)  
    const int lastMeter = 7 ; 	 	// is number of meters + 1
    
    byte curMeter = 2 ;				// current meter for serial output, wraps from 2 to lastMeter
    
    //---------------------------------------------------------------------------------------------
    PulseOutput p[14];            //Pulse output objects
    
    int a,b,la,lb;                //Input register variables
    
    unsigned long ltime, time;    //time variables
    
    void setup()
    {
     // take care: pull-up inverses state! line 155
    	//setup input pins here with pull_up, else (default) float
    	pinMode( 2, INPUT_PULLUP);
    	pinMode( 3, INPUT_PULLUP);
    	pinMode( 4, INPUT_PULLUP);
    	pinMode( 5, INPUT_PULLUP);
    	pinMode( 6, INPUT_PULLUP);
    	pinMode( 7, INPUT_PULLUP);
    	pinMode( 8, INPUT_PULLUP);
    	pinMode( 9, INPUT_PULLUP);
    	pinMode(10, INPUT_PULLUP);
    	pinMode(11, INPUT_PULLUP);
    	pinMode(12, INPUT_PULLUP);
    	pinMode(13, INPUT_PULLUP);
     
     
      Serial.begin(115200);       //standard serial
      DDRD = DDRD | B00000000;
      DDRB = DDRD | B00000000;
    }
    
    void loop()
    {
    
      la = a;                    //last register a used to detect input change 
      lb = b;                    //last register b used to detect input change
    
      //--------------------------------------------------------------------
      // Read from input registers
      //--------------------------------------------------------------------
      a = PIND >> 2;             //read digital inputs 2 to 7 really fast
      b = PINB;                  //read digital inputs 8 to 13 really fast
      time = micros();
      if (la!=a || lb!=b)
      {
    
    
        //--------------------------------------------------------------------
        // Detect pulses from register A
        //--------------------------------------------------------------------
        p[2].pulse(0,a,time);                //digital input 2
        p[3].pulse(1,a,time);                //    ''        3
        p[4].pulse(2,a,time);                //    ''        etc
        p[5].pulse(3,a,time);
        p[6].pulse(4,a,time);
        p[7].pulse(5,a,time);
    
        //--------------------------------------------------------------------
        // Detect pulses from register B
        //--------------------------------------------------------------------
        p[8].pulse(0,b,time);                //digital input 8
        p[9].pulse(1,b,time);                //etc
        p[10].pulse(2,b,time);
        p[11].pulse(3,b,time);
        p[12].pulse(4,b,time);
        p[13].pulse(5,b,time);
    
      }
    
      //--------------------------------------------------------------------
      // Spit out data every printTime sec (time here is in microseconds)
      //--------------------------------------------------------------------
      // build JSON: for all counters print Count (W), Count Accum(W), Average ms
      // Format {"m":meter,"c":count,"r":rate, "cA":countAccum}
      if ((time-ltime)>(printTime))    
      {
        ltime = time;                          	//Print timer
        
        {
          Serial.print("{\"m\":"); 
          Serial.print(curMeter-1);           	//Print meter number
          Serial.print(",\"c\":"); 
          Serial.print(p[curMeter].count);    	//Print pulse count
          Serial.print(",\"r\":"); 
          Serial.print(p[curMeter].rate(time));	//Print pulse rate
    	  p[curMeter].countAccum += p[curMeter].count;	//Increment and print count accumulator to allow for error checking at client side;
    	  Serial.print(",\"cA\":"); 
          Serial.print(p[curMeter].countAccum); 
          Serial.println("}");
          p[curMeter].count = 0;                //Reset count (we just send count increment)
          p[curMeter].prateAccum = 0;       	//Reset accum so that we can calculate a new average
        }
    	curMeter++ ;							
    	if (curMeter > lastMeter){				// wrap a around if passed last meter
    		curMeter = 2;} 
      }
    }
    
    // library for pulse, originally in separate file 
    
    //-----------------------------------------------------------------------------------
    //Gets a particular input state from the register binary value
    // A typical register binary may look like this:
    // B00100100
    // in this case if the right most bit is digital pin 0
    // digital 2 and 5 are high
    // The method below extracts this from the binary value
    //-----------------------------------------------------------------------------------
    #define BIT_TST(REG, bit, val)( ( (REG & (1UL << (bit) ) ) == ( (val) << (bit) ) ) )
    
    //-----------------------------------------------------------------------------------
    // Method detects a pulse, counts it, finds its rate, Class: PulseOutput
    //-----------------------------------------------------------------------------------
    boolean PulseOutput::pulse(int pin, int a, unsigned long timeIn)
    {
       ld = d;                                    //last digital state = digital state
       
       if (BIT_TST(a,pin,1)) d = 1; else d = 0;   //Get current digital state from pin number
       
       // if (ld==0 && d==1)                      // no internal pull_up if state changed from 0 to 1: internal pull-up inverts state
    	if (ld==1 && d==0)                         //pull_up f state changed from 0 to 1: internal pull-up inverts state
       {
         count++;                                 //count the pulse
         
         // Rate calculation
         lastTime = time;           
         time = timeIn ;						// correction to allow for processing
         prate = (time-lastTime);// - 400;          //rate based on last 2 pulses
                                                    //-190 is an offset that may not be needed...??
         prateAccum += prate - 2000;                     //accumulate rate for average calculation
         
         return 1;
       }
       return 0;
    }
    
    
    //-----------------------------------------------------------------------------------
    // Method calculates the average rate based on multiple pulses (if there are 2 or more pulses)
    //-----------------------------------------------------------------------------------
    unsigned long PulseOutput::rate(unsigned long timeIn)
    {
     if (count > 1)
     {
       prate = prateAccum / count;                          //Calculate average
     } else 
     {
     
     if ((timeIn - lastTime)>(prate*2)) prate = 0;}         //Decrease rate if no pulses are received
                                                            //in the expected time based on the last 
                                                            //pulse width.
     return prate; 
    }
    
     
    
    
    
    
    

  • Hero Member

    And an little code for the master, heavily commented. I used the "click encoder" library in combination with a "State machine" to get to a comprehensible user interface.

    /*-------------------------------------------------------------------------*
     * MySensors interface to AWI 12 input usage/ power Meter                  *
     * measures Power/ Usage/ Cumm. usage for upto 12 independent channels.    *
     * 20x4 (I2C) Display shows consumption                                    *
     * Update:                                                                 *
     * 20150821 - removed dependency on controller, added local history        *
     *	- New rotary encoder library with acceleration and click               *
     *	- Changed to simple State machine                                      *
     *                                                                         *
     *-------------------------------------------------------------------------*/ 
     /*
    //
    // Licence: GNU GPL
    //
    // Author: AWI 2015
    // Updates: AWI 20150815, new RotaryEncoder & ArduinoJson libraries
    // 20150821 - removed dependency on controller, added local history and update
    //	- Changed rotary encoder library
    //	- Changed to simple "state machine"
    //  
    //
    // Functionality:
    // continuously:
    // 1. read JSON strings from Pulse12Counter slave , format {"m":meter,"c":count,"r":rate, "cA":countAccum}
    // 2. calculate real time Power from "pulse rate"
    // 3. daily counters
    // 4. send to controller
    //
    // Changed
    // 1. Total Wh values are now stored local and are only sent to controller 
    //
    // Other:
    // 1. display on LCD, time & energy values, usage can be shown as Daily & Total 
    // 2. rotary encoder to browse the different displays
    // 3. rotary encoder used to change the local values, stored in EEPROM every day and after update
    // 4. depending on display encoder has "short/ long/ click/ double clik" functionality
    // 5. Uses new (201509) V_TEXT to display text from controller. 
    // Caution: pulse meter is connected to std serial (pin D0), Serial.print can still be used  
    //    Disconnect pulse meter when programming via FTDI! (else sync error)
     */
    #include <MySensor.h>                 	// MySensors network
    #include <SPI.h>
    #include <LiquidCrystal_I2C.h>        	// display I2C
    #include <Time.h> 
    #include <Wire.h> 
    #include <ClickEncoder.h>				// Lib includes click functionality: https://github.com/0xPIT/encoder/tree/arduino
    #include <TimerOne.h>					// for ClickEncoder interrupt calls
    #include <ArduinoJson.h>          		// used to parse the simple JSON output of the pulse meter https://github.com/bblanchon/ArduinoJson
    
    // Constants & globals
    const int NO_METERS = 6 ;				// actual meters used (max 12)
    const int NODE_ID = 24 ;             	// fixed MySensors node ID
    const int LCD1_CHILD = 20 ;				// custom text message child, Controller can display message 
    const int JSON_LENGHT = 80 ;			// Maximum json string length
    
    // new V_TEXT variable type (development 20150905)
    //const int V_TEXT = 47 ;
    // new S_INFO sensor type (development 20150905)
    //const int S_INFO = 36 ;
    
    char lastLCD1[21] = "--                  ";	// LCD message line
    
    typedef enum meterTypes: int8_t {meterIn, meterOut, meterNeutral} ;				// metertype for addition in totals: in, out, neutral
    const char METER_NAMES[NO_METERS][4] = {"Gr1", "Gr2","EA ","Pv1","Pv2", "Tst"} ; // meter names
    const meterTypes METER_TYPES[NO_METERS] = {meterOut, meterOut, meterOut, meterIn, meterIn, meterOut};
    
    const unsigned long idleTime = 10000 ;	// Delay time for any of the states to return to idle
    unsigned long idleTimer ;				// Delay timer for idleTime
    
    // RotaryEncoder
    const int8_t encPinA = A1;				// Encoder pins
    const int8_t encPinB = A0;
    const int8_t encPinButton = A2;
    const int8_t encStepsNotch = 2;   		// tune for best stepsize (1..4, 1=default)
    const bool encButton = LOW ;      		// active low pushbutton
    ClickEncoder encoder(encPinA, encPinB, encPinButton, encStepsNotch, encButton);	// instantiate the encoder
    
    union {									// used to convert long to bytes for EEPROM storage
    	long kWhLongInt;
    	uint8_t kWhLongByte[4];
    	} kWhLong ;
    
    
    // Possible states for the state machine.
    // Idle: default state, show totals for in/out nett
    // Browse: dive into meter details 
    // Update: update meter value (Wh total)
    // Reset: reset day values
    enum States: int8_t {IDLE, BROWSE, UPDATE, RST} ;
    uint8_t State = IDLE;					// current state machine state
    
    // meter class, store all relevant meter data (equivalent to struct)
    class pulseMeter              
    {
    public:                                  
    	long UsageWh; 						// last (current) usage (in W) from pulse counter
    	long UsageAccumWh;					// usage accumulator (to keep in sync) from pulse counter
    	long PowerW;						// actual power, calculated from pulse "rate"
    	long DayUsageWh;					// daily usage for display
    	meterTypes Type;					// metertype for addition in totals: in, out, neutral
    	char Name[4] ;						// meter name for display
    };
    pulseMeter pulseMeters[NO_METERS] ;		// define power meters
    pulseMeter totalMeterIn, totalMeterOut, totalMeter;	//  accumulated
    unsigned long tempUsageWh ;				// temporary store while manually updating (state UPDATE) 
    
    // Json parser:  define parse object: <10> = number of tokens in JSON: ~10 per m (4 * [key, value] + key_total, value_total))
    // example: "{\"m\":1,\"c\":12,\"r\":120000,\"cA\":12345}"
    char json[JSON_LENGHT] ;		// Storage for serial JSON string (init for example)
    
    // flags & counters 
    bool timeReceived = false;				// controller time
    bool newDay = false;					// reset at 00:00:00
    unsigned long lastUpdate=0, lastRequest=0, lastDisplay=0, lastSyncKWH=0;  // loop timers for once in while events
    int updateMeter = 0;					// current meter for update
    bool updateDisplayFlag = true ;			// indicate that display needs to be updated
    int currentMeter = 0;					// active meter for update & check, cycles through meters (0..NO_METERS-1)
    int lastRotary = 0;						// last rotary encoder position
    int updateIncrement = 1000 ;			// Interval multiplier for Update 
    int errCount = 0 ;						// error counter
    // *** Definition and initialisation
    // define the MySensor network
    MySensor gw;							// pins used RFX24(default 9,10)
        
    // Initialize messages for sensor network
    MyMessage powerMsg(0,V_WATT); 	        // message to send power in W
    MyMessage usageMsg(0,V_KWH);    	    // message to send usage in kWH
    MyMessage textMsg(0,V_TEXT);    	    // message to send/receive text
    
    
    // Set the pins on the I2C chip used for LCD connections:
    //                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
    LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
    
    // OPTIONAL: Custom characters for display - Units)
    byte degCelcius[8] = { B01000, B10100, B01000, B00111, B00100, B00100, B00111, B00000};
    //byte Pascal[8] = { B00000, B11100, B10100, B11100, B10010, B10111, B10101, B00000};
    //byte hecto[8] = { B00000, B00000, B00000, B00100, B00110, B00101, B00101, B00000};
    //byte Lux[8] = { B00000, B10000, B10101, B10010, B10010, B10101, B11100, B00000};
    byte hr[8] = { B00000, B00000, B10000, B10000, B11000, B10100, B10100, B00000};			// small hour symbol
    byte perDay[8] = { B00000, B00000, B00001, B00001, B00011, B00101, B00011, B00000};
    
    void timerIsr() {						// RotaryEncoder timer interrupt routinge
    	encoder.service();
    	}
    
    // function to reset the Arduino (jump to 0 address)
    void(* resetFunc) (void) = 0;//declare reset function at address 0
    
    void setup(void)
    {
    	gw.begin(incomingMessage, NODE_ID, false);  			// this node is fixed, no repeat
    	//Send the sensor node sketch version information to the gateway
    	gw.sendSketchInfo("AWI-12ChannelPulse", "2.0");
    	// Initialize the meter names
    	
    	// Register all Pulse counters to gw (they will be created as child devices from 0 to MAX-1)
    	for (int x = 0; x < NO_METERS; x++){ 
    		gw.present(x, S_POWER, METER_NAMES[x]);             // present power meters to gateway
    		delay(10);                        					// give it some time to process
    		}
    	gw.present(LCD1_CHILD, S_INFO);							// present the child for custom text line (20150906)
    	delay(100);
    	gw.send(textMsg.setSensor(LCD1_CHILD).set("-"));		// initialize the V_TEXT at controller for sensor to none (trick for Domoticz)
    	
    	for (int x = 0; x < NO_METERS; x++){ 					// initialize previous kWh values from EEPROM and init
    		for (int y = 0; y < 4 ; y++){						// convert from bytes
    			kWhLong.kWhLongByte[y]= gw.loadState(x *4 + y) ;// EEPROM position = meter number * 4 bytes
    			}												// controller is updated later automatically
    		pulseMeters[x].UsageWh = kWhLong.kWhLongInt;
    		strcpy(pulseMeters[x].Name, METER_NAMES[x] ); 		// copy string for meter names
    		pulseMeters[x].Type = METER_TYPES[x];				// Set type
    		}
    	
    	// Initializations
    	Wire.begin();                 							// I2C for display
    	
    	Timer1.initialize(1000);								// interrupt calls for RotaryEncoder
    	Timer1.attachInterrupt(timerIsr);
    	encoder.setAccelerationEnabled(true);
    
    	gw.requestTime(receiveTime);							// Request latest time from controller at startup
    
    	// ** LCD display **
    	lcd.begin(20, 4);										// LCD 2 lines * 16 char.
    	lcd.setBacklight(HIGH);
    	// send custom characters to display
    	lcd.createChar(1, degCelcius);
    	lcd.createChar(4, hr);
    	lcd.createChar(7, perDay);
    	lcd.setCursor(0, 0);             						// Reset cursor position
    }
    
    void loop(void)
    {
    	// Before specific states, perform generic tasks
    	unsigned long now = millis();      						// Timer in loop for "once in a while" events
    	gw.process() ;                      					// process incoming messages
    	// If no time has been received yet, request it every 10 second from controller
    	if ((!timeReceived && (now-lastRequest > 10*1000)) ||	
    		(now-lastRequest > 3600000UL)){						// request update every hour to keep in sync
    		Serial.println(F("requesting time"));				// Request time from controller. 
    		timeReceived = false;
    		gw.requestTime(receiveTime);  
    		lastRequest = now;
        }
    	// Check if new day has started (hour == 0) and reset day usage counters of meters
    	if (hour()==0 && !newDay){
    		newDay = true;
    		for (int x = 0; x < NO_METERS; x++){ 
    			pulseMeters[x].DayUsageWh = 0 ;					// reset daily counters
    			saveMeters(x);									// save meter values to EEPROM
    		}   
    	} else if(hour() != 0 && newDay)						// reset newday flag if hour != 0
    		{ newDay = false;}
      
    	// State machine depends on RotaryEncoder and time actions
    	ClickEncoder::Button rotaryButton = encoder.getButton();// Get the button state
    	switch (State) {
            // Idle state, browse through displays, watches for key presses
            // and changes state accordingly, while taking care of next state init
            case IDLE:                
                if (rotaryButton == ClickEncoder::Clicked) {
    				idleTimer = now ;										// set delay timer for return to idle
    				State = BROWSE ;
    				updateDisplayFlag = true ;}  							// only change state
                else if (rotaryButton == ClickEncoder::Held) {
    				idleTimer = now ;
    				updateDisplayFlag = true ;
    				State = RST;}											// Enter Reset state with Long press.
                //else if (rotaryButton == ClickEncoder::Released) {} 		// do nothing
    			//else if (rotaryButton == ClickEncoder::Pressed) {} 		// do nothing
                //else if (rotaryButton == ClickEncoder::DoubleClicked) {} 	// do nothing
    			else {}// perform Idle actions: browse display)
    			break ;
     		case BROWSE:
                if (rotaryButton == ClickEncoder::Clicked) {State = IDLE ;	// return to IDLE on click
    				updateDisplayFlag = true ;} 
                else if (rotaryButton == ClickEncoder::Held) {				// update the current displayed value.
    				tempUsageWh = pulseMeters[currentMeter].UsageWh ;		// use temp variable for update (background changes)
    				idleTimer = now ;										// set delay timer for return to idle
    				updateDisplayFlag = true ;
    				State = UPDATE ; 
    				}
                //else if (rotaryButton == ClickEncoder::Released) {break;} // do nothing
    			//else if (rotaryButton == ClickEncoder::Pressed) {break;} 	// do nothing
                //else if (rotaryButton == ClickEncoder::DoubleClicked) {break;} // do nothing
    			else if (now > idleTimer + idleTime){State = IDLE ;}		// return to idle after expiration of delay
    			else {// perform Idle actions: browse display)
    				int enc = encoder.getValue();							// get the value from the encoder
    				if (enc != 0)idleTimer = now ;							// set delay timer for return to idle
    				if( enc > 0) {											// cycle through displays depending on up/ down (actual value not used)
    					if (currentMeter++ >= NO_METERS - 1 ) currentMeter = 0;
    					lastRotary = enc ;
    					updateDisplayFlag = true ;
    				}
    				else if (enc < 0) {
    					if (currentMeter-- <=  0) currentMeter = NO_METERS - 1;
    					lastRotary = enc ;
    					updateDisplayFlag = true ;
    				}
    			}
    			break ;
    		case UPDATE:                									// Double state for large (start) & small increments
     			if (rotaryButton == ClickEncoder::Clicked) {
    				if (updateIncrement == 1) {								// Save values and return to idle
    					updateIncrement = 1000 ; 							// increment in kWh (x1000)
    					pulseMeters[currentMeter].UsageWh = tempUsageWh;	// save temp to actual
    					saveMeters(currentMeter);							// !!Save the current value to EEPROM (controller will be updated automatically)
    					updateDisplayFlag = true ;
    					State = BROWSE; 									// return to browse after update
    				} else{													// increment in Wh (x1)
    					updateIncrement = 1 ;
    				}
    			}
                //else if (rotaryButton == ClickEncoder::Held)  break; 		// (use as cancel function?)
                //else if (rotaryButton == ClickEncoder::Released) break; 	// do nothing
    			//else if (rotaryButton == ClickEncoder::Pressed) break; 	// do nothing
                //else if (rotaryButton == ClickEncoder::DoubleClicked) break; 		// do nothing
    			else if (now > idleTimer + idleTime){
    				updateDisplayFlag = true ;
    				State = IDLE ;}		// return to idle after expiration of delay
    			else {														// perform Update actions: change current value
    				int16_t enco = encoder.getValue();						// get the value from the encoder
    				if (enco != 0) {
    					updateDisplayFlag = true ;
    					tempUsageWh += enco * updateIncrement ;				// update the temp value from the encoder}
    					idleTimer = now ;}									// set delay timer for return to idle
    				}
                break ;
    		case RST:														// reset (jump to reset if Held again)
    			if (rotaryButton == ClickEncoder::DoubleClicked) {
    				State = IDLE ; 											//reset
    				for (int x = 0; x < NO_METERS; x++){ 
    					pulseMeters[x].DayUsageWh = 0 ;						// reset daily counters
    					saveMeters(x);										// save meter values to EEPROM
    					}				
    				errCount = 0 ;											// reset Json err counter
    				updateDisplayFlag = true ;
    				}
    			else if (rotaryButton == ClickEncoder::Clicked) {
    				updateDisplayFlag = true ;
    				State = IDLE ;}	// click = return to Idle
    			else if (now > idleTimer + idleTime){
    				updateDisplayFlag = true ;
    				State = IDLE ;}		// return to idle after expiration of delay
    			break ;
    		}
    	
    
    	// Update display every 1 second (IDLE) every loop (Other states)
    	//if (((State == IDLE) && (now-lastUpdate > 100)) || State != IDLE ){
    	if (now-lastDisplay > 1000){ 
    		updateDisplayFlag = true ;
    		lastDisplay = now;
    		//Serial.print("State: ");
    		//Serial.println(State);
    		}		
    	if (updateDisplayFlag) {
    		LCD_local_display();
    		updateDisplayFlag = false ;
    		}
        
    	// Every 10 seconds update one meter to controller to avoid traffic jams
     	if (now-lastSyncKWH > 10000){
    		//printPulsemeter(updateMeter);
    		sendPowerUpdate(updateMeter);        						// update the values for currentMeter
    		updateMeter++ ;
    		if (updateMeter >= NO_METERS){     							// increment and wrap current meter
    			updateMeter = 0 ;}
    		lastSyncKWH = now ;
    		}
    		
    	// Update sensors every 10 secs
    	if (now-lastUpdate > 10000) {
    		// get values to be displayed from controller
    		gw.request(LCD1_CHILD, V_TEXT);		 						// LCD text message
    		lastUpdate = now;
    	}
    	
    	// get readings from serial (sent every 10s)
    	// format {"m":meter,"c":count,"r":rate, "cA":countAccum}
    	// use JSON parser to process (could be replaced by simple split routine, but this works just fine)
    	if(readLineJSON(Serial.read(), json, JSON_LENGHT) > 0 ){   		//dummySerial(), Serial.read()
    	// if(readLineJSON(dummySerial(), json, 80) > 0 ){   			//dummySerial(), Serial.read()
    		Serial.println(json);
    		storeMeterJSON(json);           							//store the meter reading
    		calcMeterTotals();											// update totals
    		}
    }
    
    // This is called when a new time value was received
    void receiveTime(unsigned long controllerTime) {
        // Ok, set incoming time 
        Serial.print(F("Time value received: "));
        Serial.println(controllerTime);
        setTime(controllerTime); 										// set the clock to the time from controller
    	timeReceived = true ;
    }
    
    // This is called when a message is received 
    void incomingMessage(const MyMessage &message) {
      // Expect few types of messages from controller, V_VAR1 for messages
    	if (message.type==V_TEXT) {
    		// if message comes in, update the kWH reading for meter with value since last update
    		// Write some debug info
    		//Serial.print("Last reading for sensor: ");
    		//Serial.print(message.sensor);                
    		//Serial.print(", Message: ");
    		//Serial.println(message.getString());
    		if (message.sensor == LCD1_CHILD ) {
    			strcpy(lastLCD1, message.getString());	// read payload in LCD string
    		}
    	}
    }
    
    // save Meter to EEPROM when needed
    void saveMeters(int8_t meterNo){
    	kWhLong.kWhLongInt = pulseMeters[meterNo].UsageWh ; 					// convert to separate bytes via struct
    	for (int y = 0; y < 4 ; y++){
    		gw.saveState(meterNo * 4 + y, kWhLong.kWhLongByte[y])  ;			// EEPROM position = meter number * 4 bytes
    		}
    	}
    
    void sendPowerUpdate(int meterNo)
    // Sends update to controller for current meter 
    {
        gw.send(powerMsg.setSensor(meterNo).set((long)pulseMeters[meterNo].PowerW));			// meterNo * 100 ));
        gw.send(usageMsg.setSensor(meterNo).set((float)pulseMeters[meterNo].UsageWh/1000L ,3)); // send in kWh!
    }
    
    //  calculate the total values for from the meters
    //  Total in and out
    void calcMeterTotals(void)
    {	
    	totalMeterIn.UsageWh = 0 ;
    	totalMeterIn.PowerW = 0 ;
    	totalMeterIn.DayUsageWh = 0 ;
    	totalMeterOut.UsageWh = 0 ;
    	totalMeterOut.PowerW = 0 ;
    	totalMeterOut.DayUsageWh = 0 ;
    	for (int x = 0; x < NO_METERS; x++){ 					// add In and Out meters to totals
    		if (pulseMeters[x].Type == meterIn){				
    			totalMeterIn.UsageWh += pulseMeters[x].UsageWh;
    			totalMeterIn.PowerW += pulseMeters[x].PowerW ;
    			totalMeterIn.DayUsageWh += pulseMeters[x].DayUsageWh ;
    		}
    		else if (pulseMeters[x].Type == meterOut){
    			totalMeterOut.UsageWh += pulseMeters[x].UsageWh;
    			totalMeterOut.PowerW += pulseMeters[x].PowerW ;
    			totalMeterOut.DayUsageWh += pulseMeters[x].DayUsageWh ;
    		}
    	} // else = neutral, do nothing 
    	totalMeter.UsageWh = totalMeterOut.UsageWh - totalMeterIn.UsageWh ;		// calculate nett values
    	totalMeter.PowerW = totalMeterOut.PowerW - totalMeterIn.PowerW ;
    	totalMeter.DayUsageWh = totalMeterOut.DayUsageWh - totalMeterIn.DayUsageWh ;
    }
    
    void LCD_local_display(void)
    // prints variables on LCD display with units, depending on current State
    // IDLE = totals
    // BROWSE = individual meters
    // SET = individual meter & value
    {
       	//long loopDelay = millis();									// Test loop time
    	char buf[21]; 												// buffer for display
    	char tempBuf[11];											// Temporary storage for float -> char conversion
    	// always, time on first line
    	snprintf(buf, sizeof buf, "%02d:%02d:%02d %02d-%02d %5s", hour(), minute(), second(), day(), month(), lastLCD1);
    	lcd.setCursor(0,0);               							// LCD line 1
    	lcd.print(buf);
    	// State specific (line 2, 3, 4)
    	switch (State) {
    		case IDLE:											
    			// display In meter total on line 2
    			dtostrf((float)totalMeterOut.DayUsageWh /1000.0, 6, 2, tempBuf);
    			snprintf(buf, sizeof buf, "Use: %6skW\x04%5dW  ", tempBuf, (int)totalMeterOut.PowerW );
    			lcd.setCursor(0,1);               					// LCD line 2
    			lcd.print(buf);
    			// display Out meter total on line 3
    			dtostrf(totalMeterIn.DayUsageWh /1000.0, 6, 2, tempBuf);
    			snprintf(buf, sizeof buf, "Gen: %6skW\x04%5dW  ", tempBuf, (int)totalMeterIn.PowerW );
    			lcd.setCursor(0,2);               					// LCD line 3
    			lcd.print(buf);
    			// display Net meter total on line 4
    			dtostrf(totalMeter.DayUsageWh /1000.0, 6, 2, tempBuf);
    			snprintf(buf, sizeof buf, "Net: %6skW\x04%5dW  ", tempBuf, (int)totalMeter.PowerW );
    			lcd.setCursor(0,3);               					// LCD line 4
    			lcd.print(buf);
    			break;
    		case BROWSE:
    			// meter indicated by "currentMeter"
    			dtostrf((float)pulseMeters[currentMeter].DayUsageWh /1000.0, 6, 2, tempBuf);
    			snprintf(buf, sizeof buf, "%4s%6skW\x04\x07%5dW  ", pulseMeters[currentMeter].Name, tempBuf, (int)pulseMeters[currentMeter].PowerW );
    			lcd.setCursor(0,1);               					// LCD line 2
    			lcd.print(buf);
    			// display total for meter line 3,
    			dtostrf((float)pulseMeters[currentMeter].UsageWh /1000.0, 10, 2, tempBuf);
    			snprintf(buf, sizeof buf, "tot:%10skW\x04          ",tempBuf);
    			lcd.setCursor(0,2);               // LCD line 3
    			lcd.print(buf);
    			// TEST: display full message on line 4
    			lcd.setCursor(0,3);               // LCD line 4
    			snprintf(buf, sizeof buf, "%20s", lastLCD1);
    			lcd.print(buf);
    			break;
    		case UPDATE:
    			// meter indicated by "currentMeter"
    			dtostrf((float)pulseMeters[currentMeter].DayUsageWh /1000.0, 6, 2, tempBuf);
    			snprintf(buf, sizeof buf, "%4s%6skW\x04\x07%5dW  ", pulseMeters[currentMeter].Name, tempBuf, (int)pulseMeters[currentMeter].PowerW );
    			lcd.setCursor(0,1);               // LCD line 2
    			lcd.print(buf);
    			// display current value on line 3
    			lcd.setCursor(0,2);               // LCD line 3
    			dtostrf((float)pulseMeters[currentMeter].UsageWh /1000.0, 10, 3, tempBuf);
    			snprintf(buf, sizeof buf, "tot:%10skW\x04 \x7E      ",tempBuf);
    			lcd.print(buf);
    			// display update value on line 4
    			lcd.setCursor(0,3);               // LCD line 4
    			dtostrf((float)tempUsageWh /1000.0, 10, 3, tempBuf);
    			snprintf(buf, sizeof buf, "tot:%10skW\x04          ",tempBuf);
    			lcd.print(buf);
    			break;
    		case RST:
    			//snprintf(buf, sizeof buf, "Use: %6skW\x04%5dW  ", tempBuf, (int)totalMeterOut.PowerW );
    			lcd.clear();
    			lcd.setCursor(0,0);               					// LCD line 2
    			lcd.print("Doubleclick to reset");
    			lcd.setCursor(0,2);
    			snprintf(buf, sizeof buf, "JSON err count: %d",errCount);
    			lcd.print(buf);
    		default:												// default display
    			break;
    	}
    }
    
    
    
    int storeMeterJSON(char *json)
    /* convert JSON to values and store in corresponding meter (if used)
     input: JSON string (can be wrong formatted), with length
     output: changed meter record number or -1 if error
     use JsonParser 
    */
    {
      StaticJsonBuffer<50> jsonBuffer; 							// 4 object  -> 4 + 4*10 = 44
      // char njson[] = "{\"m\":1,\"c\":12,\"r\":120000,\"cA\":12345}";
        JsonObject& root = jsonBuffer.parseObject(json);
      if (!root.success())
        {
            Serial.println(F("JsonParser.parse() failed"));
    		errCount++ ;
            return -1;
        }
      int m = (long)root["m"];
      if (m > NO_METERS){                 						// meter value out of range for used meters (m starts at 1)
        return -1 ;
      } else {                     								// update meter values, Power is momentary, Usage is cumulative
    	long newAccumWh = (long)root["cA"] ;
    	long newWh = (long)root["c"] ;
    	long diffAccumWh = newAccumWh - pulseMeters[m-1].UsageAccumWh; 	// check for missed pulses by comparing cA with last stored value
    	if (diffAccumWh >= newWh){								// no difference or missed pulses -> correct: add difference
    		pulseMeters[m-1].UsageWh += diffAccumWh;
    		pulseMeters[m-1].DayUsageWh += diffAccumWh;
    		}
    	else {													// negative diff, out of sync -> add pulses only (can be out of range or restart)
    		pulseMeters[m-1].UsageWh += newWh;
    		pulseMeters[m-1].DayUsageWh += newWh;
    		}
    	pulseMeters[m-1].UsageAccumWh = newAccumWh; 			// always update sync counter (only for sync and error correction(serial))
        if ((long)root["r"] == 0){            					// calculate power from pulse rate (ms) and truncate to whole Watts
          pulseMeters[m-1].PowerW = 0;							// if overflow assume no Usage
        } else {
          pulseMeters[m-1].PowerW = long( 3600000000L / (long)root["r"]); // rate in microseconds
        }
        return m ;
      }
    }
    
    int readLineJSON(int readch, char *buffer, int len)
    /* checks for JSON and when started append char tot buffer and checks for line completion 
    usage:
      static char buffer[80];
      if (readline(Serial.read(), buffer, 80) > 0) { // line complete}
      returns simple JSON
      */
    {
      static int pos = 0;
      int rpos;
    
      if (readch > 0) {
        switch (readch) {
          case '\n':                // Ignore new-lines
            break;
          case '\r':                // Return on CR
            rpos = pos;
            pos = 0;                  // Reset position index ready for next time
            return rpos;
          default:
            if (pos < len-1) {
              buffer[pos++] = readch;
              buffer[pos] = 0;
            }
        }
      }
      // No end of line has been found, so return -1.
      return -1;
    }
    
    /*
    int dummySerial()
    // Acts as a dummy JSON serial character generator for debugging
    // input: none
    // output: preset JSON string
    {
      static int pos = 0;
      char json[] = "{\"m\":1,\"c\":12,\"r\":120000,\"cA\":12345}\r{\"m\":2,\"c\":212,\"r\":2120000,\"cA\":212345}\r{\"m\":3,\"c\":212,\"r\":2120000,\"cA\":212345}\n\r";
      if (pos++ >= strlen(json)){
        pos = 0;
      } 
      return json[pos] ;
      
    }
    */
    
    
    
    /*
    void printPulsemeter(int meter)
    // prints the Pulsemeter record to serial out
    {
      Serial.print("m:");
      Serial.print(meter);
      Serial.print(", power: ");
      Serial.print(pulseMeters[meter].PowerW);
      Serial.print(", usage: ");
      Serial.print(pulseMeters[meter].UsageWh);
      Serial.print(", day usage: ");
      Serial.println(pulseMeters[meter].DayUsageWh );
    //  Serial.print(", Ca: ");
    //  Serial.println(pulseMeters[meter].UsageAccumWh);
    }
    */
    


  • Hi, great work 👍

    as my S0 meter uses 2000imp/kwh and my reading seem to be twice as high as they should be i guess your meters do 1000imp/kwh?
    As i am still working on my programming skills i could not figure out where to change this 😳
    can you give me a hint which 1000 to change out with my 2000?

    thanks!



  • AWI I got it up and running with your new sketches!! 😃
    Great work!!!!! Domoticz recognizes that there are sensors in the network.

    But something is still wrong, the usage isn't going up..
    Whats wrong?

    Didn't connect a display and rotaryswitch and also didn't commented things out..

    Why does it show every sensor twice? One with subtype unknown and one with electric??
    I also seen that with the humidity temp sensor..

    Schermafdruk 2015-09-20 17.45.11.png

    The serial on the master arduino says:
    Schermafdruk 2015-09-20 18.10.52.png

    There's not more than a tiny plaster wall between the two arduinos, a total distance of one meter...


  • Hero Member

    @MarkV A few thing from what I read from the serial out:

    • The slave does not sense any pulses, all the values are at 0

    • The master gets a "fail" when sending data to the Gateway. Looks like a communication issue

    • Are you using an USB or LAN gateway. The Domoticz output show both.... I don't know if Domoticz can handle that.

    • You can try to delete the "old"/previous nodes and start the thing again. Domoticz should use a combined Usage/ Power sensor


  • Hero Member

    @jeti You are right, I never thought about other meters than the 1 pulse = 1 Whr ones..sorry. There are a few ways of getting it changed:

    • You can keep leave most as it is and when you send/ or display the values divide the pulses by two and double the Watt values in the routines "LCD_local_display" and "sendPowerUpdate"

    • or: make the necessary calculations when the data comes in. This is a little more complicated in routine "storeMeterJSON" but if you understand the logic (including error corrrection) should be not too hard.



  • @AWI
    I´m using a LAN GW, i disconnected the serial and made the LAN one of it.
    Mmm, the comms error is strange, they're at max one meter apart, with a tiny plaster wall in between, both also got a cap between vcc and gnd.
    Next weekend i´m going to have a look at the slave input, rather strange that it doesn´t receives pulses.
    I connected the 5volt line to all the 6 pulse meters and connected their output to D2 - D8, through a cord of network cable.
    Thanks for all your help so far!!!!!



  • @AWI
    thanks! as i do not have display (yet) i just did the calculations in the "sendPowerUpdate" -> now it works perfect 😄
    I just double check with a powermeter to see if everything is ok.


  • Hero Member

    @MarkV just one tip for today... You should connect the common ground to the pulse meters. The inputs use a pull-up to vcc..



  • Dawm maybe thats the problem..
    I connected the 5v as commen to the pulse + and the - to the digital inputs.

    So to be sure i need to connect the pulse + to the digitale inputs and the pulse - to gnd????


  • Hero Member

    @MarkV I would depend on your S0 meters, but normal is "open collector" so you can connect '-' to ground and '+' to the input (= pull-up to Vcc).

    (another way to connect is + to vcc and - to input, but then you need external "pull down" resistors and change the sketch)



  • Then thats the problem with the slave arduino getting no data. I connected the pulse output like you mentioned second and didn't changed the sketch.
    Maybe i'm home tomorrow then i will give it a try.
    Or is it hard to chsnge the sketch? And what should i change?


  • Hero Member

    @MarkV it is indicated in the sketch but you also need external resistors for pull-down. If you disable the internal pull-up resistors the levels are inverted. So you also need to'invert' the measurements or you are measuring the pulse width instead of the time between pulses.



  • Tonight i checked the wires and changed them and i believe your wright, the output is:
    Schermafdruk 2015-09-22 21.30.56.png

    But it keeps giving a error, so i hangt them near each other with approx. 10cm space in between, why does it stil say fail after a good startup with ok and after 10a20sec it starts saying fail..

    Is the readout correct?
    And what more could cause the fail error?


  • Hero Member

    @MarkV In most of the cases "fail" is related to the power supply of the radio. Did you follow the instructions for connection of the radio?


  • Hardware Contributor

    @AWI
    Your measurement sketch is really nice. There is one line I do not understand.

    prateAccum += prate - 2000;

    Is this prateAccum needed or couldn't it be left away? What is the meaning of 2000 in this context?

    Cu,
    FotoFieber


  • Hero Member

    @FotoFieber To be honest .. a left over from some experiments. Just leave it out... you won't notice the difference.



  • @AWI thanks again!
    I am using your sketches with only one S0 Meter (the 2000pulse/kwh one).
    *One thing i just found out, that as long as the master arduino is connected to the serial port of my pc it is running fine but when its not, it does not send.I have checked the voltage at the radio and it is the same... I also do not have any display or rotary encoder, as I do the visualisation with FHEM.
    Do you know why this is the case? * -> Voltage was to low... but the secon question remains:

    Any idea to use differen pulses/kwh S0 meters? for example one 2000pulse/kwh and one 800 pulse/kwh.
    thanks in advance!



  • Arduino: 1.6.5 (Windows 7), Board:"Arduino Nano, ATmega328"

    sketch_oct03e:128: error: 'V_TEXT' was not declared in this scope
    sketch_oct03e.ino: In function 'void setup()':
    sketch_oct03e:162: error: 'S_INFO' was not declared in this scope
    sketch_oct03e.ino: In function 'void loop()':
    sketch_oct03e:337: error: 'V_TEXT' was not declared in this scope
    sketch_oct03e.ino: In function 'void incomingMessage(const MyMessage&)':
    sketch_oct03e:364: error: 'V_TEXT' was not declared in this scope
    'V_TEXT' was not declared in this scope

    Dit rapport zou meer informatie hebben met
    "Tijdens de compilatie uitgebreide uitvoer weergeven"
    ingeschakeld in Bestand > Voorkeuren.



  • this is what i'll get when trying to upload the master sketch


  • Hero Member

    @marten You need to use the "Development" branch of MySensors until the production version gets updated -or- you can uncomment the lines with "const int ..."

    // new V_TEXT variable type (development 20150905)
    //const int V_TEXT = 47 ;
    // new S_INFO sensor type (development 20150905)
    //const int S_INFO = 36 ;
    


  • This post is deleted!

  • Hero Member

    @jeti as mentioned in a previous post..

    There are a few ways of getting it changed:

    You can keep/ leave most as it is and when you send/ or display the values divide the pulses by two and double the Watt values in the routines "LCD_local_display" and "sendPowerUpdate"

    or: make the necessary calculations when the data comes in. This is a little more complicated in routine "storeMeterJSON" but if you understand the logic (including error corrrection) should be not too hard.

    I don't have the time at this moment to do it for you...



  • @AWI: 😃 nevermind I will start workin on it
    Thanks again for the great work!



  • was not that much work 😃
    this is the modified master-sketch from AWI i am starting to use. The changes i made:
    -deletion of LCD and rotray part
    -deletion of accumulation part
    -modified meterType to meter800, meter1000 and meter2000 this reflects meters with 800imp/khw;1000imp/kwh;2000imp/kwh

    I am using this with only one meter (2000imp/kwh) for now and works pretty nicly second different meter type is on the way.

    As I am a beginner please be gentle with comments 🙂

    
    #include <MySensor.h>                   // MySensors network
    #include <SPI.h>
    #include <Time.h> 
    #include <ArduinoJson.h>                // used to parse the simple JSON output of the pulse meter https://github.com/bblanchon/ArduinoJson
    
    // Constants & globals
    const int NO_METERS = 1 ;               // actual meters used (max 12)
    const int NODE_ID = 152 ;                // fixed MySensors node ID
    
    const int JSON_LENGHT = 80 ;            // Maximum json string length
    
    // new V_TEXT variable type (development 20150905)
    const int V_TEXT = 47 ;
    // new S_INFO sensor type (development 20150905)
    const int S_INFO = 36 ;
    
    char lastLCD1[21] = "--                  "; // LCD message line
    
    typedef enum meterTypes: int8_t {meter800, meter1000, meter2000} ;             // metertype meter800, meter1000, meter2000; 
    const char METER_NAMES[NO_METERS][4] = {"PC"} ; // meter names
    const meterTypes METER_TYPES[NO_METERS] = {meter2000};
    
    const unsigned long idleTime = 10000 ;  // Delay time for any of the states to return to idle
    unsigned long idleTimer ;               // Delay timer for idleTime
    
    
    
    union {                                 // used to convert long to bytes for EEPROM storage
        long kWhLongInt;
        uint8_t kWhLongByte[4];
        } kWhLong ;
    
    
    // Possible states for the state machine.
    // Idle: default state, show totals for in/out nett
    // Browse: dive into meter details 
    // Update: update meter value (Wh total)
    // Reset: reset day values
    enum States: int8_t {IDLE, BROWSE, UPDATE, RST} ;
    uint8_t State = IDLE;                   // current state machine state
    
    // meter class, store all relevant meter data (equivalent to struct)
    class pulseMeter              
    {
    public:                                  
        long UsageWh;                       // last (current) usage (in W) from pulse counter
        long UsageAccumWh;                  // usage accumulator (to keep in sync) from pulse counter
        long PowerW;                        // actual power, calculated from pulse "rate"
        long DayUsageWh;                    // daily usage for display
        meterTypes Type;                    // metertype for addition in totals: meter800, meter1000, meter2000; 
        char Name[4] ;                      // meter name for display
    };
    pulseMeter pulseMeters[NO_METERS] ;     // define power meters
    //pulseMeter meter800, meter1000, meter2000; //  pulse/kWh
    unsigned long tempUsageWh ;             // temporary store while manually updating (state UPDATE) 
    
    // Json parser:  define parse object: <10> = number of tokens in JSON: ~10 per m (4 * [key, value] + key_total, value_total))
    // example: "{\"m\":1,\"c\":12,\"r\":120000,\"cA\":12345}"
    char json[JSON_LENGHT] ;        // Storage for serial JSON string (init for example)
    
    // flags & counters 
    bool timeReceived = false;              // controller time
    bool newDay = false;                    // reset at 00:00:00
    unsigned long lastUpdate=0, lastRequest=0, lastDisplay=0, lastSyncKWH=0;  // loop timers for once in while events
    int updateMeter = 0;                    // current meter for update
    bool updateDisplayFlag = true ;         // indicate that display needs to be updated
    int currentMeter = 0;                   // active meter for update & check, cycles through meters (0..NO_METERS-1)
    int lastRotary = 0;                     // last rotary encoder position
    int updateIncrement = 1000 ;            // Interval multiplier for Update 
    int errCount = 0 ;                      // error counter
    // *** Definition and initialisation
    // define the MySensor network
    MySensor gw;                            // pins used RFX24(default 9,10)
        
    // Initialize messages for sensor network
    MyMessage powerMsg(0,V_WATT);           // message to send power in W
    MyMessage usageMsg(0,V_KWH);            // message to send usage in kWH
    MyMessage textMsg(0,V_TEXT);            // message to send/receive text
    
    
    
    // function to reset the Arduino (jump to 0 address)
    void(* resetFunc) (void) = 0;//declare reset function at address 0
    
    void setup(void)
    {
        gw.begin(incomingMessage, NODE_ID, false);              // this node is fixed, no repeat
        //Send the sensor node sketch version information to the gateway
        gw.sendSketchInfo("AWI-12ChannelPulse", "2.0");
        // Initialize the meter names
        
        // Register all Pulse counters to gw (they will be created as child devices from 0 to MAX-1)
        for (int x = 0; x < NO_METERS; x++){ 
            gw.present(x, S_POWER, METER_NAMES[x]);             // present power meters to gateway
            delay(10);                                          // give it some time to process
            }
        
        delay(100);
    
        
        for (int x = 0; x < NO_METERS; x++){                    // initialize previous kWh values from EEPROM and init
            for (int y = 0; y < 4 ; y++){                       // convert from bytes
                kWhLong.kWhLongByte[y]= gw.loadState(x *4 + y) ;// EEPROM position = meter number * 4 bytes
                }                                               // controller is updated later automatically
            pulseMeters[x].UsageWh = kWhLong.kWhLongInt;
            strcpy(pulseMeters[x].Name, METER_NAMES[x] );       // copy string for meter names
            pulseMeters[x].Type = METER_TYPES[x];               // Set type
            }
     }
    
    void loop(void)
    {
        // Before specific states, perform generic tasks
        unsigned long now = millis();                           // Timer in loop for "once in a while" events
        gw.process() ;                                          // process incoming messages
        // If no time has been received yet, request it every 10 second from controller
        if ((!timeReceived && (now-lastRequest > 10*1000)) ||   
            (now-lastRequest > 3600000UL)){                     // request update every hour to keep in sync
            Serial.println(F("requesting time"));               // Request time from controller. 
            timeReceived = false;
            gw.requestTime(receiveTime);  
            lastRequest = now;
        }
        // Check if new day has started (hour == 0) and reset day usage counters of meters
        if (hour()==0 && !newDay){
            newDay = true;
            for (int x = 0; x < NO_METERS; x++){ 
                pulseMeters[x].DayUsageWh = 0 ;                 // reset daily counters
                saveMeters(x);                                  // save meter values to EEPROM
            }   
        } else if(hour() != 0 && newDay)                        // reset newday flag if hour != 0
            { newDay = false;}
         
        // Every 10 seconds update one meter to controller to avoid traffic jams
        if (now-lastSyncKWH > 10000){
        //    printPulsemeter(updateMeter);
            sendPowerUpdate(updateMeter);                               // update the values for currentMeter
            updateMeter++ ;
            if (updateMeter >= NO_METERS){                              // increment and wrap current meter
                updateMeter = 0 ;}
            lastSyncKWH = now ;
            }
            
        // Update sensors every 10 secs
        if (now-lastUpdate > 10000) {
            // get values to be displayed from controller
            
            lastUpdate = now;
        }
        
        // get readings from serial (sent every 10s)
        // format {"m":meter,"c":count,"r":rate, "cA":countAccum}
        // use JSON parser to process (could be replaced by simple split routine, but this works just fine)
        if(readLineJSON(Serial.read(), json, JSON_LENGHT) > 0 ){        //dummySerial(), Serial.read()
        // if(readLineJSON(dummySerial(), json, 80) > 0 ){              //dummySerial(), Serial.read()
            Serial.println(json);
            storeMeterJSON(json);                                       //store the meter reading
            //calcMeterTotals();                                          // update totals
            }
    }
    
    // This is called when a new time value was received
    void receiveTime(unsigned long controllerTime) {
        // Ok, set incoming time 
        Serial.print(F("Time value received: "));
        Serial.println(controllerTime);
        setTime(controllerTime);                                        // set the clock to the time from controller
        timeReceived = true ;
    }
    
    // This is called when a message is received 
    void incomingMessage(const MyMessage &message) {
      // Expect few types of messages from controller, V_VAR1 for messages
        if (message.type==V_TEXT) {
            // if message comes in, update the kWH reading for meter with value since last update
            // Write some debug info
            //Serial.print("Last reading for sensor: ");
            //Serial.print(message.sensor);                
            //Serial.print(", Message: ");
            //Serial.println(message.getString());
    
            }
        }
    
    
    // save Meter to EEPROM when needed
    void saveMeters(int8_t meterNo){
        kWhLong.kWhLongInt = pulseMeters[meterNo].UsageWh ;                     // convert to separate bytes via struct
        for (int y = 0; y < 4 ; y++){
            gw.saveState(meterNo * 4 + y, kWhLong.kWhLongByte[y])  ;            // EEPROM position = meter number * 4 bytes
            }
        }
    
    void sendPowerUpdate(int meterNo)
    // Sends update to controller for current meter 
    {
        gw.send(powerMsg.setSensor(meterNo).set((long)pulseMeters[meterNo].PowerW));            // meterNo * 100 ));
        gw.send(usageMsg.setSensor(meterNo).set((float)pulseMeters[meterNo].UsageWh/1000L ,3)); // send in kWh!
    }
    
    int storeMeterJSON(char *json)
    /* convert JSON to values and store in corresponding meter (if used)
     input: JSON string (can be wrong formatted), with length
     output: changed meter record number or -1 if error
     use JsonParser 
    */
    {
      StaticJsonBuffer<50> jsonBuffer;                          // 4 object  -> 4 + 4*10 = 44
      // char njson[] = "{\"m\":1,\"c\":12,\"r\":120000,\"cA\":12345}";
        JsonObject& root = jsonBuffer.parseObject(json);
      if (!root.success())
        {
            Serial.println(F("JsonParser.parse() failed"));
            errCount++ ;
            return -1;
        }
      int m = (long)root["m"];
      if (m > NO_METERS){                                       // meter value out of range for used meters (m starts at 1)
        return -1 ;
      } else {                                                  // update meter values, Power is momentary, Usage is cumulative
        long newAccumWh = (long)root["cA"] ;
        long newWh = (long)root["c"] ;
        long diffAccumWh = newAccumWh - pulseMeters[m-1].UsageAccumWh;  // check for missed pulses by comparing cA with last stored value
        if (diffAccumWh >= newWh){                              // no difference or missed pulses -> correct: add difference
            pulseMeters[m-1].UsageWh += diffAccumWh;
            pulseMeters[m-1].DayUsageWh += diffAccumWh;
            }
        else {                                                  // negative diff, out of sync -> add pulses only (can be out of range or restart)
            pulseMeters[m-1].UsageWh += newWh;
            pulseMeters[m-1].DayUsageWh += newWh;
            }
        pulseMeters[m-1].UsageAccumWh = newAccumWh;             // always update sync counter (only for sync and error correction(serial))
        if ((long)root["r"] == 0){                              // calculate power from pulse rate (ms) and truncate to whole Watts
          pulseMeters[m-1].PowerW = 0;                          // if overflow assume no Usage
        } else if (pulseMeters[m-1].Type == meter800){
          pulseMeters[m-1].PowerW = long( 3600000000L / (long)root["r"]*0.8); // rate in microseconds for 800 pulse/kWh
          }
          else if (pulseMeters[m-1].Type == meter1000){
          pulseMeters[m-1].PowerW = long( 3600000000L / (long)root["r"]); // rate in microseconds for 1000 pulse/kWh
          }
           else if (pulseMeters[m-1].Type == meter2000){
           pulseMeters[m-1].PowerW = long( 3600000000L / (long)root["r"]/2); // rate in microseconds for 2000 pulse/kWh
          }
          else {
            Serial.println("geht nicht");
            }
        return m ;
      }
    }
    
    int readLineJSON(int readch, char *buffer, int len)
    /* checks for JSON and when started append char tot buffer and checks for line completion 
    usage:
      static char buffer[80];
      if (readline(Serial.read(), buffer, 80) > 0) { // line complete}
      returns simple JSON
      */
    {
      static int pos = 0;
      int rpos;
    
      if (readch > 0) {
        switch (readch) {
          case '\n':                // Ignore new-lines
            break;
          case '\r':                // Return on CR
            rpos = pos;
            pos = 0;                  // Reset position index ready for next time
            return rpos;
          default:
            if (pos < len-1) {
              buffer[pos++] = readch;
              buffer[pos] = 0;
            }
        }
      }
      // No end of line has been found, so return -1.
      return -1;
    }
    
    /*
    int dummySerial()
    // Acts as a dummy JSON serial character generator for debugging
    // input: none
    // output: preset JSON string
    {
      static int pos = 0;
      char json[] = "{\"m\":1,\"c\":12,\"r\":120000,\"cA\":12345}\r{\"m\":2,\"c\":212,\"r\":2120000,\"cA\":212345}\r{\"m\":3,\"c\":212,\"r\":2120000,\"cA\":212345}\n\r";
      if (pos++ >= strlen(json)){
        pos = 0;
      } 
      return json[pos] ;
      
    }
    */
    
    
    /*
    
    void printPulsemeter(int meter)
    // prints the Pulsemeter record to serial out
    {
      Serial.print("m:");
      Serial.print(meter);
      Serial.print("type:");
      Serial.print(pulseMeters[meter].Type);
      Serial.print(", power: ");
      Serial.print(pulseMeters[meter].PowerW);
      Serial.print(", usage: ");
      Serial.print(pulseMeters[meter].UsageWh);
      Serial.print(", day usage: ");
      Serial.println(pulseMeters[meter].DayUsageWh );
    //  Serial.print(", Ca: ");
    //  Serial.println(pulseMeters[meter].UsageAccumWh);
    }
    */
    


  • Good evening,

    After a couple of weeks, again i'm busy with my arduino's, but now i have a rasberry with domoticz near my arduino, so i don't need to send it wireless.
    Is there a possibility to connect the master or slave arduino directly to my rasberry and read out the pulse meters???


  • Mod

    With MySensors version 1.6 (currently in development) you can connect the Arduino to the Raspberry Pi's USB port using a FTDI or CH340G. That type of connection is called a serial gateway. Before 1.6, the gateways do not support local sensors.



  • So with 1.6 i can install the master software and connect my arduino master directly through usb without the rf24''s? ?


  • Mod

    I am not entirely sure what a master is, but yes, I think you can.



  • I meen the master sketch like written above.

    I'd want to build a gateway but without the wireless RF part, just connect the master arduino directly through usb to the RasberryPi and readout the measurements.

    So in general:

    Kwh Meters -- cable --> Slave arduino <-- serial --> Master arduino <-- USB --> RasberryPi


  • Hero Member

    @MarkV If you don't want it to be MySensors enabled and not used the display etc. it's easier to readout the slave with the raspberry. The protocol is a simple Json and if you use a nano for the slave you can connect it with usb..



  • Last weeks i'm trying to get it to work but without luck. Could you give a manual on how to get it working with json? What should we do? And how? On my rasberry, on the arduino and in domoticz??



  • @AWI
    Goodmorning,
    I've got a question, are you planning to re-write the script for the main and slave Arduino to the new MySensors 2.0 structuur?

    I've also tryed to look on github if there's a page from you, but didn't find it...


  • Hero Member

    @MarkV I updated the sketch for main a few months ago to development (pre-2.0). Also changed it a little for stability and removed the (complicated) JSON library The slave has no MySensors dependency.



  • Super.
    I've updated everything and it's up and running.
    But my Master arduino (UNO clone) gives this readout on the USB port:

    0;255;3;0;14;Gateway startup complete.
    0;255;3;0;11;AWI-12ChannelPulse
    0;255;3;0;12;2.0
    0;0;0;0;13;Groep 1
    0;1;0;0;13;Groep 2
    0;2;0;0;13;Groep 3
    0;3;0;0;13;Groep 4
    0;4;0;0;13;Groep 5
    0;5;0;0;13;Groep 6
    0;20;0;0;36;Usage meter LCD
    0;20;1;0;47;-
    0;255;3;0;1;
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    0;255;3;0;1;
    0;1;1;0;17;0
    0;1;1;0;18;-0.001
    0;255;3;0;1;
    0;2;1;0;17;0
    0;2;1;0;18;-0.001
    0;255;3;0;1;
    0;3;1;0;17;0
    0;3;1;0;18;-0.001
    0;255;3;0;1;
    0;4;1;0;17;0
    0;4;1;0;18;-0.001
    0;20;2;0;47;
    0;255;3;0;1;
    0;5;1;0;17;0
    0;5;1;0;18;-0.001
    0;255;3;0;1;
    0;0;1;0;17;0
    0;0;1;0;18;-0.001
    

    Is this correct?
    Because Domoticz doesn't change the values.
    Also this is not the format, wich is past through in the JSON code from the slave to master duino??



  • @AWI
    How is it that i get the readings like above? And after a while the sensor also stops with sending data, last connection stays on that time and date.

    I've uploaded your github sketch and done a serial readout of the measuring arduino and that one functions proberbly. No faulty low voltage errors etc.


  • Hero Member

    @MarkV Ok, one step at a time.. the output looks like the output of your (serial) gateway. Have you tried to switch on the "Master node" debug Serial.print() statements?



  • @AWI
    It the serial output from the gateway arduino, wich i've got connected through usb.
    I don't have a rotaryswitch connected and yesterday evening i connected a I2c Display just to see if something changes on that.

    I noticed one thin, when i check the serial output, the timer on the display is reset. Further more when i stop the serial readout, the counter also stops, like if the hole thing go's into a idle or stop.

    I've uncommented the debug informatie wich i could find:

    // Handle incoming messages from the MySensors Gateway
    void receive(const MyMessage &message) {  // Expect few types of messages from controller, V_VAR1 for messages
      if (message.type==V_TEXT) {
        // if message comes in, update the kWH reading for meter with value since last update
        // Write some debug info
        Serial.print("Last reading for sensor: ");
        Serial.print(message.sensor);                
        Serial.print(", Message: ");
        Serial.println(message.getString());
        if (message.sensor == LCD1_CHILD ) {
          strcpy(lastLCD1, message.getString());  // read payload in LCD string
        }
      }
    }
    

    Were could i read this debug information?
    At this moment i got my arduino hooked up through usb to my rasberry to see if it keeps sending information.

    I also changed all the cables to the meters.

    This is how one meter looks like:
    0_1462289685255_upload-a785d675-02ea-45c7-9888-2616d6a5c4f4

    And a other one:
    0_1462289751196_upload-ac4e8673-e392-43ac-aeea-1be31416479a

    All the meters are showing also strange values on the axis.

    Sorry for being such a noob at this.. :-S


  • Hero Member

    @MarkV Hi Mark It is a rather complicated sketch, so not easy to debug remote.
    You need to connect the master arduino to your USB and check with the serial monitor.
    then add some Serial.print() statements starting form where the data is read.

    The piece you commented out is where the node should receive information from the controller. I expect you didn't come far enough to set that up.

    (as I noticed you are Dutch, we can use the chat function on the forum to get this working...please don't expect immediate answers..)



  • @AWI
    Goodevening
    Oh better, we definitie have to de that. When does it suit you?

    Grtz


Log in to reply
 

Suggested Topics

  • 8
  • 44
  • 5
  • 3
  • 1
  • 2

15
Online

11.4k
Users

11.1k
Topics

112.7k
Posts