Mały, bezprzewodowy czujnik temperatury na radiu

Awatar użytkownika
pzygmunt
Posty: 18284
Rejestracja: wt sty 19, 2016 9:26 am
Lokalizacja: Paczków
Kontakt:

vcompl pisze: pn lip 15, 2019 3:26 pm Czemu w głownym nurcie rozwoju SUPLA nie ma w realnych planach bramek dla - transmisji lora, zigbee, mqtt, mbus/wmbus ???
Jak zwiększymy zasoby to pewnie się pojawią
Awatar użytkownika
Robert Błaszczak
Posty: 3970
Rejestracja: sob gru 22, 2018 8:55 pm
Lokalizacja: Zielona Góra
Kontakt:

Potrzebuję pomocy w ogarnięciu oprogramowania bramki. Niestety moja "wiedza" (właściwie to brak tej wiedzy) w zakresie programowania jest znikoma i pisanie programu robię bardziej na zasadzie CTRL+C i CTRL+V. Choć jest już pewien postęp - coraz więcej rozumiem, co kopiuję :lol:

Na podstawie przykładów z pierwszej strony tego wątku (od @Beku oraz @fracz) napisałem oprogramowanie do bramki. Teoretycznie wszystko działa, jednak po jakimś czasie pomiary z 3 i 4 transmitera (drugi i trzeci kanał) nie odświeżają się w SUPLA Cloud, natomiast zerowy i pierwszy kanał cały czas działają poprawnie. Próbowałem modyfikacji SuplaDevice.cpp zgodnie z tym opisem: viewtopic.php?p=49585#p49585. Pomaga tylko tyle, że trochę dłużej kanał 3 i 4 pokazują prawidłowe wartości. Bramka cały czas otrzymuje prawidłowe wartości z nadajników. Wklejam kody, może jakaś dobra dusza przyglądnie się tematowi.

BRAMKA:

Kod: Zaznacz cały

#include <RFM69_ATC.h>                    //Pobierz z: https://github.com/lowpowerlab/rfm69

#define NODEID 1                          //Unikalny numer bramki (1 - 254).
#define NETWORKID 100                     //Numer sieci (1 - 254) w której działą bramka SUPLA oraz inne transmitery. Bramka oraz max. 6 transmiterów musi działać w sieci o takim samym ID.
#define FREQUENCY RF69_868MHZ             //Jeśli posiadasz moduł radiowy pracujący z inną częstotliwością zamień parametr na RF69_433MHZ lub RF69_915MHZ.
//#define IS_RFM69HW_HCW                  //Usuń zacznik komentarza, jeśli posiadasz moduł radiowy w wersji RFM69HW/HCW.
#define ENCRYPTKEY "sampleEncryptKey"     //Klucz kodowania - 16 znaków (ani mniej, ani więcej) - identyczny w bramce SUPLA i we wszystkich transmiterach.
#define ENABLE_ATC                        //Wstaw znacznik komentarza jeśli chcesz wyłączyć automatyczną kontrolę transmisji (ATC).
#define ATC_RSSI -80                      //Poziom sygnału radiowego (odcięcie) dla ATC.
#define SERIAL_BAUD 115200                //Prędkość transmisji portu szeregowego.

#define SUPLADEVICE_CPP
#include <SuplaDevice.h>

#define SUPLA_SERVER "svr1.supla.org"
#define LOCATION_ID 999
#define LOCATION_PASSWORD "xxxx"
Pobierz identyfikator urządzenia ze strony https://www.supla.org/arduino/get-guid i wprowadź go poniżej
#define GUID {0x24,0xEA,0x85,0x7D,0xA3,0x5F,0xAD,0x4E,0x89,0xA4,0xB7,0xF7,0x85,0x73,0xEE,0xE7}

RFM69_ATC radio(D8, D2, false);
bool promiscuousMode = false;

const unsigned long period = 300000;      //Czas (5 minut) po którym bramka uznaje brak odczytu z transmitera i ustawia wartości wysyłane do Supla Cloud na 0.
double temperature = 0.0;
double humidity = 0.0;
unsigned long startMillis = 0;
unsigned long currentMillis = 0;
unsigned long RFNodesCount = 0;
typedef struct {
  int nodeid;
  int channelNumber;
  double temperature;
  double humidity;
  double batteryLevel;
  unsigned long lastContact;
} RF_TemperatureandHumidityNode;

