245 lines
8.5 KiB
C
245 lines
8.5 KiB
C
|
/* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
*/
|
||
|
/* Copyright (c) 2015 King Kévin <kingkevin@cuvoodoo.info> */
|
||
|
/* the spark counter transmits electricity measurements over radio
|
||
|
* the electricity measurements (voltage, current, power, energy) are query everry second from a peacefair PZEM-004 power meter
|
||
|
* they are then transmitted using an nRF2L01+ transceiver
|
||
|
*/
|
||
|
|
||
|
#include <stdint.h> // Standard Integer Types
|
||
|
#include <stdio.h> // Standard IO facilities
|
||
|
#include <stdlib.h> // General utilities
|
||
|
#include <stdbool.h> // Boolean
|
||
|
#include <string.h> // Strings
|
||
|
|
||
|
#include <avr/io.h> // AVR device-specific IO definitions
|
||
|
#include <util/delay.h> // Convenience functions for busy-wait delay loops
|
||
|
#include <avr/interrupt.h> // Interrupts
|
||
|
#include <avr/wdt.h> // Watchdog timer handling
|
||
|
#include <avr/pgmspace.h> // Program Space Utilities
|
||
|
#include <avr/sleep.h> // Power Management and Sleep Modes
|
||
|
#include <avr/eeprom.h> // EEPROM handling
|
||
|
#include <util/crc16.h> // CRC Computations
|
||
|
|
||
|
#include "main.h" // main definitions
|
||
|
#include "usart.h" // basic USART functions
|
||
|
#include "nrf24.h" // nRF24L01 functions
|
||
|
|
||
|
/* variables */
|
||
|
volatile bool query_pzem004 = false; // flag to query the PZEM-004 power meter
|
||
|
|
||
|
/* disable watchdog when booting */
|
||
|
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
|
||
|
void wdt_init(void)
|
||
|
{
|
||
|
MCUSR = 0;
|
||
|
wdt_disable();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* initialize GPIO */
|
||
|
void io_init(void)
|
||
|
{
|
||
|
/* use UART as terminal */
|
||
|
usart_init();
|
||
|
stdout = &usart_output;
|
||
|
stdin = &usart_input;
|
||
|
/* use nRF24L01 */
|
||
|
nrf24_init();
|
||
|
/* set up timer 1 to regularly wake up and request data from the power meter */
|
||
|
TCCR1B |= (0<<WGM13)|(1<<WGM12); // use timer 1 in CTC mode
|
||
|
TCCR1A |= (0<<WGM11)|(0<<WGM10); // use timer 1 in CTC mode
|
||
|
TCNT1 = 0; // reset timer 1 counter
|
||
|
OCR1A = (F_CPU/256UL)*1.0; // the time to wait before triggering. with a prescale of 256 the max is 1.05s. here I choose 1s.
|
||
|
TIMSK1 |= (1<<OCIE1A); // enable interrupt
|
||
|
|
||
|
sei(); // enable interrupts
|
||
|
}
|
||
|
|
||
|
int main(void)
|
||
|
{
|
||
|
io_init(); // initialize IOs
|
||
|
|
||
|
//printf(PSTR("welcome to the spar counter power meter monitor\n"));
|
||
|
|
||
|
/* fallback nRF24 node configuration */
|
||
|
struct configuration conf = {{1,'h','o','m','e'},{0,'h','o','m','e'},42};
|
||
|
// read configuration data from EEPROM + CRC-8-iButton value
|
||
|
uint8_t eeprom_conf[sizeof(struct configuration)+1];
|
||
|
uint8_t crc = 0; // CRC8 iButton/1Wire/Dallas/Maxim calculation
|
||
|
for (uint16_t i=0; i<sizeof(eeprom_conf); i++) {
|
||
|
eeprom_conf[i] = eeprom_read_byte((const uint8_t*)i); // read data
|
||
|
crc = _crc_ibutton_update(crc, eeprom_conf[i]); // calculate CRC
|
||
|
}
|
||
|
if (crc==0) { // CRC succeeded
|
||
|
memcpy(&conf,eeprom_conf,sizeof(struct configuration));
|
||
|
}
|
||
|
nrf24_set_rx_addr(conf.rx_addr);
|
||
|
nrf24_set_tx_addr(conf.tx_addr);
|
||
|
nrf24_set_rf_channel(conf.channel);
|
||
|
|
||
|
/* set the PZEM-004 IP address (it won't reply to other requests before) */
|
||
|
uint8_t cmd[7] = {0xB4,0xC0,0xA8,0x01,0x01,0x00,0x1E}; // set the address 192.168.1.1 (keep this address for the other commands)
|
||
|
for (uint8_t i=0; i<sizeof(cmd); i++) {
|
||
|
usart_putchar_nonblocking(cmd[i],NULL);
|
||
|
}
|
||
|
|
||
|
/* start timer to periodically query the power meter */
|
||
|
TCCR1B |= (1<<CS12)|(0<<CS11)|(0<<CS10); // set prescale to 256 (starting the timer)
|
||
|
query_pzem004 = false; // immediately start querying the power meter
|
||
|
bool queried_pzem004 = true; // all values have been queried from the power meter
|
||
|
|
||
|
/* PZEM-004 power meter values */
|
||
|
float voltage = 0, current = 0, power = 0, energy = 0; // the read values
|
||
|
bool send_values = false; // set to true to send out the values
|
||
|
|
||
|
bool action = false; // to know if we performed any king of action during which some other activity could have happend
|
||
|
while (true) { // endless loop for micro-controller
|
||
|
action = false; // new cycle of actions
|
||
|
if (query_pzem004) {
|
||
|
action = true; // an action is performed
|
||
|
query_pzem004 = false; // clear flag
|
||
|
if (queried_pzem004) { // wait until previous queries finished
|
||
|
queried_pzem004 = false;
|
||
|
// query all values (voltage, current, power, energy)
|
||
|
// start with voltage, the other will be queried after the power meter answered
|
||
|
cmd[0] = 0xb0; // query voltage
|
||
|
cmd[6] = 0x1a; // update checksum
|
||
|
for (uint8_t i=0; i<sizeof(cmd); i++) {
|
||
|
usart_putchar_nonblocking(cmd[i],NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (usart_incoming>=7) { // wait for an answer to be available
|
||
|
action = true; // an action is performed
|
||
|
//printf(PSTR("got value\n"));
|
||
|
// store answer
|
||
|
uint8_t answer[7];
|
||
|
for (uint8_t i=0; i<sizeof(answer); i++) {
|
||
|
answer[i] = usart_getchar(NULL); // save input (this updates usart_incoming)
|
||
|
}
|
||
|
// verify checksum
|
||
|
uint8_t checksum = 0;
|
||
|
for (uint8_t i=0; i<sizeof(answer)-1; i++) {
|
||
|
checksum += answer[i];
|
||
|
}
|
||
|
if (checksum==answer[sizeof(answer)-1]) { // checksum is correct
|
||
|
char str[5+1+3+1]; // store the value as string
|
||
|
switch (answer[0]) {
|
||
|
case 0xa0: // voltage
|
||
|
snprintf(str,sizeof(str),"%u.%u",(answer[1]<<8)+answer[2],answer[3]);
|
||
|
voltage = atof(str);
|
||
|
cmd[0] = 0xb1; // query current
|
||
|
cmd[6] = 0x1b; // update checksum
|
||
|
for (uint8_t i=0; i<sizeof(cmd); i++) {
|
||
|
usart_putchar_nonblocking(cmd[i],NULL);
|
||
|
}
|
||
|
break;
|
||
|
case 0xa1: // current
|
||
|
snprintf(str,sizeof(str),"%u.%u",(answer[1]<<8)+answer[2],answer[3]);
|
||
|
current = atof(str);
|
||
|
cmd[0] = 0xb2; // query power
|
||
|
cmd[6] = 0x1c; // update checksum
|
||
|
for (uint8_t i=0; i<sizeof(cmd); i++) {
|
||
|
usart_putchar_nonblocking(cmd[i],NULL);
|
||
|
}
|
||
|
break;
|
||
|
case 0xa2: // power
|
||
|
snprintf(str,sizeof(str),"%u.%u",(answer[1]<<8)+answer[2],answer[3]);
|
||
|
power = atof(str);
|
||
|
cmd[0] = 0xb3; // query energy
|
||
|
cmd[6] = 0x1d; // update checksum
|
||
|
for (uint8_t i=0; i<sizeof(cmd); i++) {
|
||
|
usart_putchar_nonblocking(cmd[i],NULL);
|
||
|
}
|
||
|
break;
|
||
|
case 0xa3: // energy
|
||
|
energy = (((uint32_t)answer[1])<<16)+(answer[2]<<8)+answer[3];
|
||
|
queried_pzem004 = true; // all have been queried
|
||
|
send_values = true; // this should be the last of the 4 values we requested. now send them
|
||
|
break;
|
||
|
case 0xa4: // address
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (send_values) { // send the stored values from the power meter using the nRF24L01
|
||
|
action = true; // an action is performed
|
||
|
send_values = false; // clear flag
|
||
|
uint8_t message[3+6+6+6+6]; // create TLV based message
|
||
|
uint8_t i = 0;
|
||
|
message[i++] = 0; // type: message source
|
||
|
message[i++] = 1; // length
|
||
|
message[i++] = 1; // value: home 1
|
||
|
message[i++] = 1; // type: voltage
|
||
|
message[i++] = 4; // length
|
||
|
memcpy(&message[i],&voltage,4); // value
|
||
|
i += 4; // value
|
||
|
message[i++] = 2; // type: current
|
||
|
message[i++] = 4; // length
|
||
|
memcpy(&message[i],¤t,4); // value
|
||
|
i += 4; // value
|
||
|
message[i++] = 3; // type: power
|
||
|
message[i++] = 4; // length
|
||
|
memcpy(&message[i],&power,4); // value
|
||
|
i += 4; // value
|
||
|
message[i++] = 4; // type: energy
|
||
|
message[i++] = 4; // length
|
||
|
memcpy(&message[i],&energy,4); // value
|
||
|
i += 4; // value
|
||
|
nrf24_transmit(message,i);
|
||
|
}
|
||
|
if (nrf24_flag) {
|
||
|
action = true; // an action was performed
|
||
|
nrf24_flag = false; // reset flag
|
||
|
uint8_t activity = nrf24_activity();
|
||
|
if (activity&TX_SUCCEEDED) {
|
||
|
//printf(PSTR("transmission succeeded\n"));
|
||
|
}
|
||
|
if (activity&TX_FAILED) {
|
||
|
//printf(PSTR("transmission failed\n"));
|
||
|
}
|
||
|
if (activity&RX_RECEIVED) {
|
||
|
//printf(PSTR("received data\n"));
|
||
|
}
|
||
|
if (activity&RX_AVAILABLE) {
|
||
|
//printf(PSTR("data available\n"));
|
||
|
// read all RX FIFO to clear them
|
||
|
while (nrf24_data_available()) {
|
||
|
uint8_t data[32];
|
||
|
nrf24_rx_payload(data,sizeof(data));
|
||
|
//uint8_t size = nrf24_rx_payload(data,sizeof(data));
|
||
|
//printf("got %d bytes\n",size);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/* go to sleep and wait for next interrupt */
|
||
|
if (!action) { // only go to sleep if no action had to be performed
|
||
|
set_sleep_mode(SLEEP_MODE_IDLE);
|
||
|
sleep_mode();
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* timer 1 triggered
|
||
|
* start querying the power meter
|
||
|
*/
|
||
|
ISR(TIMER1_COMPA_vect)
|
||
|
{
|
||
|
query_pzem004 = true; // warm main programm to start querying
|
||
|
}
|