kWh i ackumulatortankar
Experiment med HC-12 transceivers

Displayen visar temeperatur pÄ 5 punkter pÄ ackumulatortanken.
Med temperartur och berÀknad kWh.
Shunten visar raw vÀrden direkt frÄn potentiometern.
NÀr jag vet grÀnsvÀrden, kommer den att göras om till procent.
Status frÄn elpatronen visar vitt vid av, och rött vid pÄ.
Total visar hur mÄnga kWh som finns i tanken och procent laddat.
Med temperartur och berÀknad kWh.
Shunten visar raw vÀrden direkt frÄn potentiometern.
NÀr jag vet grÀnsvÀrden, kommer den att göras om till procent.
Status frÄn elpatronen visar vitt vid av, och rött vid pÄ.
Total visar hur mÄnga kWh som finns i tanken och procent laddat.

Motor för shunten

Se uppdatering Potentiometer pÄ shunt

En universal shield till Uno har XH2.54 kontakter

Och nÄgra pull-up motstÄnd Àr kopplade till XH2.54 kontakterna pÄ10K

Uno kort med shielden monterat i en lÄda med en step-down converter till 9 Volt
SÀndaren HC-12 Àr monterad stÄende pÄ shielden och en extern antenn pÄ lÄdan.
SÀndaren HC-12 Àr monterad stÄende pÄ shielden och en extern antenn pÄ lÄdan.

Den externa antennen med smÀlt lim som stöd.

En av de 5 temperatursensorerna som Àr NTC10K och ett 10K motstÄnd pÄ shielden.

PÄ elpatronen Àr en ljussensensor monterad med skydd för solljus.

Mottagaren Àr en Mega2560 med shield och en display och en HC-12 pÄ toppen.

