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