RF_TemperatureandHumidityNode RFNodes[6];

void get_temperature_and_humidity(int channelNumber, double *temp, double *humidity) {

  for (int i = 0; i < RFNodesCount; i++)
    if (RFNodes[i].channelNumber == channelNumber)
    {
      *temp = RFNodes[i].temperature;
      *humidity = RFNodes[i].humidity;
    }
}

void setup() {
  Serial.begin(SERIAL_BAUD);
  
  SuplaDevice.setTemperatureHumidityCallback(&get_temperature_and_humidity);
  SuplaDevice.addAM2302();
  SuplaDevice.addAM2302();
  SuplaDevice.addAM2302();
  SuplaDevice.addAM2302();
  SuplaDevice.addAM2302();
  SuplaDevice.addAM2302();
  char guid[SUPLA_GUID_SIZE] = GUID;
  uint8_t mac[6] = {0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
  SuplaDevice.setName("BRAMKA RB-10G");
  SuplaDevice.begin(guid, mac, SUPLA_SERVER, LOCATION_ID, LOCATION_PASSWORD);
  
  radio.initialize(FREQUENCY,NODEID,NETWORKID);

#ifdef IS_RFM69HW_HCW
  radio.setHighPower();
#endif

  radio.encrypt(ENCRYPTKEY);
  radio.promiscuous(promiscuousMode);

  char buff[65];
  sprintf(buff, "BRAMKA RB-10G. Start komunikacji radiowej na częstotliwości %d MHz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
  Serial.println(buff);
}

byte ackCount = 0;
uint32_t packetCount = 0;

String getValue(String data, char separator, int index)
{
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length() - 1;

  for (int i = 0; i <= maxIndex && found <= index; i++) {
    if (data.charAt(i) == separator || i == maxIndex) {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i + 1 : i;
    }
  }

  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void readRadioData() {

  String value = "";
  if (radio.receiveDone())
  {
    int foundIdx = -1;

    for (int i = 0; i < RFNodesCount; i++)
      if (RFNodes[i].nodeid == radio.SENDERID)
      {
        foundIdx = i;
        break;
      }

    Serial.print("Odbieram dane: ");
    if (foundIdx == -1)
    {
      if (RFNodesCount < 6)
      {
        Serial.print("Nowy transmiter. Dodaję go do SUPLA Cloud. ");
        RFNodes[RFNodesCount].nodeid = radio.SENDERID;
        RFNodes[RFNodesCount].lastContact = millis();
        RFNodes[RFNodesCount].channelNumber = radio.SENDERID - 1;
        RFNodes[RFNodesCount].temperature = 0.0;
        RFNodes[RFNodesCount].humidity = 0.0;
        RFNodes[RFNodesCount].batteryLevel = 0;
        
        foundIdx = RFNodesCount;
        RFNodesCount++;
      } else {
        Serial.print("Maksymalna ilość transmiterów przekroczona! ");
        foundIdx = 0;
      }
    } else
    {
      RFNodes[foundIdx].temperature = 0.0;
    }

    Serial.print("#[");
    Serial.print(++packetCount);
    Serial.print(']');
    Serial.print('['); Serial.print(radio.SENDERID, DEC); Serial.print("] ");

    value = "";
    for (byte i = 0; i < radio.DATALEN; i++)
      if (radio.DATA[i] != ' ')
        value += (char)radio.DATA[i];

    Serial.println(value);

    String tempStr = getValue(value, '|', 0);
    String humStr = getValue(value, '|', 1);
    String battLevelStr = getValue(value, '|', 2);

    Serial.print("Numer kanału SUPLA: ");
    Serial.println(radio.SENDERID - 1);
    Serial.print("Temperatura: ");
    Serial.println(tempStr);
    Serial.print("Wilgotność: ");
    Serial.println(humStr);
    Serial.print("Poziom baterii: ");
    Serial.println(battLevelStr);

    RFNodes[foundIdx].temperature = tempStr.toFloat();
    RFNodes[foundIdx].humidity = humStr.toFloat();
    RFNodes[foundIdx].batteryLevel = battLevelStr.toFloat();
    RFNodes[foundIdx].lastContact = millis();

    Serial.print("Poziom sygnału transmitera [RX_RSSI]:"); Serial.print(radio.RSSI); Serial.print(" dBm");

    if (radio.ACKRequested())
    {
      byte theNodeID = radio.SENDERID;
      radio.sendACK();
      
      if (ackCount++ % 3 == 0)
      {
        Serial.print(" ACK TEST - Transmiter ");
        Serial.print(theNodeID);
        delay(5);
        radio.sendWithRetry(theNodeID, "ACK TEST", 8, 0);
      }
    }
    Blink(LED_BUILTIN, 100);
    Serial.println();
    Serial.println();
  }

  for (int i = 0; i < RFNodesCount; i++)
  {
    if (millis() - RFNodes[i].lastContact >= period)
    {
      RFNodes[i].temperature = 0.0;
      RFNodes[i].humidity = 0.0;
      RFNodes[i].batteryLevel = 0;
      Serial.print("Brak odczytu z czujnika ");
      Serial.println (RFNodes[i].nodeid);
    };
  }
}

void Blink(byte PIN, int DELAY_MS)
{
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN,LOW);
  delay(DELAY_MS);
  digitalWrite(PIN,HIGH);
}

void loop() {
  readRadioData();
//  SuplaDevice.iterate();
}
SuplaTCP.cpp (musi być w tym samym folderze, co główny kod i jest identyczny z załączonym przez @fracz.

Kod: Zaznacz cały

#define wifiname "SSID"
#define wifipassword "password"


#include <srpc.h>
#include <log.h>
#include <eh.h>
#include <proto.h>
#include <IEEE754tools.h>
#define SUPLADEVICE_CPP
#include <SuplaDevice.h>
#include <lck.h>

#include <WiFiClient.h>
#include <ESP8266WiFiType.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiScan.h>
#include <ESP8266WiFiMulti.h>
#include <WiFiServer.h>
#include <ESP8266WiFiGeneric.h>
#include <WiFiClientSecure.h>
#include <ESP8266WiFiAP.h>
#include <ESP8266WiFiSTA.h>
#include <WiFiUdp.h>
    


    WiFiClient client;
    
    int supla_arduino_tcp_read(void *buf, int count) {
        _supla_int_t size = client.available();
       
        if ( size > 0 ) {
            if ( size > count ) size = count;
            return client.read((uint8_t *)buf, size);
        };
    
        return -1;
    };
    
    int supla_arduino_tcp_write(void *buf, int count) {
        return client.write((const uint8_t *)buf, count);
    };
    
    bool supla_arduino_svr_connect(const char *server, int port) {
          return client.connect(server, 2015);
    }
    
    bool supla_arduino_svr_connected(void) {
          return client.connected();
    }
    
    void supla_arduino_svr_disconnect(void) {
         client.stop();
    }
    
    void supla_arduino_eth_setup(uint8_t mac[6], IPAddress *ip) {

       Serial.println("WiFi init");
        WiFi.begin(wifiname, wifipassword);

        while (WiFi.status() != WL_CONNECTED) {
            delay(500);
        //    Serial.print(".");
        }

        Serial.print("\nlocalIP: ");
        Serial.println(WiFi.localIP());
        Serial.print("subnetMask: ");
        Serial.println(WiFi.subnetMask());
        Serial.print("gatewayIP: ");
        Serial.println(WiFi.gatewayIP());
    }

SuplaDeviceCallbacks supla_arduino_get_callbacks(void) {
          SuplaDeviceCallbacks cb;
          
          cb.tcp_read = &supla_arduino_tcp_read;
          cb.tcp_write = &supla_arduino_tcp_write;
          cb.eth_setup = &supla_arduino_eth_setup;
          cb.svr_connected = &supla_arduino_svr_connected;
          cb.svr_connect = &supla_arduino_svr_connect;
          cb.svr_disconnect = &supla_arduino_svr_disconnect;
          cb.get_temperature = NULL;
          cb.get_temperature_and_humidity = NULL;
          cb.get_rgbw_value = NULL;
          cb.set_rgbw_value = NULL;
          
          return cb;
}
Oprogramowanie nadajnika (moduł Moteino R6, czujnik temperatury i wilgotności Si7021, pomiar poziomu baterii):

Kod: Zaznacz cały

#include <RFM69_ATC.h>                                  //Pobierz z: https://github.com/lowpowerlab/rfm69
#include <Wire.h>                                       //Zawarty w Arduino IDE (www.arduino.cc)
#include <SPI.h>                                        //Zawarty w Arduino IDE (www.arduino.cc)
#include <SPIFlash.h>                                   //Pobierz z: https://github.com/lowpowerlab/spiflash
#include "Adafruit_Si7021.h"                            //Pobierz z: https://github.com/adafruit/Adafruit_Si7021
#include <LowPower.h>                                   //Pobierz z: https://github.com/lowpowerlab/lowpower

#define NODEID 1                                        // Unikalny numer transmitera (1 - 6) pracującego w sieci o takim samym ID, który jest jednocześnie numerem czujnika w SUPLA Cloud.
#define NETWORKID 100                                   // Numer sieci (1 - 254) w której działą bramka SUPLA oraz inne transmitery. Bramka oraz max. 6 transmiterów musi działać w sieci o takim samym ID.
#define GATEWAYID 1                                     // Numer bramki (0 - 254).

#define FREQUENCY RF69_868MHZ                           //Jeśli posiadasz moduł radiowy pracujący z inną częstotliwością zamień parametr na RF69_433MHZ lub RF69_915MHZ.
//#define IS_RFM69HW_HCW                                //Usuń zacznik komentarza, jeśli posiadasz moduł radiowy w wersji RFM69HW/HCW.
#define ENCRYPTKEY "sampleEncryptKey"                   //Klucz kodowania - 16 znaków (ani mniej, ani więcej) - identyczny w bramce SUPLA i we wszystkich transmiterach.

#define ENABLE_ATC                                      //Wstaw znacznik komentarza jeśli chcesz wyłączyć automatyczną kontrolę transmisji (ATC).
#define ATC_RSSI -80                                    //Poziom sygnału radiowego (odcięcie) dla ATC.

period_t sleepTime = SLEEP_4S;                          //Zdefiniowany czas uśpienia transmitera 4 sekundy (zgodnie z LowPower library (LowPower.h)).
#define SEND_LOOPS 13                                   //Ilość pętli uśpienia transmitera (np. 4 sekundy * 15 = 60 sekund). Ilość pętli należy dobrać do konkretnej płytki Monteino.
#define BATT_READ_LOOPS SEND_LOOPS * 30                 //Ilość pętli, po których wykonywany jest pomiar napięcia na baterii (np. 60 sekund * 30 = 30 minut).

//#define SERIAL_ENABLE                                 //Usuń znaki komentarza '//' aby włączyć wyświetlanie na porcie szeregowym.

#define BATT0 730                                       //Odczytana wartość napięcia BATTx poniżej której poziom baterii ustawiany jest na wartość 0 (3.10V).
#define BATT1 1002                                      //Odczytana wartość napięcia BATTx poniżej której poziom baterii ustawiany jest na wartość 1 (3.20V).
#define BATT2 1008                                      //Odczytana wartość napięcia BATTx poniżej której poziom baterii ustawiany jest na wartość 2 (3.25V).

#ifdef SERIAL_ENABLE
  #define SERIAL_BAUD 115200
  #define SP(input) {Serial.print(input);}
  #define SPln(input) {Serial.println(input);}
  #define SFLUSH() {Serial.flush();}
#else
  #define SERIAL_BAUD 115200
  #define SP(input);
  #define SPln(input);
  #define SFLUSH();
#endif

RFM69_ATC radio;
SPIFlash flash(SS_FLASHMEM, 0xEF30);
Adafruit_Si7021 Si7021 = Adafruit_Si7021();
char Tstr[10];
char Hstr[10];
char Bstr[10];
double T,H,B;
char buffer[60];


void setup() {
  Serial.begin(SERIAL_BAUD);

  radio.initialize(FREQUENCY, NODEID, NETWORKID);
  #ifdef IS_RFM69HW_HCW
    radio.setHighPower();
  #endif
  radio.encrypt(ENCRYPTKEY);
  radio.enableAutoPower(ATC_RSSI);
  sprintf(buffer, "T/H SUPLA TRANSMITER RB-10 %d MHz.", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
  SPln(buffer);
  Wire.begin();
  Wire.setClock(400000);
  Si7021.begin();
  if (flash.initialize()) flash.sleep();

  for (uint8_t i=0; i<=A5; i++)
  {
    if (i == RF69_SPI_CS) continue;
    if (i == SS_FLASHMEM) continue;
    pinMode(i, OUTPUT);
    digitalWrite(i, LOW);
  }
  pinMode(A1, INPUT);
  SFLUSH();
}

char input = 0;
unsigned long sendLoops = 0;
unsigned long battReadLoops = 0;
word Battery1024 = 0;
byte BatteryLevel = 0;
byte sendLen;


void loop() {
  if (battReadLoops--<=0) {
    ReadBattery();
    battReadLoops = BATT_READ_LOOPS-1;
  }
  
  if (sendLoops--<=0) {
    sendLoops = SEND_LOOPS-1;
    
    T = Si7021.readTemperature();
    H = Si7021.readHumidity();
    dtostrf(T, 3, 2, Tstr);
    dtostrf(H, 3, 2, Hstr);
    dtostrf(BatteryLevel, 0, 0, Bstr);
    sprintf(buffer, "%s|%s|%s", Tstr, Hstr, Bstr);
//    sprintf(buffer, "%s|%s", Tstr, Hstr);

    sendLen = strlen(buffer);
    radio.sendWithRetry(GATEWAYID, buffer, sendLen);
    SP("Wysłane dane: "); SP(buffer); SPln();
  }
  
  if (radio.receiveDone()) {
    SPln();
  }
  
  SFLUSH();
  flash.sleep();
  radio.sleep();
  
  LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF);
  
  SPln("SLEEP");
}


void ReadBattery() {
  unsigned int readings = 0;

  analogWrite(A0, 1024);
  for (byte i = 0; i < 6; i++)
    readings += analogRead(A1);
  analogWrite(A0, 0);

  Battery1024 = (readings / 3);

  if (Battery1024 >= BATT2) {
  BatteryLevel = 3;
  }
  else if (Battery1024 < BATT2 && Battery1024 >= BATT1) {
    BatteryLevel = 2;
  }
  else if (Battery1024 < BATT1 && Battery1024 >= BATT0) {
    BatteryLevel = 1;
  }
  else if (Battery1024 < BATT0) {
    BatteryLevel = 0;
  }
  SP("BATTx: "); SP(Battery1024); SP(" | Poziom baterii (0 - 3): ");SPln(BatteryLevel);
}
Załączam jeszcze zdjęcia nadajniczków:
SUPLA-TRANSMITER-RB-10_01.jpg
SUPLA-TRANSMITER-RB-10_01.jpg (158.82 KiB) Przejrzano 3731 razy
SUPLA-TRANSMITER-RB-10_02.jpg
SUPLA-TRANSMITER-RB-10_02.jpg (221.86 KiB) Przejrzano 3731 razy
SUPLA-TRANSMITER-RB-10_03.jpg
SUPLA-TRANSMITER-RB-10_03.jpg (255.69 KiB) Przejrzano 3731 razy


Zastosowałem pomiar napięcia baterii działający jak na tej symulacji: https://tiny.pl/tpqnr
Ostatnio zmieniony wt paź 01, 2019 4:42 pm przez Robert Błaszczak, łącznie zmieniany 1 raz.
Pozdrawiam
Robert Błaszczak


Moja prywatna strona: www.blaszczak.pl
Awatar użytkownika
klew
Posty: 8184
Rejestracja: czw cze 27, 2019 12:16 pm
Lokalizacja: Wrocław

A próbowałeś kolejkę zwiększyć?
Widzimy się na Supla Offline Party vol. 2 :!:
Awatar użytkownika
Robert Błaszczak
Posty: 3970
Rejestracja: sob gru 22, 2018 8:55 pm
Lokalizacja: Zielona Góra
Kontakt:

@klew, czy piszesz o tym:
klew pisze:Spróbuj w SuplaDevice.cpp zamienić linię:

channel_pin[Params.reg_dev.channel_count].time_left = 0;
na:
channel_pin[Params.reg_dev.channel_count].time_left = 1000*Params.reg_dev.channel_count;

Do tego w metodzie "onRegisterResult" w znajdź linię:
status(STATUS_REGISTERED_AND_READY, "Registered and ready.");
I przed nią dodaj:
last_iterate_time = millis();

Jeśli tak, to próbowałem z różnymi wartościami, 100ms, 250ms i 1000ms. Najdłużej działało przy 250ms.
Pozdrawiam
Robert Błaszczak


Moja prywatna strona: www.blaszczak.pl
Awatar użytkownika
klew
Posty: 8184
Rejestracja: czw cze 27, 2019 12:16 pm
Lokalizacja: Wrocław

Robert Błaszczak pisze: wt paź 01, 2019 2:24 pm @klew, czy piszesz o tym:
Nie, ten kod rozrzuca odczyt z termometrów na różne chwile, aby bit czytać wszystkiego na raz.

Jest gdzie taki define do "max queue size" - ustawiony domyślnie na 2. Była o tym mowa kilka razy w różnych miejscach. Zwiększ sobie na 10 i powinno chodzić. Chyba tylko Arduino ma mało ramu i tam zwiększenie nie działa.
Widzimy się na Supla Offline Party vol. 2 :!:
Awatar użytkownika
Robert Błaszczak
Posty: 3970
Rejestracja: sob gru 22, 2018 8:55 pm
Lokalizacja: Zielona Góra
Kontakt:

Wielkie dzięki. Definicja zmiennej SRPC_QUEUE_SIZE jest w pliku srpc.c
Zmieniłem na 10 i obserwuję. Wieczorem dam znać, czy pomogło :)
Pozdrawiam
Robert Błaszczak


Moja prywatna strona: www.blaszczak.pl
Awatar użytkownika
Robert Błaszczak
Posty: 3970
Rejestracja: sob gru 22, 2018 8:55 pm
Lokalizacja: Zielona Góra
Kontakt:

@klew, wygląda na to, że rozwiązałeś mój problem :D
Jak do tej pory wszystko działa poprawnie.

THX

Edit 02.10.2019 22:35
Cztery uruchomione przeze mnie nadajniki z czujnikiem temperatury i wilgotności działają bez zarzutu.
W najbliższych dniach zrobię dokładny opis elektroniki nadajników i bramki.
W świetle zbliżającego się wdrożenia scen pewnie paru osobom może się przydać :).
Pozdrawiam
Robert Błaszczak


Moja prywatna strona: www.blaszczak.pl
Awatar użytkownika
Robert Błaszczak
Posty: 3970
Rejestracja: sob gru 22, 2018 8:55 pm
Lokalizacja: Zielona Góra
Kontakt:

Część 1 opisu transmitera temperatury i wilgotności: https://www.blaszczak.pl/supla-radiowy- ... i-czesc-1/

Zapraszam do lektury :D

Ps. Czy jest już wiadome, w jaki sposób SUPLA będzie obsługiwała urządzenia bateryjne w zakresie pomiaru napięcia baterii?
Ja bym to widział tak, że urządzenie wysyła znacznik, że jest zasilane bateryjnie i wysyła informację o poziomie baterii w postaci 4 poziomów. W aplikacji mogłoby to wyglądać tak:
SUPLA-APP-BATTERY_01.png
SUPLA-APP-BATTERY_01.png (83.86 KiB) Przejrzano 3550 razy
SUPLA-APP-BATTERY_02.png
SUPLA-APP-BATTERY_02.png (82.67 KiB) Przejrzano 3550 razy
Pozdrawiam
Robert Błaszczak


Moja prywatna strona: www.blaszczak.pl
Simono
Posty: 406
Rejestracja: pn wrz 17, 2018 5:26 pm

Uważam, że to świetny pomysł. Czekam na możliwość nie za skomplikowanego zbudowania takich urządzeń do pomiaru temperatury.
Awatar użytkownika
pzygmunt
Posty: 18284
Rejestracja: wt sty 19, 2016 9:26 am
Lokalizacja: Paczków
Kontakt:

Dobry pomysł. Postaram się go uwzględnić.
ODPOWIEDZ

Wróć do „Projekty użytkowników”