Stensig
Hi 

I'm new to OpenPLC and in the learning process and find it amacing to have the opportunity to program the RPI in Ladder and even in a free open source program.
I have a present project where I need to build up a solar heating system for my house and want to use a RPI with touch screen to control the system. 
The plan is to have inputs from 4-5 pcs. of DS18B20 temp sensors and one PT1000 temp sensor and control 2 cirkulation pumps, both with relay (on/off) and PWM (speed), and one three way valve.

However i found on an old topic in this forum that openPLC don't supports 1 wire sensors as DS20B18.
Is this still the case?
I've found this library, can this be used:
https://github.com/nwertzberger/OpenPLC/tree/master/plc-sw/libs/1wire


Another question I have is regarding simulation.
I've found a topic where it was mentioned that simulation wasn't presently available, but it was a feature that was on the to-do list.
Has this feature become available?
I am mostly thinking of surveying the program while it is running in order to find out faults in the ladder program and also surveying values on inputs and outputs.  

Thanks in advance.

Best regards 
Søren Stensig
Quote 0 0
jean-jean
Hi,

In this Thiago's video : 

He uses a DS18B20 on a NodeMcu, but I can't find the code to help you.

He probably send the data throught the modbus analog variables, but you probably whant it directly with on your raspberry.
Quote 1 0
jean-jean
If you want to go this way, you can download the initial NodeMcu arduino code:
http://www.openplcproject.com/getting-started-esp8266

and modify this function:

void updateInputs()
{
for (int i = 0; i < sizeof(pinMask_DIN); i++)
{
bitWrite(input_data.digital, i % 8, digitalRead(pinMask_DIN[i]));
}

for (int i = 0; i < sizeof(pinMask_AIN); i++)
{
input_data.analog = analogRead(pinMask_AIN[i]) * 64;
}
input_data.device_id = DEVICE_ID;
}

Replace the "analogRead()" function with the DS18B20 temperature reading funtion.
Quote 1 0
thiagoralves
Hi all,

On that vídeo I’m not using a DS20B18 sensor. That is just a regular PT1000 temperature sensor connected to the analog input of the ESP8266. These type of sensors vary their resistance value according to temperature. Given that the analog input of microcontrollers sense voltage, I had to built a very simple circuit with a 10k resistor to read the sensor’s resistance. There are millions of tutorials on the internet on how to do that. On the OpenPLC side, I didn’t have to change any line of code because OpenPLC can read analog inputs (if the board has them).

Unfortunately, OpenPLC cannot read other types of sensors yet, like digital humidity and temperature sensors. It will be possible to use them on the next version of OpenPLC that is almost ready. If you want to use a Raspberry Pi for your project, I recommend you to connect an Arduino to it. The Arduino has a lot of analog inputs that can be used to read your PT1000 sensors and also many PWM outputs. The Raspberry Pi only has one PWM output and don’t have any analog input.

About monitoring, this feature will be available also on the next version of OpenPLC. You can check a funcional demo of it here: http://18.216.178.208/OpenPLC_v3/monitoring.html

For now, once this feature is available, it will only allow you to see the current state of your inputs and outputs and also to force them to be any value you want. It won’t have a graphical ladder simulation on the editor that many users are familiar with from other PLC vendors.
Quote 1 0
Stensig
Hi jean-jean and Thiago

Thank you for your replies. 

@Thiago
I have an Arduino that I could use if nessessarry but I would prefer to keep it simple and stay with the Rpi only, if possible.
I only need one PWM output, so on that side the Rpi is sufficient. Also only one PT1000 is needed. For this I bought a ADC board with a MAX31865 wich communicates over SPI to read out the temperature. My hope is to get this running through OpenPLC also.
What is your oppinion to this solution? SPI?

You say that a new version is coming up, can you give a gues on when?
It sounds nice with support for DS20B18 and input/output simulation:-)
Quote 0 0
thiagoralves
I'm expecting a beta version to come out around next week. OpenPLC v3 will not support external hardware (like MAX31865 and your DS20B18 sensor) out of the box. What it will have is a module that allows you to modify the current hardware settings and insert your own code into it, just like you saw on the demo screen. So for example, the hardware layer for the Raspberry Pi allows you to use your GPIO pins as digital inputs and outputs, but it doesn't allow you to communicate over SPI to an analog-digital converter. If you need to communicate with an external ADC board over SPI like the MAX31865, you will have to write the code to do it, and then put that code on the Hardware Layer Code Box. Your code then complements OpenPLC code while reading your I/O and then makes it possible for the MAX31865 data to be available as a regular analog input on %IW0, %IW1, etc...

You can read the comments on the Hardware Layer Code Box to understand more about the process.
Quote 1 0
Stensig
Hi Thiago

Thank you for the extended explanation.
However I'm pretty new to all the coding stuff, so starting to doubt that I will be able to reach the goal in my project.
But I will discover what information is available on the demo screen and comments in the Hardware Layer Code Box and give it a try afterwards:-)