Systemet Àr inte kalibrerat men ger en indikation pÄ temperatur och kWh.
Koden för sÀndaren vid ackumulator tankarna.
Det Àr en enkel kod som lÀser av data och skickar data via RF.
Det Àr en enkel kod som lÀser av data och skickar data via RF.
PlaytformIO, sÀndaren. fil: 'main.cpp'
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <math.h>
/* ---------- FUNCTION PROTOTYPES ---------- */
uint8_t calcCRC(const char *data);
void hc12Command(const char* cmd);
void initHC12();
float readNTC(int pin);
void sendPacket();
/* ---------- PIN ----------
* HC12 RX, TX och SET anvÀnds för att kommunicera med HC12 modulen.
* RX och TX anvÀnds för att skicka data till HC12 modulen.
* SET anvÀnds för att sÀtta HC12 modulen i sÀndningslÀge.
* Den ska vara HIGH nÀr man skickar data och LOW nÀr man inte skickar data Àr dÄ i AT-command lÀge.
*/
#define HC12_RX 9
#define HC12_TX 10
#define HC12_SET 4
/* ---------- TEMP ----------
* Vita XH-2.54-2P-kontakter anvÀnds för att ansluta temperaturgivarna.
* Före en NTC 10 kOhm 3950 ska pull-up-motstÄndet normalt vara: 10 kOhm
* 5 temperature sensors Àr NTC 10 kOhm 3950 med pull-up motstÄnd pÄ 10 kOhm frÄn 5 Volt.
* De Àr placerade pÄ olika stÀllen av akumulatortanken.
* Akumulatortanken Àr pÄ 750 liter, sÄ 750 / 5 = 150 liter per sensor.
* Ăr det vintertid och bĂ„da tankarna Ă€r paralellt ihop kopplade sĂ„ Ă€r 1500 / 5 = 300 liter per sensor.
* Om 80 och efter 35 grader gÄr det inte att ta ut mera energi sÄ Àr det 80 - 35 = 45 grader c
* DÄ Àr det 7.9 kW per sensor vid 150 liter och 15.8 kW per sensor vid 300 liter.
* Eller 7.9 / 45 = 0.175 kW per grad vid 150 liter och 15.8 / 45 = 0.35 kW per grad vid 300 liter.
*/
#define TEMP1 A0
#define TEMP2 A1
#define TEMP3 A2
#define TEMP4 A3
#define TEMP5 A4
#define D1_PIN 5 //BlÄ XH2.54-2P kontakt anvÀnds ej
#define D2_PIN 6 //BlÄ XH2.54-2P kontakt anvÀnds ej
#define D3_PIN 7 //Vit XH2.54-3P kontakt Elpatron pÄ eller av. Ljussensor pÄ elpatronens lampa
/* ---------- ANALOG RANGE ----------
* Vit XH2.54-4P kontakt anvÀnds för att lÀsa av shuntens position, en pinne anÀnds inte.
* Denna Àr en extra 10 Kohm potentiometer som anvÀnds för att lÀsa av shuntens position.
* Rörelse 90 grader av potentiometern motsvarar 0-100% öppning av shunten.
* Det Àr inte en exakt avlÀsning, utan mer en uppskattning av hur mycket shunten Àr öppen.
*/
#define ANA1 A5
#define A1_MIN 300
#define A1_MAX 700
/* ---------- NTC CONST ---------- */
#define SERIES_RESISTOR 10000
#define NOMINAL_RESISTANCE 10000
#define NOMINAL_TEMPERATURE 25
#define B_COEFFICIENT 3950
/* ---------- SERIAL ---------- */
SoftwareSerial HC12(HC12_RX, HC12_TX);
/* ---------- TIMER ---------- */
unsigned long lastSend=0;
const unsigned long sendInterval=60000; // 60 sekunder mellan varje sÀndning
/* ---------- D3 Àndras ---------- */
int lastD3State = HIGH;
unsigned long lastChangeTime = 0;
#define CHANGE_DEBOUNCE 50 // Skydd mot kontaktstuds och brus pÄ D3, i millisekunder
/**
* LÀs temperatur frÄn en NTC-termistor kopplad till angiven analog pinne.
*
* - LĂ€ser ADC med analogRead(pin) (10-bit: 0..1023).
* - Skyddar mot kantvÀrden 0 och 1023 (delning med noll) och returnerar NAN i sÄdana fall.
* - BerÀknar termistorns resistans i ett spÀnningsdelararrangemang med SERIES_RESISTOR.
* Formeln: R_ntc = SERIES_RESISTOR * adc / (1023.0 - adc)
* - AnvĂ€nder B-parameter-modellen (förenklad SteinhartâHart) för att rĂ€kna ut temperatur i °C.
*
* @param pin Analog ingÄngspin för NTC (A0..An).
* @return Temperatur i °C (float). Returnerar NAN vid ogiltigt ADC-vÀrde.
*/
float readNTC(int pin) {
int adc = analogRead(pin);
// Skydda mot delning med noll eller extremvÀrden som ger ogiltig berÀkning
if (adc <= 0 || adc >= 1023) {
return NAN;
}
// R_ntc = R_series * adc / (1023 - adc)
float resistance = SERIES_RESISTOR * (static_cast<float>(adc) / (1023.0f - static_cast<float>(adc)));
// SteinhartâHart (B-parameter) berĂ€kning
float steinhart = resistance / NOMINAL_RESISTANCE; // (R/Ro)
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= B_COEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0f / (NOMINAL_TEMPERATURE + 273.15f);// + 1/To
steinhart = 1.0f / steinhart; // K
steinhart -= 273.15f; // till °C
return steinhart;
}
/**
* sendPacket
*
* Samlar in sensor- och ingÄngsvÀrden, bygger en textbaserad telemetripayload,
* berÀknar en 8-bitars CRC och skickar hela paketet över HC-12-modulen.
* Skriver Àven ut det skickade paketet till Serial-konsolen.
*
* Funktionalitet:
* - LĂ€ser fem temperaturer via readNTC(TEMP1..TEMP5) och konverterar varje
* float till kort ASCII med dtostrf().
* - LÀser en analog vÀrde av shunt potentiometer som raw.
* - LÀser tre digitala ingÄngar med digitalRead(D1_PIN..D3_PIN).
* - Formaterar en payload i buffern (payload, storlek 140):
* "T1=<t1>;T2=<t2>;T3=<t3>;T4=<t4>;T5=<t5>;A1=<percent>;D1=<d1>;D2=<d2>;D3=<d3>"
* - BerÀknar 8-bit CRC över payload med calcCRC(payload).
* - Bygger ett paket i buffern (packet, storlek 160) som omsluter payload och CRC:
* "<<payload>;CRC=<XX>>"
* dÀr <XX> Àr CRC i tvÄ stora hex-siffror.
* - SÀtter HC12_SET HIGH, vÀntar 10 ms, skickar paket med HC12.println(packet),
* vÀntar 10 ms och sÀtter sedan HC12_SET LOW.
* - Skriver det skickade paketet till Serial för debug.
*
* Noteringar och sidoeffekter:
* - Funktionen blockerar (anvÀnder delay()).
* - Utför I/O: digitalWrite(HC12_SET), HC12.println(...), Serial.println(...).
* - AnvÀnder globala konstanter/objekt: TEMP1..TEMP5, ANA1, D1_PIN..D3_PIN, HC12_SET,
* HC12 och Serial.
* - Förlitar sig pÄ hjÀlpfunktioner: readNTC(), calcCRC(), dtostrf(), snprintf().
* - Buffertstorlekar Àr begrÀnsade (payload: 140 byte, packet: 160 byte). snprintf
* trunkerar om strĂ€ngen blir för lĂ„ng â sĂ€kerstĂ€ll att format och strĂ€nglĂ€ngder
* hÄller sig inom grÀnserna för att undvika informationsförlust.
*
* Ă
teranvÀndbarhet / trÄdsÀkerhet:
* - Ej reentrent: anvÀnder delade hÄrdvaruresurser (HC12, Serial) och globala pinnar.
*
* Retur:
* - void. Transmitterar data som sidoeffekt; ingen felstatus returneras.
*/
void sendPacket(){
float t1 = readNTC(TEMP1);
float t2 = readNTC(TEMP2);
float t3 = readNTC(TEMP3);
float t4 = readNTC(TEMP4);
float t5 = readNTC(TEMP5);
int raw = analogRead(ANA1);
int d1 = digitalRead(D1_PIN);
int d2 = digitalRead(D2_PIN);
int d3 = digitalRead(D3_PIN);
char packet[160];
char payload[140];
char t1s[10], t2s[10], t3s[10], t4s[10], t5s[10];
dtostrf(t1,0,1,t1s);
dtostrf(t2,0,1,t2s);
dtostrf(t3,0,1,t3s);
dtostrf(t4,0,1,t4s);
dtostrf(t5,0,1,t5s);
snprintf(payload,sizeof(payload),
"T1=%s;T2=%s;T3=%s;T4=%s;T5=%s;A1=%d;D1=%d;D2=%d;D3=%d",
t1s,t2s,t3s,t4s,t5s,raw,d1,d2,d3);
uint8_t crc = calcCRC(payload);
snprintf(packet,sizeof(packet),
"<%s;CRC=%02X>",payload,crc);
digitalWrite(HC12_SET,HIGH);
delay(10);
HC12.println(packet);
delay(10);
digitalWrite(HC12_SET,LOW);
Serial.println("Sent:");
Serial.println(packet);
}
/**
* Skicka en kommandostrÀng till HC-12 medan modulen sÀtts i kommandolÀge.
*
* - SÀtter HC12_SET lÄg för att gÄ in i AT-lÀge (HC12_SET mÄste vara definierad och konfigurerad som OUTPUT).
* - VÀntar 50 ms sÄ att modulen hinner byta lÀge.
* - Skriver den nullterminerade cmd-strÀngen till HC12 (anvÀnder den globala HC12-objektet).
* - VÀntar 200 ms efter sÀndning sÄ att modulen hinner bearbeta kommandot.
*
* Viktigt:
* - Funktionen blockerar (anvÀnder delay) och fÄr inte anvÀndas i avbrottshanterare eller andra tidskritiska sammanhang.
* - HC12_SET lÀmnas LOW nÀr funktionen returnerar; anroparen ansvarar för att sÀtta den HIGH om sÄ önskas.
* - Funktionen lÀgger inte automatiskt till radslut (CR/LF); inkludera dessa i cmd-strÀngen om modulen krÀver dem.
* - Ingen felrapportering utförs.
*
* FörutsÀttningar:
* - HC12 (SoftwareSerial) mÄste vara initierad.
* - HC12_SET mÄste vara definierad och konfigurerad som OUTPUT.
* - cmd mÄste vara en giltig, nullterminerad C-strÀng (nullptr ger undefined behaviour).
*
* Exempel:
* hc12Command("AT+VERSION\r\n");
*/
void hc12Command(const char* cmd){
digitalWrite(HC12_SET,LOW);
delay(50);
HC12.print(cmd);
//HC12.print("\r\n");
delay(200);
}
/**
* Initierar och konfigurerar HC-12 trÄdlös transceiver.
*
* Beskrivning:
* - Skriver en initieringsrad till Serial för felsökning.
* - SÀtter HC12 i AT-lÀge genom att styra HC12_SET-pinnen och vÀntar kort.
* - Skickar ett antal AT-kommandon via hc12Command() för att stÀlla in kanal,
* uteffekt och driftlÀge:
* - "AT" : kontroll/test av anslutning
* - "AT+C003" : vÀlj kanal 3
* - "AT+P5" : sÀtt uteffekt till nivÄ ~10 mW
* - "AT+FU3" : vÀlj stabilt driftlÀge
* - LÀmnar slutligen modulen i normalt (mottagnings)lÀge och vÀntar kort.
*
* Biverkningar/krav:
* - Funktionen blockerar körningen p.g.a. anvÀndning av delay().
* - FörutsÀtter att HC12_SET, Serial och funktionen hc12Command() Àr korrekt
* definierade och initialiserade i omgivande kod.
*
* Parametrar: inga
* ReturvÀrde: ingen (void)
*/
void initHC12(){
Serial.println("Init HC12");
// Se till att HC12 Àr i AT-lÀge innan vi skickar kommandon
digitalWrite(HC12_SET, LOW);
delay(50);
hc12Command("AT");
hc12Command("AT+C003"); // kanal 3
hc12Command("AT+P7"); // ~??mW
hc12Command("AT+FU3"); // stabilt lÀge
hc12Command("AT+RX"); // get all
// LÀmna HC12 i normalt (mottagnings)lÀge efter init
digitalWrite(HC12_SET, LOW);
delay(50);
}
/**
* BerÀkna enkel 8-bit XOR-checksumma över en nullterminerad C-strÀng.
*
* Itererar över varje byte i strÀngen (terminerande '\0' ingÄr inte) och
* kombinerar dem med bitvis XOR. Om en nullpekare skickas tillbaka 0.
*
* @param data Pekare till nullterminerad C-strÀng.
* @return 8-bit checksum som uint8_t.
*/
uint8_t calcCRC(const char *data){
if(data == nullptr) return 0;
uint8_t crc = 0;
while(*data){
crc ^= static_cast<uint8_t>(*data++);
}
return crc;
}
/**
* @brief Initiera pins, seriella grÀnssnitt och HC-12.
*
* Konfigurerar HC12_SET som OUTPUT och sÀtter den LOW, aktiverar interna pull-ups
* för D1/D2/D3, startar Serial och HC12-SoftwareSerial, vÀntar kort och kör initHC12().
* LÀser Àven initialt D3-tillstÄnd för att undvika falsk Àndringsdetektion direkt efter boot.
*/
void setup() {
pinMode(HC12_SET, OUTPUT);
digitalWrite(HC12_SET, LOW);
pinMode(D1_PIN, INPUT_PULLUP);
pinMode(D2_PIN, INPUT_PULLUP);
pinMode(D3_PIN, INPUT_PULLUP);
Serial.begin(9600);
HC12.begin(9600);
delay(300); // lÄt hÄrdvara och serial stabilisera
initHC12();
// LÀs initialt D3-tillstÄnd och tidsstÀmpel för debounce-logiken
lastD3State = digitalRead(D3_PIN);
lastChangeTime = millis();
Serial.println("HC12 Ready");
}
/**
* loop()
*
* Huvudloopen som gör tre saker varje varv:
*
* 1) Periodisk sÀndning
* - Kontrollerar om tiden sedan senaste sÀndning (millis() - lastSend)
* Àr >= sendInterval. Om ja sÄ ökar lastSend med sendInterval och
* anropar sendPacket() för att skicka.
* - Att addera intervallet (lastSend += sendInterval) minskar kumulativt
* tidsdrift jÀmfört med att sÀtta lastSend = millis().
*
* 2) AvkÀnning av förÀndring pÄ D3 med debounce
* - LÀser aktuell pinne och jÀmför mot lastD3State.
* - Om förÀndring och mer Àn CHANGE_DEBOUNCE ms har passerat sÄ accepteras
* förÀndringen: uppdatera lastChangeTime, lastD3State, skriv debugmeddelande
* och anropa sendPacket().
*
* 3) Debug/ vidarebefordran av inkommande HC12-data
* - Medan HC12.available() lÀser varje byte och skriver den till Serial.
*
* Kommentarer:
* - TidsjÀmförelser anvÀnder millis()-differenser och Àr robusta mot rullning.
* - Debounce-tiden kan behöva finjusteras för den faktiska hÄrdvaran.
*/
void loop(){
/* TIMER SĂNDNING */
if(millis() - lastSend >= sendInterval){
lastSend += sendInterval;
sendPacket();
}
/* D3 CHANGE DETECT */
int current = digitalRead(D3_PIN);
if(current != lastD3State){
if(millis() - lastChangeTime > CHANGE_DEBOUNCE){
lastChangeTime = millis();
lastD3State = current;
Serial.println("D3 changed â sending packet");
sendPacket();
}
}
/* DEBUG RX */
while(HC12.available())
Serial.write(HC12.read());
}
Koden för mottagaren.
Det Àr en enkel kod som visar inkommande data pÄ en liten display.
Det Àr en enkel kod som visar inkommande data pÄ en liten display.
PlaytformIO, mottagaren. fil: 'main.cpp'
#include <Arduino.h>
#include <U8g2lib.h>
#include <Arduino_GFX_Library.h>
/*
* Koden Àr skapad av PchButik.se med hjÀlp av AI
* HC-12 VCC â 5V
* HC-12 GND â GND
* HC-12 TXD â Pin 19 (RX1)
* HC-12 RXD â Pin 18 (TX1)
* HC-12 SET â pin 4 (SET)
* Query-kommandon anvÀnder R-prefix (RC/RP/RB/RF)
* ?-kommandon stöds inte i denna firmwaregren
* AT+RX fungerar alltid och Àr sÀkraste statusdump
*/
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define ORANGE 0xFD20
/* ---------- HC12 SERIAL ---------- */
#define HC12 Serial1
//AT-kommandon GET
const char* AT_GET_VERSION = "AT+V";
const char* AT_GET_BAUD = "AT+RB";
const char* AT_GET_CHANNEL = "AT+RC";
const char* AT_GET_POWER = "AT+RP";
const char* AT_GET_RADIO_MODE = "AT+RF";
const char* AT_GET_ALL = "AT+RX";
//Pinnar
#define HC12_SET 4
#define WINTER_MODE 1 // 0 = sommar (1 tank) | 1 = vinter (2 tankar)
#define LITER_PER_SENSOR 150.0
#define MIN_TEMP 35.0
#define MAX_TEMP 85.0
#define WATER_CP 4.186 // kJ/kg°C
/* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */
Arduino_DataBus *bus = new Arduino_HWSPI(8,10);
Arduino_GFX *gfx = new Arduino_ST7735(bus, 9, 3, false,128, 160, 2, 1, 2, 1,false); //art. 3273 // MÄste anpassas för din skÀrm.
/* ---------- DATA STORAGE ---------- */
float T[5]={0};
int shuntRaw=0;
int D1=0,D2=0,D3=0;
/* ----------Functions------ */
/* ---------- CRC ---------- */
/**
* @brief BerÀkna en 8-bitars kontrollsumma genom XOR av varje byte i en nullterminerad strÀng.
*
* Itererar över bytes i den angivna null-terminerade C-strÀngen och
* ackumulerar en enkel kontrollsumma genom bitvis XOR av varje byte
* i en 8-bitars accumulator. Detta Àr en lÀttviktig checksumma (inte
* en formell CRC) och ger grundlÀggande felupptÀckt.
*
* @param data Pekare till en nullterminerad C-strÀng. Bearbetningen
* stoppar vid första NUL ('\0'). För binÀra buffertar som
* kan innehÄlla NUL, anvÀnd en variant som tar en lÀngd.
*
* @return uint8_t Resultatet av 8-bitars XOR-checksumman. Tom strÀng ger 0.
*
* @note Varje char behandlas som en rÄ byte; tecknets signedness hanteras
* genom konvertering till uint8_t vid XOR. Komplextet Àr O(n).
*/
uint8_t calcCRC(const char *data){
uint8_t crc=0;
while(*data) crc ^= *data++;
return crc;
}
/* ---------- PAKETPARSER ---------- */
/**
* parsePacket
*
* Parsar en skrivbar, null-terminerad ASCII-paketstrÀng som innehÄller sensordata/telemetri
* och en avslutande CRC-markör (";CRC=xx"). Om CRC stÀmmer extraheras numeriska vÀrden
* frÄn vÀlkÀnda nycklar och lagras i globala variabler.
*
* Parametrar:
* msg - pekare till en skrivbar, null-terminerad C-strÀng med paketet. Funktionen
* kommer att modifiera bufferten (ersÀtter ';' före CRC med '\0').
*
* Beteende:
* 1. Söker efter substringen ";CRC=" i msg. Om den inte finns returneras funktionen omedelbart.
* 2. LÀser det hexadecimala CRC-vÀrdet som följer ";CRC=" med strtol(...,16) till rxCRC (uint8_t).
* 3. ErsÀtter ';' vid CRC-markören med '\0' för att terminera meddelandedelen som ska CRC-kalkyleras.
* 4. BerÀknar CRC över den trunkerade msg med calcCRC(msg) och jÀmför mot rxCRC.
* Vid avvikelse skrivs "CRC FAIL" till Serial och funktionen returnerar.
* 5. Om CRC matchar söker funktionen i den trunkerade msg efter följande nycklar (strstr):
* - "T1=", "T2=", "T3=", "T4=", "T5=" -> parsas med atof(...) och sparas i T[0..4]
* - "A1=" -> parsas med atoi(...) och sparas i shuntRaw
* - "D1=", "D2=", "D3=" -> parsas med atoi(...) och sparas i D1, D2, D3
* För varje funnen nyckel startar numerisk konvertering direkt efter '='. Om en nyckel
* saknas Àndras den inte.
*
* Biverkningar / globala som Àndras:
* - Ăverskriver *crcPos med '\0' (modifierar msg-bufferten).
* - Kan skriva "CRC FAIL" till Serial vid CRC-missmatch.
* - Uppdaterar globala: T[] (0..4), shuntRaw, D1, D2, D3.
*
* FörutsÀttningar / noteringar:
* - msg mÄste vara skrivbar (funktionen skriver en NUL).
* - msg mÄste vara null-terminerad.
* - CRC-vÀrdet mÄste vara hexadecimalt; strtol accepterar bÄde "0x" och rena hex-siffror.
* - calcCRC(msg) förvÀntas vara kompatibel med det insÀnda vÀrdet.
* - atof/atoi anvÀnds för konvertering:
* - atof accepterar decimalpunkt '.' för brÄkdel.
* - atoi/atof returnerar 0 vid parse-fel eller om vÀrdet Àr noll.
*
* Exempelpaket:
* "T1=23.5;T2=19.0;A1=1023;D1=1;D2=0;CRC=AB"
*
* Felhantering:
* - Om ";CRC=" saknas eller CRC inte matchar returnerar funktionen tidigt och Àndrar inte
* de numeriska globala (förutom modifiering av msg-bufferten).
*/
void parsePacket(char *msg){
char *crcPos = strstr(msg,";CRC=");
if(!crcPos) return;
uint8_t rxCRC = strtol(crcPos+5,NULL,16);
*crcPos=0;
if(calcCRC(msg)!=rxCRC){
Serial.println("CRC FAIL");
return;
}
char *p;
if((p=strstr(msg,"T1="))) T[0]=atof(p+3);
if((p=strstr(msg,"T2="))) T[1]=atof(p+3);
if((p=strstr(msg,"T3="))) T[2]=atof(p+3);
if((p=strstr(msg,"T4="))) T[3]=atof(p+3);
if((p=strstr(msg,"T5="))) T[4]=atof(p+3);
if((p=strstr(msg,"A1="))) shuntRaw=atoi(p+3);
if((p=strstr(msg,"D1="))) D1=atoi(p+3);
if((p=strstr(msg,"D2="))) D2=atoi(p+3);
if((p=strstr(msg,"D3="))) D3=atoi(p+3);
}
/* ---------- DRAW BAR ---------- */
/**
* @brief Ritar en horisontell progress-/fyllningsstapel med vit kant och fÀrgad inre del.
*
* @param x VÀnster pixelkoordinat för ytterrektangeln.
* @param y Ăvre pixelkoordinat för ytterrektangeln.
* @param w Bredd i pixlar för ytterrektangeln (bör vara >= 2 för inre yta).
* @param h Höjd i pixlar för ytterrektangeln (bör vara >= 2).
* @param value Aktuellt vÀrde som ska representeras; mappas proportionellt till fyllbredden.
* @param max VÀrdet som motsvarar fullt fylld stapel (mÄste vara > 0 för korrekt beteende).
* @param col 16-bit fÀrg som anvÀnds för fyllningen.
*
* Beteende:
* - Ritar en vit ytterrektangel pÄ (x, y) med storlek (w, h).
* - BerÀknar inre fyllbredd med map(value, 0, max, 0, w - 2).
* - Fyller den inre rektangeln pÄ (x+1, y+1) med bredd = fill och höjd = h - 2 i fÀrgen col.
*
* Noteringar / förutsÀttningar:
* - Funktionen förutsÀtter att en global pekare `gfx` och fÀrgkonstanten WHITE finns.
* - För att undvika extrapolering bör caller klippa value till intervallet [0, max].
* - Om w < 2 eller h < 2 blir inre yta <= 0; beteendet Àr ej definierat.
* - Om max == 0 blir mappningen felaktig; sÀkerstÀll max > 0 innan anrop.
*/
void drawBar(int x,int y,int w,int h,int value,int max,uint16_t col){
gfx->drawRect(x,y,w,h,WHITE);
int fill = map(value,0,max,0,w-2);
gfx->fillRect(x+1,y+1,fill,h-2,col);
}
/**
* @brief BerÀknar energi som krÀvs för att vÀrma vattnet för en sensor.
*
* Funktionen:
* - BegrÀnsar temperaturen till intervallet [MIN_TEMP, MAX_TEMP].
* - BerÀknar temperaturskillnaden frÄn MIN_TEMP.
* - BestÀmmer antalet tankar: 2.0 om WINTER_MODE Àr sann, annars 1.0.
* - Konverterar volym till massa: mass = LITER_PER_SENSOR * tanks (1 L â 1 kg).
* - BerÀknar energi i kilojoule: kJ = mass * WATER_CP * (temp - MIN_TEMP).
* - Returnerar energi i kilowattimmar: kWh = kJ / 3600.0.
*
* @param temp MÄlvÀrme i samma enhet som MIN_TEMP/MAX_TEMP (vanligtvis °C).
* @return Energi i kWh. Observera att vÀrdet ibland visas som "kWh" i UI:t,
* men den hÀr funktionen returnerar kWh.
*
* Antaganden/Kommentarer:
* - Om temp < MIN_TEMP sÀtts temp = MIN_TEMP (resultat = 0).
* - Om temp > MAX_TEMP sÀtts temp = MAX_TEMP.
* - Inga sidoeffekter; ren berÀkning som anvÀnder globala konstanter:
* MIN_TEMP, MAX_TEMP, WINTER_MODE, LITER_PER_SENSOR, WATER_CP.
*/
float calcEnergy(float temp){
if(temp < MIN_TEMP) temp = MIN_TEMP;
if(temp > MAX_TEMP) temp = MAX_TEMP;
float delta = temp - MIN_TEMP;
float tanks = WINTER_MODE ? 2.0 : 1.0;
float mass = LITER_PER_SENSOR * tanks; // kg (1L â 1kg)
float kJ = mass * WATER_CP * delta;
return kJ / 3600.0;
}
/**
* ---------- DRAW SCREEN ----------
* void drawScreen()
*
* Renderar hela UI-skÀrmen som visar tanktemperaturer, per-sensor energi,
* en aggregerad totalenergibar, shuntens rÄvÀrde och en digital indikator.
*
* Beteende:
* - Rensar displayen och stÀller in standardkursor/textfÀrg.
* - Skriver en rubrik "Tank:".
* - För varje av 5 temperaturgivare (T[0]..T[4]):
* - Anropar calcEnergy(T[i]) för att berÀkna energibidraget (kWh).
* - Ackumulerar totalEnergy och maxTotal (baserat pÄ LITER_PER_SENSOR, WINTER_MODE, WATER_CP och temperaturrange).
* - Skriver ut temperaturen (1 decimal) följt av sensorns energi i kW (1 decimal).
* - Ritar en horisontell röd stapel som representerar temperaturen med drawBar pÄ en fix x-position.
* - BerÀknar percent = (totalEnergy / maxTotal) * 100 och visar:
* - "TOTAL:"-etikett, totalEnergy (1 decimal, kWh) och percent (0 decimaler, %).
* - En grön totalenergiprogressbar lÀngst ner.
* - Visar "Shunt:" och aktuellt shuntRaw-vÀrde pÄ en fast position.
* - Visar "El:" och en statuscirkel (vit nÀr digital ingÄng D3 Àr sann, röd nÀr falsk).
*
* Antaganden / beroenden:
* - Pekaren `gfx` finns och erbjuder:
* fillScreen(color), setCursor(x,y), setTextColor(color), print(...), println(...), fillCircle(x,y,r,color).
* - HjÀlpfunktioner och globala som anvÀnds:
* calcEnergy(float temp) -> float (returnerar kWh),
* drawBar(int x,int y,int w,int h,int value,int max,uint16_t col),
* Konstanter: BLACK, WHITE, RED, GREEN, WINTER_MODE, LITER_PER_SENSOR, WATER_CP, MAX_TEMP, MIN_TEMP.
* Globala: float T[5], int shuntRaw, digitala D3.
*
* Koordinat-/layoutnoteringar (sÄ som implementerat):
* - Titel och per-sensor text börjar vid (0,0); vertikalt mellanrum â 20 px.
* - Per-sensor staplar ritas vid x=80, y = i*20+2, storlek 80x12.
* - Shunt skrivs nÀra y=78; "El:" och dess statuscirkel nÀra y=91 med cirkelns centrum i (30,93), radie 5.
* - Total summering skrivs vid y=105; totalbar vid y=115 med bredd 158 och höjd 12.
*
* Kanthantering & förslag:
* - Skydda mot division med noll nÀr maxTotal == 0 innan berÀkning av percent.
* - Byt ut magiska tal (positioner, storlekar, antal sensorer) mot namngivna konstanter för bÀttre underhÄll.
* - ĂvervĂ€g klippning/avrundning och kulturberoende formatering för visade vĂ€rden.
* - Om uppdateringsfrekvens eller flimmer Àr ett problem: uppdatera endast de regioner som Àndrats istÀllet för att rensa hela skÀrmen varje bildruta.
*
* TrÄdsÀkerhet / sidoeffekter:
* - Inte trÄdsÀker; uppdaterar delad displaystatus och lÀser globala variabler.
*/
void drawScreen(){
gfx->fillScreen(BLACK);
gfx->setCursor(0,0);
gfx->setTextColor(WHITE);
gfx->setCursor(0,0);
gfx->println("Tank:");
float totalEnergy=0;
float maxTotal=0;
float tanks = WINTER_MODE ? 2.0 : 1.0;
float mass = LITER_PER_SENSOR * tanks;
float maxPerSensor = (mass * WATER_CP * (MAX_TEMP-MIN_TEMP))/3600.0;
/* TEMPERATURES + ENERGY */
for(int i=0;i<5;i++){
float e = calcEnergy(T[i]);
totalEnergy += e;
maxTotal += maxPerSensor;
// gfx->print("T");
// gfx->print(i+1);
// gfx->print(": ");
gfx->print(T[i],1);
gfx->print("C ");
gfx->print(e,1);
gfx->println("kWh");
drawBar(80, i*20+2,80,12,T[i],100,RED);
}
/* TOTAL ENERGY BAR */
float percent = (totalEnergy/maxTotal)*100.0;
gfx->setCursor(0,105);
gfx->print("TOTAL:");
gfx->print(totalEnergy,1);
gfx->print("kWh ");
gfx->print(percent,0);
gfx->println("%");
drawBar(0,115,158,12,percent,100,GREEN);
/* SHUNT RAW */
gfx->setCursor(0,78);
gfx->print("Shunt:");
gfx->println(shuntRaw);
/* DIGITAL */
gfx->setCursor(0,91);
gfx->print("El:");
gfx->fillCircle(30,93,5, D3?WHITE:RED);
}
/**
* ---------- SETUP ---------- *
*
* @brief Initierar hÄrdvaruperiferier och förbereder mottagarens UI.
*
* Körs en gÄng vid programstart. Utför följande:
* - Initierar hÄrdvaru-Serial för debug pÄ 9600 baud.
* - Initierar HC12-radio pÄ 9600 baud.
* - Initierar displayobjektet, rensar skÀrmen till BLACK och sÀtter textstorlek till 1.
* - Skriver "Receiver ready" till Serial-konsolen.
*
* @note Antas att globala objekt/handtag Serial, HC12 och gfx Àr korrekt konstruerade
* och tillgÀngliga innan denna funktion anropas.
* @pre Serial, HC12 och gfx mÄste vara giltiga och deras begin()-metoder anropbara.
* @post Serial och HC12 Àr konfigurerade till 9600 baud. Displayen Àr rensad och klar för text.
*/
void setup(){
Serial.begin(9600);
HC12.begin(9600);
gfx->begin();
gfx->fillScreen(BLACK);
gfx->setTextSize(1);
Serial.println("Receiver ready");
}
/* ---------- LOOP ---------- */
/**
* @brief Arduino loop-hanterare: ta emot, parsa och validera paket frÄn HC12-radio.
*
* LÀs kontinuerligt bytes frÄn HC12-serien och bygg paket avgrÀnsade med
* '<' (start) och '>' (slut) i en statisk teckenbuffert. Vid slutavgrÀnsare
* terminera paketet, skriv ut det till Serial, gör en kopia, anropa parsePacket(...)
* och jÀmför buffern med kopian för att avgöra om paketet klarade CRC/validering.
* SkÀrmen uppdateras med drawScreen() efter varje paket.
*
* Beteende:
* - AnvÀnder static char buf[180] och static byte idx för ackumulering över anrop.
* - '<' ÄterstÀller index för att pÄbörja nytt paket.
* - '>' terminera buffern, skriv ut, kopiera, anropa parsePacket(buf), jÀmför med kopian
* och skriv "CRC OK" eller "CRC ERROR". Anropa drawScreen() och nollstÀll idx.
* - Ăvriga tecken lĂ€ggs till bufferten om idx < 179 (plats för avslutande NUL).
*
* Antaganden och begrÀnsningar:
* - HC12 implementerar available() och read() (Stream-liknande).
* - Valideringslogiken förutsĂ€tter att parsePacket muterar buf vid framgĂ„ng; detta Ă€r skört â
* bÀttre Àr att parsePacket returnerar explicit status.
* - Ingen timeout eller utförlig hantering av för lÄnga paket utöver buffertstorleken.
* - Payload som innehÄller '<' eller '>' bryter framer eftersom inga escape-mekanismer finns.
* - Ăverflöd (index >=179) ignoreras i dagslĂ€get utan felrapportering.
*
* FörbÀttringsförslag:
* - LÄt parsePacket returnera success/failure i stÀllet för att jÀmföra buffrar.
* - LÀgg till paket-timeout och explicit överflödes-/felhantering.
* - Implementera en enkel state-machine som stödjer escaping om payloads kan innehÄlla
* avgrÀnsartecken.
* - AnvÀnd lÀngdbaserade och bounds-kontrollerade API:er och undvik tysta truncations.
*
* @return void
*/
void loop(){
static char buf[180];
static byte idx=0;
while(HC12.available()){
char c = HC12.read();
if(c=='<') idx=0;
else if(c=='>'){
buf[idx]=0;
Serial.print("RAW: ");
Serial.println(buf);
char test[180];
strcpy(test,buf);
parsePacket(buf);
if(strcmp(buf,test)==0)
Serial.println("CRC ERROR");
else
Serial.println("CRC OK");
drawScreen();
idx=0;
}
else if(idx<179){
buf[idx++]=c;
}
}
}
ï»ż