BR
Søren 
Quote 0 0
Ken
I have recently added a UniPi 1.1 to my RPi.  I have successfully installed  OpenPLC v3 - no trouble at all.  Before using the UniPi I have connected DS18B20 sensors to capture temp data via GPIO4 on the RPi alone.  What must I do in your Hardware Layer Code Box to read sensors over the 1-Wire connection. Thank you.
Quote 0 0
thiagoralves
First you need to have some code in C that can read your sensors over 1-Wire. You may want to google for examples, I bet there are plenty. Once that is working, you can implement this code in the Hardware Layer Code Box.

I believe all the required instructions for how it works are on the comments of the code itself. When you open the Hardware page it loads the contents of the custom_layer.cpp file. Read the comments on that file carefully and you will have an idea of how it works. To clarify more on the subject, here is an explanation of the custom layer functions:

- void initCustomLayer()
This function is called only once, when OpenPLC runtime is starting. You should put in there all your initialization code, like starting up your 1-Wire hardware, setting up your sensors, initializing global variables, etc...

- void updateCustomIn()
This function is called in a loop at the beginning of the scan cycle. It should be used to update OpenPLC's internal input buffers. Since you want to implement 1-Wire sensors, it should be appropriate to put the readings from the sensors into the input buffers. So on this function you should put the code to actually read the sensors and then insert those readings into a position in the input buffer. The input buffer is linearly mapped with OpenPLC variables. So, for example:
int_input[0] = %IW0
int_input[1] = %IW1
bool_input[0][2] = %IX0.2
... and so on

To avoid writting into null pointers, you must check first if that position on the buffer exists before writing to it. Therefore, the right code to insert something on the input buffer is:

if (int_input[3] != NULL) *int_input[3] = onewire_value;

This code inserts the integer "onewire_value" into %IW3.

- void updateCustomOut()
Similarly, this function is called at the end of the scan cycle and should update your custom outputs based on the output buffers. Since all you want to implement is reading 1-Wire sensors, it seems that you don't have any outputs to work with, so you can leave this function empty.

Finally, the ignored vectors should be used only if you're planning to overwrite some of your current hardware inputs or outputs. In your case, you may need to ignore the pins that are responsible for the 1-Wire hardware, otherwise OpenPLC will use them as inputs or outputs, and you won't be able to communicate with your sensors. For example, if you want to exclude Raspberry Pi pin 7 (%IX0.2 on OpenPLC addressing) you need to add this pin to the ignored_bool_inputs[] vector. If you don't add %IX0.2 to the ignored vectors and try to manipulate it with your custom code, both Raspberry Pi driver and your code will compete for %IX0.2 causing a race condition. Therefore, to add %IX0.2 to the ignored vector, just change the declaration of int ignored_bool_inputs[] = {-1} on line 19 with:

int ignored_bool_inputs[] = {2};
Quote 0 0
brasei
Hello,

I am Bram and I am also working on the implementation of DS18B20 sensors in OpenPLC.

Currently it does read one sensor, but with the code enabled in UpdateCustomIn the other outputs will not respond to actions from ScadaBR anymore. With the code commented out, things behave as normal.

This is the code:

int ignored_bool_inputs[] = {13};
int ignored_bool_outputs[] = {-1};
int ignored_int_inputs[] = {0, 1, 2};
int ignored_int_outputs[] = {-1};
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
     
DIR *dir;
struct dirent *dirent;
char dev[5][16];      // Dev ID
char devPath[5][128]; // Path to device
char buf[256];     // Data from device
char tmpData[6];   // Temp C * 1000 reported by device 
char path[] = "/sys/bus/w1/devices"; 
float tempC;
ssize_t numRead;
int numDevicesFound = 0;
//-----------------------------------------------------------------------------
// This function is called by the main OpenPLC routine when it is initializing.
// Hardware initialization procedures for your custom layer should be here.
//-----------------------------------------------------------------------------
void initCustomLayer()
{
    dir = opendir (path);
    if (dir != NULL)
    {
        while ((dirent = readdir (dir)))
        // 1-wire devices are links beginning with 28-
        if (dirent->d_type == DT_LNK && strstr(dirent->d_name, "28-") != NULL) { 
            strcpy(dev[numDevicesFound], dirent->d_name);
            //printf("\nDevice: %s\n", dev);
            // Assemble path to OneWire device
            sprintf(devPath[numDevicesFound++], "%s/%s/w1_slave", path, dev);
        }
        (void) closedir (dir);
    }
    else
    {
        perror ("Couldn't open the w1 devices directory");
        return 1;
    }
    
    //numDevicesFound = 0;
}
//-----------------------------------------------------------------------------
// This function is called by OpenPLC in a loop. Here the internal input
// buffers must be updated with the values you want. Make sure to use the mutex 
// bufferLock to protect access to the buffers on a threaded environment.
//-----------------------------------------------------------------------------
void updateCustomIn()
{
//    char i = 0;
//    while(i < numDevicesFound) {
//        int fd = open(devPath[i], O_RDONLY);
//        //printf("Trying to open: %s", devPath);
 //       if(fd == -1)
//        {
//            perror ("Couldn't open the w1 device.");
//            return 1;   
//        }
//        while((numRead = read(fd, buf, 256)) > 0) 
//        {
//            strncpy(tmpData, strstr(buf, "t=") + 2, 5); 
//            tempC = strtof(tmpData, NULL);
//            printf("Device: %s  - ", dev); 
//            printf("Temp: %.3f C  ", tempC / 1000);
//            //printf("%.3f F\n\n", (tempC / 1000) * 9 / 5 + 32);
//            //ds18b20_val1 = tempC;
//        }    
//    
 //       if (int_input[i] != NULL) *int_input[i] = (int)tempC;
//    }
}


Is it because I don´t use the bufferLock as commented above the updateCustomIn function?
The system does set the temp read from the sensor, but updates are very slow or only when connecting ScadaBR to the system, any thoughts?
Quote 0 0
thiagoralves
It appears to me that your while loop is just stuck reading forever, and that's why your updates are very slow. Also, the verification in your while loop seems wrong as well because numRead is not used in it at all. Try changing your while loop to an if statement and correcting the check:

//change this
while((numRead = read(fd, buf, 256)) > 0)

//to this
if (read(fd, buf, 256) > 0)
Quote 0 0
Thn
Hi ,
I think you made an infinite loop,you use the variable "i" but it is never updated in the loop.
So "i" is always equal to 0

void updateCustomIn()
{
//    char i = 0;
//    while(i < numDevicesFound)

I am new to this forum, but congratulations to thiagoralves for this excellent project.😃
Quote 0 0
brasei
Thanks for the answers, I am new here also and want to thank Thiago too!

I replaced the while loop with the if statement as you suggested, but it doesn't solve the problem.
I've adjusted the code to run for multiple sensors as Thn says, I will share it later, but that isn't the solution either.

The code only uploads the first reading, when I run openplc from command line, the readings continue however (the printf is displayed) but still it doesn't upload to ScadaBR. Do we have to make a delay when setting the values of int_input[i] or something?
Quote 0 0
thiagoralves
Thn is right. What he said is that you're having an infinite loop in your first while loop:

while(i < numDevicesFound) {

You're never incrementing your "i" variable, so that's probably why it only runs once and then gets stuck forever. You need to let it get out of the updateCustomIn() function, otherwise OpenPLC will just get stuck and won't do its thing. Just add a i++; line at the end of the loop and it should work.

Also, instead of testing using ScadaBR, try testing with Radzio first (  http://en.radzio.dxp.pl/modbus-master-simulator/ ). This tool is simpler to use and will let you know if the PLC is not responding or if there are other problems.
Quote 0 0
brasei
It is true that the code was hanging in the loop, I've changed it to:

char i = 0;
void updateCustomIn()
{
    if(i < numDevicesFound) {
      //pthread_mutex_lock(&bufferLock); //lock mutex
        int fd = open(devPath[i], O_RDONLY);
        //printf("Trying to open: %s", devPath);
        if(fd == -1)
        {
            perror ("Couldn't open the w1 device.");
            return 1;   
        }
        if (read(fd, buf, 256) > 0)\
        {
            //printf(strlen(buf));
            strncpy(tmpData, strstr(buf, "t=") + 2, 5); 
            tempC = strtof(tmpData, NULL);
            printf("Device: %s  - ", dev); 
            printf("Temp: %.3f C  ", tempC / 1000);
            //printf("%.3f F\n\n", (tempC / 1000) * 9 / 5 + 32);
            //ds18b20_val1 = tempC;
        }    
   
        if (int_input[i] != NULL) *int_input[i] = (int)tempC;
        i++;
        close(fd);
      //pthread_mutex_unlock(&bufferLock); //lock mutex
    }
    else i = 0;
}


But on Modbus the data still only displays the first value, I think because the Modbus server continuously shuts down and starts a new thread, as seen in the SSH log:

 Modbus Server: Client accepted! Creating thread for the new client ID: 30...
Modbus Server: waiting for new client...
Modbus Server: Thread created for client ID: 30
Device: 28-0417c3b63bff  - Temp: 23.187 C  Device: 28-0417c3b63bff  - Temp: 23.062 C  Device: 28-0417c3b63bff  - Temp: 22.937 C  Modbus Server: client ID: 26 has closed the connection
Terminating Modbus connections thread
Device: 28-0417c3b63bff  - Temp: 22.875 C  Modbus Server: Client accepted! Creating thread for the new client ID: 26...
Modbus Server: waiting for new client...
Modbus Server: Thread created for client ID: 26
Device: 28-0417c3b63bff  - Temp: 22.750 C  Device: 28-0417c3b63bff  - Temp: 22.625 C  Modbus Server: Client accepted! Creating thread for the new client ID: 31...
Modbus Server: waiting for new client...
Modbus Server: Thread created for client ID: 31
Device: 28-0417c3b63bff  - Temp: 22.562 C  Device: 28-0417c3b63bff  - Temp: 22.437 C  Modbus Server: client ID: 25 has closed the connection
Terminating Modbus connections thread


The code works but takes still too long so that the Modbus server restarts? The values are retrieved via the Radzio tool, and it looks the same as ScadaBR.
Quote 0 0