HC 12 trÄdlös 433 MHz modul med UART och lÄng rÀckvidd

HC 12 trÄdlös 433 MHz modul med UART och lÄng rÀckvidd

UNO kort, Budget-version av ARDUINO UNO R3

2-Pack, Experimentkort för Arduino UNO. Protobord, Shield, PCB

MEGA 2560 kort med CH340G USB interface.

Experiment shield för MEGA2560 med skruvterminaler

10st Stiftlist 1x40 pinnar. Passar moduler till Arduino

XH2.54

1.8 tum, fullfÀrgs skÀrm 128x160 pixel

6 st hylslist 1x40 pol. Standard delning 2.54 mm

Antenn 2.4GHz SMA koaxial kabel till IPX, 100mm

2 st. NivÄ omvandlare 5V till 3.3V bÄda riktningar 4 kanaler, passar Arduino

Ljusdetektor med justerbar kÀnslighet

Justerbar DC-DC step-down spÀnnings omvandlare LM2596

100 st MotstÄnd 10000 Ohm, 10K metallfilm 1 %.

1 Meter. NTC Temperature Sensor 10K 3950

Air och wrapping trÄd 8 fÀrger.

10K Potentiometer 2W

Hitta i vÄr butik
HC 12 trÄdlös 433 MHz modul med UART och lÄng rÀckvidd
HC 12 trÄdlös 433 MHz modul med UART och lÄng rÀckvidd

UNO kort, Budget-version av ARDUINO UNO R3

2-Pack, Experimentkort för Arduino UNO. Protobord, Shield, PCB

MEGA 2560 kort med CH340G USB interface.

Experiment shield för MEGA2560 med skruvterminaler

10st Stiftlist 1x40 pinnar. Passar moduler till Arduino

XH2.54

1.8 tum, fullfÀrgs skÀrm 128x160 pixel
6 st hylslist 1x40 pol. Standard delning 2.54 mm
Antenn 2.4GHz SMA koaxial kabel till IPX, 100mm

2 st. NivÄ omvandlare 5V till 3.3V bÄda riktningar 4 kanaler, passar Arduino

Ljusdetektor med justerbar kÀnslighet
Justerbar DC-DC step-down spÀnnings omvandlare LM2596
100 st MotstÄnd 10000 Ohm, 10K metallfilm 1 %.

1 Meter. NTC Temperature Sensor 10K 3950
Air och wrapping trÄd 8 fÀrger.

10K Potentiometer 2W