Lazarus

Programming => General => Topic started by: BLL on March 03, 2018, 08:46:13 pm

Title: Hopeless Debugging
Post by: BLL on March 03, 2018, 08:46:13 pm
Hi
I am getting an Access violation error message when I run my program.
If I run my program from within lazarus then it runs but doesn't see any of the USB devices which my program accesses (why not?), so it's useless in finding the problem! If I run the program outside lazarus, then I get the absolutely useless dialog that just tells me that an access violation has occurred but with no indication as to where it has happened! How the hell does one track down the error?

Talk about frustrating!!

Brian

RasPi3, jessie, fpc 3.1.1, lazarus 1.9.0
Title: Re: Hopeless Debugging
Post by: Thaddy on March 03, 2018, 08:53:22 pm
Brian, the only way is to provide code...?
Title: Re: Hopeless Debugging
Post by: jamie on March 03, 2018, 09:04:25 pm
Most likely an Error is developing at the OS level outside the debugger and is getting ignored somehow
.

 You need to employ some TRY USB code Except/Finally in the suspected areas.
Title: Re: Hopeless Debugging
Post by: Bart on March 04, 2018, 12:21:44 am
Build with debuginfo (-gl) and see what the backtrace says when it crashes.
You might need a console for that, on Windows build with -WG- (Lazarus: Compiler options->Config and target: uncheck "Win32 gui application").

Bart
Title: Re: Hopeless Debugging
Post by: BLL on March 04, 2018, 10:42:48 am
Hi all
Thanks for the replies. Noone has explained why when running my program within lazarus, it can't communicate with any of its usb ports.

The -gl option sounds interesting, so I'll try that.

I have put try except loops all over the place, all to no avail. I have isolated the fault to one function but there's a lot of code in there! The access violation seems to have no pattern and can happen almost as soon as the program starts or it may be hours but it always happens!

Brian

RasPi3, jessie, fpc 3.1.1, lazarus 1.9.0
Title: Re: Hopeless Debugging
Post by: Thaddy on March 04, 2018, 12:21:39 pm
If you can locate it to a single procedure, did you resolve all hints and warnings?
Stackspace is dirty so any warning about uninitialzed vars should be resolved.
And dirty stackspace can easily sigsev on an uninitialized var or result.

Anyway: don't pollute your code with too many try/except, use assertions for that.
Title: Re: Hopeless Debugging
Post by: Martin_fr on March 04, 2018, 12:47:41 pm
Thanks for the replies. Noone has explained why when running my program within lazarus, it can't communicate with any of its usb ports.
Unfortunately, no idea. Maybe it is because (as you say below) the error happens at random, and that affects the communication too? (Not too likely, but...)
Or a permission issue? Or environment?

You can start your app outside the IDE, and if it lasts long enough attach to it.
Or use gdbserver... maybe...

Quote
I have put try except loops all over the place, all to no avail. I have isolated the fault to one function but there's a lot of code in there! The access violation seems to have no pattern and can happen almost as soon as the program starts or it may be hours but it always happens!
If an error happens at random, you may have uninitialized variables....

Try compiling with
-gt
Also try with -gtt and -gttt and -gtttt

Best to add all of the following (and disable all optimizations)
-gt -gh -gl -O- -Criot -Sa

and set the following Environment variable (Run -> Run Parameters) / this will need a bit more memory, skip if you run out of mem.
HEAPTRC="keepreleased"

If anywhere your code is based on an assumption, such as Sender should contain an object of class TButton, then assert(Sender is TButton)
Title: Re: Hopeless Debugging
Post by: BLL on March 05, 2018, 12:58:21 pm
Hi all and thanks for all the replies.
I have this morning found the error and it was my fault!
One of the functions in my program uses data supplied by a PIC micro, which consists of a comma delimited list of electricity values (voltage, current, power etc) from sensors. The values are put into a Stringlist every few seconds and this function displays the results on screen. My code always checks that the list has the correct number of entries every time it is refreshed.
I then use the Val function on each item before using it, to make sure the stringlist item is a valid real number:
Code: Pascal  [Select][+][-]
  1. //Cost
  2.     Val(DataList.Strings[12], cost, ErrCode);
  3.     if ErrCode = 0 then
  4.       begin
  5.        if cost < 0 then cost := 0;
  6.        ElecForm.Cost.Caption := DataList.Strings[12];
  7.       end;
This has worked fine for months, but recently, I added some code lower in the function, which used DataList.Strings[12], but didn't use Val to check its validity, so although the above code was fine, it isn't now! I have now put that right and all seems well and it has been running fine now for some hours.

Thanks again folks.

Brian
Title: Re: Hopeless Debugging
Post by: BLL on March 08, 2018, 06:49:16 pm
Hi all
The bl**** error message has returned. Never in 38 years of programming have I had an error as obscure as this. The only concrete fact is that if I do not call the following function or any part of it, the error goes away:

Code: Pascal  [Select][+][-]
  1. [s][s]procedure TMainForm.doElecState;
  2. var V, I, P, Today, DAverage, solarData: String;
  3. current, voltage, power, excess, used_today, daily_average, cost, tmp, solarI: Real;
  4. tmp1, j, Chg:integer;
  5. LBV: real;
  6. gud: boolean;
  7.  begin
  8.  if(DataList.Count <> 16) then
  9.   exit;
  10.  
  11.   if Assigned(ElecForm) then
  12.    begin
  13.     V := DataList.Strings[0];
  14.     //spurious 1st char sometimes appears. If so, get rid of it!
  15.     if (Ord(V[1]) < 48) or (Ord(V[1]) > 57) then Delete(V, 1, 1);
  16.     Val(V, tmp, ErrCode);
  17.     if ErrCode <> 0 then exit
  18.     else
  19.      begin
  20.       voltage := 2 * (tmp - 200);
  21.       ElecForm.VoltBar.Progress := round(voltage);
  22.       ElecForm.VdigLabel.Caption := V + 'V';
  23.       ElecForm.VdigLabel.Repaint;
  24.      end;
  25.  
  26.     I := DataList.Strings[1];
  27.     Val(I, current, ErrCode);
  28.  
  29.     if ErrCode <> 0 then exit
  30.     else
  31.      begin
  32.       ElecForm.IdigLabel.Caption := I + 'A';
  33.       ElecForm.IdigLabel.Repaint;
  34.       //Set appropriate range for display
  35.       if current > 10.0 then
  36.        begin
  37.         ElecForm.IunitsLabel.Caption := HAScale;
  38.         ElecForm.CurrentBar.Progress := round(5 * current);
  39.        end
  40.       else
  41.        begin
  42.         ElecForm.IunitsLabel.Caption := LAScale;
  43.         ElecForm.CurrentBar.Progress := round(10 * current);
  44.        end;
  45.      end;
  46.     P := DataList.Strings[2];
  47.     Val(P, power, ErrCode);
  48.     if ErrCode <> 0 then exit
  49.     else
  50.      begin
  51.       if power > 1000 then
  52.        begin
  53.         ElecForm.PUnitsLabel.Caption := HPScale;
  54.         ElecForm.Label14.Caption := 'Power (kW):';
  55.         ElecForm.PowerBar.Progress := round(power/50);
  56.         ElecForm.PdigLabel.Caption := FloatToStrF(power/1000, ffFixed,2,2) + 'kW';
  57.        end
  58.       else
  59.        begin
  60.         ElecForm.PUnitsLabel.Caption := LPScale;
  61.         ElecForm.Label14.Caption := 'Power (W):';
  62.         ElecForm.PowerBar.Progress := round(power / 10);
  63.         ElecForm.PdigLabel.Caption := P + 'W';
  64.        end;
  65.      end;
  66.     //add values to StatusList
  67.    { StatusList.Strings[13] :=  ls + 'V = ' + ElecForm.VdigLabel.Caption + ', I = '
  68.           + ElecForm.IdigLabel.Caption + ', P = ' + ElecForm.PdigLabel.Caption + le; }
  69.     //Used today
  70.     Today := DataList.Strings[10];
  71.     Val(Today, used_today, ErrCode);
  72.     if ErrCode <> 0 then exit
  73.     else
  74.      if ErrCode = 0 then
  75.       begin
  76.       if(used_today >= 0) and (used_today < 100) then
  77.        begin
  78.         ElecForm.TodayBar.Progress := round(used_today * 10);
  79.         ElecForm.TdigLabel.Caption := Today + 'kWh';
  80.         StatusList.Strings[14] := ls + 'Used today = ' + ElecForm.TdigLabel.Caption + le;
  81.         ElecForm.TdigLabel.Repaint;
  82.        end;
  83.       if(used_today > 6.0) then ElecForm.TodayBar.ForeColor  := clRed
  84.       else
  85.        ElecForm.TodayBar.ForeColor := clLime;
  86.      end;
  87.  
  88.     //daily average
  89.     DAverage := DataList.Strings[9];
  90.     Val(DAverage, daily_average, ErrCode);
  91.     if ErrCode <> 0 then exit
  92.     else
  93.      begin
  94.       if(daily_average < 20) then
  95.        begin
  96.         ElecForm.DAvg.Progress := round(daily_average * 5);
  97.         ElecForm.DAdigLabel.Caption := DAverage + 'kWh';
  98.         //StatusList.Strings[15] := ls + 'Daily Avg = ' + ElecForm.DAdigLabel.Caption + le;
  99.        end;
  100.  
  101.       if(daily_average > 6) then ElecForm.DAvg.ForeColor := clRed
  102.       else
  103.        ElecForm.DAvg.ForeColor := clLime;
  104.      end;
  105.  
  106.     //Excess units
  107.     Val(DataList.Strings[11], excess, ErrCode);
  108.     if ErrCode <> 0 then exit
  109.     else
  110.      begin
  111.       if(excess < 0) then excess := 0;
  112.        ElecForm.ExcessUnits.Caption := FloatToStrF(excess, ffFixed,3,1)
  113.         + 'kWh';
  114.      end;
  115.  
  116.     //Currency unit
  117.     if not Assigned(setupform) then exit;
  118.     if curSymbol = '' then
  119.      begin
  120.       Val(setupData.Strings[3], tmp1, ErrCode);
  121.       if ErrCode <> 0 then exit
  122.       else
  123.        begin
  124.         if tmp1 = 1 then
  125.          curSymbol := '€'
  126.        else
  127.         curSymbol := '£';
  128.        end;
  129.      end;
  130.  
  131.     //Cost
  132.     Val(DataList.Strings[12], cost, ErrCode);
  133.     if ErrCode <> 0 then exit
  134.     else
  135.      begin
  136.       if cost < 0 then cost := 0;
  137.       ElecForm.Cost.Caption := DataList.Strings[12];
  138.      //end;
  139.     //StatusList.Strings[16] := '';
  140.    { if CurSymbol = '£' then
  141.      StatusList.Strings[16] := ls + 'Cost = ' + '£'
  142.          + DataList.Strings[12] + le
  143.     else
  144.      StatusList.Strings[16] := ls + 'Cost = ' + DataList.Strings[12]
  145.         + '€' + le; }
  146.      ElecForm.Cost.Caption := '';
  147.      if CurSymbol = '€' then
  148.       ElecForm.Cost.Caption := 'Cost = ' + DataList.Strings[12] + CurSymbol
  149.      else
  150.       ElecForm.Cost.Caption := 'Cost = ' + CurSymbol + DataList.Strings[12];
  151.      end;
  152.  
  153.      //process LBV, ChgI
  154.        for j := 0 to 5 do
  155.         begin
  156.          gud := false;
  157.          //read SOLAR battery voltage value
  158.          solarData := srData('SOLAR', '12548'); //0x3104
  159.          //get rid of CR at end of string
  160.          if Length(solarData) > 1 then
  161.           SetLength(solarData, Length(solarData) - 1);
  162.          Val(solarData, LBV, ErrCode);
  163.          if ErrCode = 0 then
  164.           begin
  165.            gud := true;
  166.            break;
  167.           end;
  168.         end;
  169.        //final check in case all 5 read attempts failed
  170.        if gud = false then exit
  171.        else
  172.         begin
  173.          LBV := LBV/100; //mV to V
  174.          ElecForm.LBVValue.Caption := FloatToStrF(LBV, ffFixed,3,2) + 'V';
  175.          {StatusList.Strings[18] := ls + 'Pri Bat = '
  176.             + ElecForm.LBVValue.Caption + ' Sec = ' + le;}
  177.          //offset as scale starts at 8V
  178.          LBV := LBV - 8;
  179.          if LBV < 0 then LBV := 0;
  180.          ElecForm.LBVBar.Progress := round(LBV * 10);
  181.         end;
  182.  
  183.        //Battery charge percentage
  184.        for j := 0 to 5 do
  185.         begin
  186.          gud := false;
  187.          solarData := srData('SOLAR', '12570'); //0x311A
  188.         if Length(solarData) > 1 then
  189.           SetLength(solarData, Length(solarData) - 1);
  190.        //
  191.         Val(solarData, Chg, ErrCode);
  192.         if ErrCode = 0 then
  193.          begin
  194.           gud := true;
  195.           break;
  196.          end;
  197.         end;
  198.  
  199.        if gud = false then exit
  200.        else
  201.         begin
  202.          if Chg > 25 then ElecForm.BatState.ForeColor := clLime
  203.          else ElecForm.BatState.ForeColor := clRed;
  204.          ElecForm.BatState.Progress := Chg;
  205.         end;
  206.  
  207.      //LBI
  208.      Val(DataList.Strings[6], LBI, ErrCode);
  209.      if ErrCode <> 0 then exit
  210.      else
  211.       begin
  212.        LBI := LBI - 499;
  213.        if LBI < 0 then LBI := 0;
  214.        ElecForm.ChgI.Progress := round (LBI/5.843);
  215.        ElecForm.LBIValue.Caption := FloatToStrF(LBI/10.5, ffFixed,3,2) + 'A';
  216.       end;
  217.  
  218.       //solar charge current
  219.        for j := 0 to 5 do
  220.         begin
  221.          gud := false;
  222.          solarData := srData('SOLAR', '12549'); //0x3105
  223.          //get rid of CR at end of string, only if string isn't empty
  224.          //or an Access violation occurs
  225.          if Length(solarData) > 1 then
  226.           SetLength(solarData, Length(solarData) - 1);
  227.          Val(solarData, solarI, ErrCode);
  228.          if ErrCode = 0 then
  229.           begin
  230.            gud := true;
  231.            break;
  232.           end;
  233.         end;
  234.       if gud = false then exit
  235.       else
  236.        begin
  237.         //mA to A
  238.         if solarI < 0 then solarI := 0;
  239.         solarI := solarI/100;
  240.         ElecForm.SolarIval.Caption  := FloatToStrF(solarI, ffFixed,3,2) + 'A';
  241.         ElecForm.SolarBar.Progress := 2 * round(solarI);
  242.        end;
  243.     {StatusList.Strings[19] := ls + 'Load = ' + ElecForm.LBIValue.Caption
  244.         + ', Mchg = ???A, ' + 'Schg = ' + ElecForm.SolarIval.Caption + le;}
  245.    end;
  246.   end;[/s][/s]
I have remmed out all calls to StatusList, which is supposed to write data to the stringlist to save to a file which the webserver will send if requested.

It seems that I need to use the options -g -gl -gs -gw and -Xg and do a backtrace, but I have never done this before. I have looked in project options but most of these switches aren't there. Do I have to compile from the command line instead of from lazarus? If so, I don't have a clue how to do that. Then, how do I access this debug info and use it?

I am just so fed up with it!!

Brian
Title: Re: Hopeless Debugging
Post by: Josh on March 08, 2018, 07:40:20 pm
Hi

Just a thought, what would happen if V contained just one spurious character?
V would be Empty, could this cause your error.
Or if V was an Empty String to start with.

I have also stopped using STRING and then indexing it for a character within it. I tend to use RawByteString; due to Unicode changes.


http://wiki.freepascal.org/not_Delphi_compatible_enhancement_for_Unicode_Support



Code: [Select]
V := DataList.Strings[0];
 //spurious 1st char sometimes appears. If so, get rid of it!
 if (Ord(V[1]) < 48) or (Ord(V[1]) > 57) then Delete(V, 1, 1);
 Val(V, tmp, ErrCode);
Title: Re: Hopeless Debugging
Post by: Martin_fr on March 08, 2018, 08:53:04 pm
I am assuming that you are again unlucky, and you do not get the error if your run this in the debugger?

First thing you can do to narrow it down:
insert lots of
Code: Pascal  [Select][+][-]
  1. debugln('I am at line ....');
You can use writeln instead of debugln. debugln is part of the LazLogger.
You can also output the content of variables.

Then compile with console (project options / app-type: uncheck GUI), and watch the output.

2nd thing:
Again asserts.
Quote
Code: Pascal  [Select][+][-]
  1. if(DataList.Count <> 16) then
Is DataList assigned?
Code: Pascal  [Select][+][-]
  1. assert(DataList<>nil, 'error DataList is nil')
I haven't read the entire bit of code not sure what else there may be...

3rd
"if assigned" or "<> nil) (including my assert above)

Those are not a gurantee, that the variable can be used.
Code: Pascal  [Select][+][-]
  1. a := TFoo.create;
  2. a.destroy;
a is assigned, but it will still crash....

Title: Re: Hopeless Debugging
Post by: Cyrax on March 08, 2018, 09:12:26 pm
Can you show us the code where you are reading values from USB? It sounds like that you are somehow writing the values from USB to string index of 0 which is big no no.
Title: Re: Hopeless Debugging
Post by: Martin_fr on March 08, 2018, 11:09:23 pm
Quote
It seems that I need to use the options -g -gl -gs -gw and -Xg and do a backtrace, but I have never done this before. I have looked in project options but most of these switches aren't there. Do I have to compile from the command line instead of from lazarus? If so, I don't have a clue how to do that. Then, how do I access this debug info and use it?
You can always put them in "Project Options" >  "Custom Options" (3rd last). But you also find them on the "debugging" page of Project Opts.

----

-gl is will give you a backtrace to stdout/console (if you compile with console enabled), otherwise to a logfile if you redirect.

But only if your exception is caught, and something calls DumpStack or similar. For normal exceptions, if you do not catch them yourself, the LCL may do that for you (IIRC).

But access violations are a tricky beast.
They are not a exception (in the sense of raise Exception.create()...)
They are a signal.

access violations  means the OS has detected that your app tried to access memory to which it has no rights.
So the OS sends the signal, and the app has to catch it and convert it to an exception.
Only it may not be able to do so. An access violations almost always means that your apps memory got corrupted. So anything your app tries to do can cause further errors. Sometimes even all stack trace info is destroyed..... Yet sometimes it can be caught.

The best would be if you can get the error in the Debugger.
For that you should use  -gw
"Project Options" >  "Debugging"
 Generate debugging info for GDB
 Type of debug info: Dwarf with sets

http://wiki.lazarus.freepascal.org/Debugger_Setup

------------
On "Project Options" >  "Debugging" you may also want to enable
"Checks and Assertion" : ALL (you may skip "Verify method calls"
"Trash variables"
"Use Heaptrc"
Title: Re: Hopeless Debugging
Post by: BLL on March 08, 2018, 11:30:10 pm
Wow - that's a lot to take in!! To read from USB devices, I originally tried to use sdpoSerial and found it absolutely useless and unreliable so I now call a small C program which receives parameters from my lazarus app, sends them to the converter. Data returned is printed to screen, but TProgress sends it to a string. I am using this on a number of USB-serial converters with no problems. Here is the lazarus code:
Code: Pascal  [Select][+][-]
  1. function TMainForm.srData(prog, args : String) : String;
  2. var
  3.  p:TProcess;
  4.  list:TstringList;
  5. begin
  6. list := TStringList.Create;
  7. p := TProcess.Create(nil);
  8. list.Sorted := false;
  9. list.StrictDelimiter := true;
  10. list.Delimiter := #10;
  11. //none of the following need progPath
  12. if ((prog = 'date') or (prog = 'hwclock') or (prog = 'lxterminal') or (prog = 'chromium-browser')
  13.   or (prog = 'rm')) then
  14.  p.Executable := prog
  15. else
  16.  p.Executable := progPath + prog;
  17.  
  18. p.Parameters.Add(args);
  19. p.Options := p.Options + [poWaitOnExit, poUsePipes];
  20. p.Execute;
  21. list.LoadFromStream(p.Output);
  22. Result := list.Text;
  23. FreeAndNil(list);
  24. FreeAndNil(p);
  25. end;
The C program looks like this:
Code: Pascal  [Select][+][-]
  1. //Get data from /send data to PIC
  2. //usage: ./PIC "#"
  3.  
  4. #include <string.h>
  5. #include <errno.h>
  6. #include <stdio.h>
  7. #include <wiringSerial.h>
  8. #include <unistd.h>
  9. #include <time.h>
  10.  
  11. void sleep_ms(int);
  12.  
  13. int main(int argc, char **argv)
  14. {
  15. int fd, i, count;
  16. if((fd = serialOpen("/dev/PIC", 115200)) < 0)
  17.   {
  18.     fprintf(stdout, "Unable to open serial device: %s\n", strerror(errno));
  19.     return 1;
  20.   }
  21. serialPrintf(fd, argv[1]);
  22. serialPrintf(fd, "\r\n");
  23. sleep_ms(500);
  24. count = serialDataAvail(fd);
  25. //Loop, getting and printing characters
  26.  
  27. for(i = 0; i < count; i++)
  28.  {
  29.   putchar(serialGetchar(fd));
  30.   fflush(stdout);
  31.  }
  32. serialClose(fd);
  33. return 0;
  34. }
  35. //--------------------------------------------------------------------------------------
  36. void sleep_ms(int milliseconds) // cross-platform sleep function
  37. {
  38.  struct timespec ts;
  39.  ts.tv_sec = milliseconds / 1000;
  40.  ts.tv_nsec = (milliseconds % 1000) * 1000000;
  41.  nanosleep(&ts, NULL);
  42. }

I will try the other suggestions.

Brian
Title: Re: Hopeless Debugging
Post by: jamie on March 09, 2018, 01:07:56 am
quick glance I would say its a fail!

 You are using Shell shortcuts..

 Tprocess I am sure requires the actual path to the file...

 Try using "ShellExecute" if you want to launch programs like that.

Also, have you looked for Serial support units ? They are out there and they work.
Title: Re: Hopeless Debugging
Post by: BLL on March 09, 2018, 11:33:36 am
Hi
Thanks for the reply. I tried in vain to use pascal serial routines. sdposerial was completely unreliable, often seizing up ports, which is why I went to calling C progs instead. With added debug switches, my access violation error has become a range check error, so some progress.

Brian
Title: Re: Hopeless Debugging
Post by: BLL on March 09, 2018, 11:53:55 am
Jamie, concerning TProcess. As I understand it, ShellExecute is for MS Windows only - I am using linux jessie on a RaspberryPi. What do you mean by "shell shortcuts"?

Thanks

Brian
Title: Re: Hopeless Debugging
Post by: taumetric on March 10, 2018, 11:47:17 pm
Hi,
I am sure you checked these but just to be complete.
It sounds like overwriting of memory as that would cause USB to fail (messes up data structure) and the changing of that string variable DataList.Strings[12](and others) all of which would be on the heap somewhere.
Be careful of the Range Check Error as it may be in your code but not related.
Do you have any pointer operations? If so it could be a data dependent (as in info from the PIC via USB) operation that is causing a pointer to be set incorrectly especially if mixing arrays with strings or short strings and passing pointers.  Data dependent operations could be why it runs for a while before faulting.
Also, do you have any threading? If so check all that any shared data has critical sections.  Bad or missing critical sections causes race conditions and overwrites.

Title: Re: Hopeless Debugging
Post by: engkin on March 11, 2018, 01:35:53 am
Hi Brian, I just read some of your posts and I came across this notice in your code:
Code: Pascal  [Select][+][-]
  1.     //spurious 1st char sometimes appears. If so, get rid of it!

You seem to expect wrong data sometimes (noise?) and you use Val and ErrCode to check that you have a valid number, as you had explained.

I just want to let you know that if you expect a number like 9100 (four digits) or 97100 (five digits) and instead you received 9E100 (five characters). Pass it to Val and see what happens.

You also mentioned you are sure the problem is related to doElecState. Try to enclose it in a Try Except block as in:
Code: Pascal  [Select][+][-]
  1. procedure TMainForm.doElecState;
  2. var
  3. ...
  4. begin
  5.   try
  6.     your original code here
  7.   except
  8.   end;
  9. end;
and see if your problem disappears.

Consider using a checksum in the data coming from the PIC.
Title: Re: Hopeless Debugging
Post by: Josh on March 11, 2018, 01:46:08 am
Hi,

Just curious if getting range error, it could be checking for string location [1] of s null string; also could be the data your checking in the routine has been altered during the running of the routine.

I have done some mods, I have not tested it obviously I cant; so hopefully you will get the idea, When main function is called; it immediately creates a working copy of the Data; not sure what structure your using so you will need to assign and create the working data type ( Then free the working datatype when exiting);

It will not compile as is, but should be easy enough to adapt, hopefully its over kill, but should help in tracking down the issue; you could put breaks in the get_valid_data functions to se what data is coming in.

Code: Pascal  [Select][+][-]
  1. Function TMainForm.doElecState:Boolean; //
  2.  
  3. var V, I, P, Today, DAverage, solarData,anyString: String;
  4. current, voltage, power, excess, used_today, daily_average, cost, tmp, solarI: Real;
  5. tmp1, j, Chg:integer;
  6. LBV: real;
  7. gud: boolean;
  8.  
  9. working_Data:TStringList;// DataList Type;
  10.  
  11. Function Check_Value_Is_Valid_Integer(Var AString:String; Var AValue:Integer):Boolean;
  12. var VErrorCode;
  13. begin
  14.   result:=true;
  15.   If AString='' then exit(False);
  16.   if (Ord(AString[1]) < 48) or (Ord(AString[1]) > 57) then Delete(AString, 1, 1);
  17.   If AString='' then exit(False);
  18.   Val(AString, AValue, VErrorCode);
  19.   If VErrorCode<>0 then exit(False);
  20.   // You can check for valid range if you like ie If ((AValue<-100) or (AValue>200)) then exit(false);
  21. end;
  22.  
  23. Function Check_Value_Is_Valid_Real(Var AString:String; Var AValue:Real):Boolean;
  24. var VErrorCode;
  25. begin
  26.   result:=true;
  27.   If AString='' then exit(False);
  28.   if (Ord(AString[1]) < 48) or (Ord(AString[1]) > 57) then Delete(AString, 1, 1);
  29.   If AString='' then exit(False);
  30.   Val(AString, AValue, VErrorCode);
  31.   If VErrorCode<>0 then exit(False);
  32.   // You can check for valid range if you like ie If ((AValue<-100.00) or (AValue>200.00)) then exit(false);
  33. end;
  34.  
  35. begin
  36.  Result:=False;
  37.  Working_Data.TStringList.Create;
  38.  Working_Data:=DataList;// Make a copy of DataList immediately; incase it gets changed mid run of routine.
  39.  if(Working_Data.Count <> 16) then exit(False); // Exit function with reult set as false
  40.  if Assigned(ElecForm) then
  41.  begin
  42.    v:=Working_Data.Strings[0];
  43.    If Check_Value_Is_Valid_Real(v,tmp) Then
  44.    begin
  45.      voltage := 2 * (tmp - 200);
  46.      ElecForm.VoltBar.Progress := round(voltage);
  47.      ElecForm.VdigLabel.Caption := V + 'V';
  48.      ElecForm.VdigLabel.Repaint;
  49.    End
  50.    else
  51.    begin
  52.      Working_Data.Free;
  53.      Exit(False);
  54.    end;
  55.  
  56.    i:=Working_Data.Strings[1];
  57.    If Check_Value_Is_Valid_Real(i,current) Then
  58.    begin
  59.      ElecForm.IdigLabel.Caption := I + 'A';
  60.      ElecForm.IdigLabel.Repaint;
  61.       //Set appropriate range for display
  62.      if current > 10.0 then
  63.      begin
  64.        ElecForm.IunitsLabel.Caption := HAScale;
  65.        ElecForm.CurrentBar.Progress := round(5 * current);
  66.      end
  67.      else
  68.      begin
  69.        ElecForm.IunitsLabel.Caption := LAScale;
  70.        ElecForm.CurrentBar.Progress := round(10 * current);
  71.      end;
  72.    End
  73.    else
  74.    begin
  75.      Working_Data.Free;
  76.      Exit(False);
  77.    end;
  78.  
  79.    P := Working_Data.Strings[2];
  80.    If Check_Value_Is_Valid_Real(p,power) Then
  81.    begin
  82.      if power > 1000 then
  83.      begin
  84.        ElecForm.PUnitsLabel.Caption := HPScale;
  85.        ElecForm.Label14.Caption := 'Power (kW):';
  86.        ElecForm.PowerBar.Progress := round(power/50);
  87.        ElecForm.PdigLabel.Caption := FloatToStrF(power/1000, ffFixed,2,2) + 'kW';
  88.      end
  89.      else
  90.      begin
  91.        ElecForm.PUnitsLabel.Caption := LPScale;
  92.        ElecForm.Label14.Caption := 'Power (W):';
  93.        ElecForm.PowerBar.Progress := round(power / 10);
  94.        ElecForm.PdigLabel.Caption := P + 'W';
  95.      end;
  96.    end
  97.    else
  98.    begin
  99.      Working_Data.Free;
  100.      Exit(False);
  101.    end;
  102.  
  103.    //add values to StatusList
  104.    { StatusList.Strings[13] :=  ls + 'V = ' + ElecForm.VdigLabel.Caption + ', I = '
  105.           + ElecForm.IdigLabel.Caption + ', P = ' + ElecForm.PdigLabel.Caption + le; }
  106.    //Used today
  107.    Today := Working_Data.Strings[10];
  108.    If Check_Value_Is_Valid_Real(today,used_today) Then
  109.    begin
  110.      if(used_today >= 0) and (used_today < 100) then
  111.      begin
  112.        ElecForm.TodayBar.Progress := round(used_today * 10);
  113.        ElecForm.TdigLabel.Caption := Today + 'kWh';
  114.        StatusList.Strings[14] := ls + 'Used today = ' + ElecForm.TdigLabel.Caption + le;
  115.        ElecForm.TdigLabel.Repaint;
  116.      end;
  117.      if(used_today > 6.0) then ElecForm.TodayBar.ForeColor  := clRed
  118.      else ElecForm.TodayBar.ForeColor := clLime;
  119.    End;
  120.  
  121.    //daily average
  122.    DAverage := Working_Data.Strings[9];
  123.    If Check_Value_Is_Valid_Real(DAverage,daily_average) Then
  124.    Begin
  125.      if(daily_average < 20) then
  126.      begin
  127.        ElecForm.DAvg.Progress := round(daily_average * 5);
  128.        ElecForm.DAdigLabel.Caption := DAverage + 'kWh';
  129.        //StatusList.Strings[15] := ls + 'Daily Avg = ' + ElecForm.DAdigLabel.Caption + le;
  130.      end;
  131.      if(daily_average > 6) then ElecForm.DAvg.ForeColor := clRed
  132.      else ElecForm.DAvg.ForeColor := clLime;
  133.    end;
  134.  
  135.    //Excess units
  136.    AnyString:= Working_Data.Strings[11];
  137.    If Check_Value_Is_Valid_Real(AnyString,excess) Then
  138.    Begin
  139.      if(excess < 0) then excess := 0;
  140.      ElecForm.ExcessUnits.Caption := FloatToStrF(excess, ffFixed,3,1)+'kWh';
  141.    end;
  142.  
  143.    //Currency unit
  144.    if not Assigned(setupform) then exit;
  145.    if curSymbol = '' then
  146.    begin
  147.      AnyString:= setupData.Strings[3];
  148.      If Check_Value_Is_Valid_Integer(AnyString,tmp1) Then
  149.      Begin
  150.        if tmp1 = 1 then curSymbol := '€' else curSymbol := '£';
  151.      end
  152.      else
  153.      begin
  154.        Working_Data.Free;
  155.        Exit(False);
  156.      end;
  157.    end;
  158.  
  159.    //Cost
  160.    AnyString:= Working_Data.Strings[12];
  161.    If Check_Value_Is_Valid_Real(AnyString,cost) Then
  162.    begin
  163.      if cost < 0 then cost := 0;
  164.      ElecForm.Cost.Caption := AnyString;
  165.    end
  166.    else
  167.    begin
  168.      Working_Data.Free;
  169.      Exit(False);
  170.    end;
  171.  
  172.      //end;
  173.     //StatusList.Strings[16] := '';
  174.    { if CurSymbol = '£' then
  175.      StatusList.Strings[16] := ls + 'Cost = ' + '£'
  176.          + DataList.Strings[12] + le
  177.     else
  178.      StatusList.Strings[16] := ls + 'Cost = ' + DataList.Strings[12]
  179.         + '€' + le; }
  180.    ElecForm.Cost.Caption := '';
  181.    if CurSymbol = '€' then ElecForm.Cost.Caption := 'Cost = ' + DataList.Strings[12] + CurSymbol
  182.    else ElecForm.Cost.Caption := 'Cost = ' + CurSymbol + DataList.Strings[12];
  183.  
  184.  
  185.      //process LBV, ChgI
  186.    for j := 0 to 5 do
  187.    begin
  188.      gud := false;
  189.        //read SOLAR battery voltage value
  190.      solarData := srData('SOLAR', '12548'); //0x3104
  191.        //get rid of CR at end of string
  192.      if Length(solarData) > 1 then SetLength(solarData, Length(solarData) - 1);
  193.      If Check_Value_Is_Valid_Real(solarData,LBV) Then
  194.      begin
  195.        gud := true;
  196.        break;
  197.      end;
  198.    end;
  199.  
  200.      //final check in case all 5 read attempts failed
  201.    if gud = false then
  202.    begin
  203.      Working_Data.Free;
  204.      Exit(False);
  205.    end
  206.    else
  207.    begin
  208.      LBV := LBV/100; //mV to V
  209.      ElecForm.LBVValue.Caption := FloatToStrF(LBV, ffFixed,3,2) + 'V';
  210.         {StatusList.Strings[18] := ls + 'Pri Bat = '
  211.             + ElecForm.LBVValue.Caption + ' Sec = ' + le;}
  212.          //offset as scale starts at 8V
  213.      LBV := LBV - 8;
  214.      if LBV < 0 then LBV := 0;
  215.      ElecForm.LBVBar.Progress := round(LBV * 10);
  216.    end;
  217.  
  218.      //Battery charge percentage
  219.    for j := 0 to 5 do
  220.    begin
  221.      gud := false;
  222.      solarData := srData('SOLAR', '12570'); //0x311A
  223.      if Length(solarData) > 1 then SetLength(solarData, Length(solarData) - 1);
  224.      If Check_Value_Is_Valid_Real(solarData,Chg) Then
  225.      begin
  226.        gud := true;
  227.        break;
  228.      end;
  229.    end;
  230.  
  231.    if gud = false then exit
  232.    else
  233.    begin
  234.      if Chg > 25 then ElecForm.BatState.ForeColor := clLime
  235.      else ElecForm.BatState.ForeColor := clRed;
  236.      ElecForm.BatState.Progress := Chg;
  237.    end;
  238.  
  239.      //LBI
  240.    AnyString:= Working_Data.Strings[6];
  241.    If Check_Value_Is_Valid_Real(AnyString,LBI) Then
  242.    begin
  243.      LBI := LBI - 499;
  244.      if LBI < 0 then LBI := 0;
  245.      ElecForm.ChgI.Progress := round (LBI/5.843);
  246.      ElecForm.LBIValue.Caption := FloatToStrF(LBI/10.5, ffFixed,3,2) + 'A';
  247.    end
  248.    else
  249.    begin
  250.      Working_Data.Free;
  251.      Exit(False);
  252.    end;
  253.  
  254.      //solar charge current
  255.    for j := 0 to 5 do
  256.    begin
  257.      gud := false;
  258.      solarData := srData('SOLAR', '12549'); //0x3105
  259.        //get rid of CR at end of string, only if string isn't empty
  260.         //or an Access violation occurs
  261.      if Length(solarData) > 1 then
  262.      SetLength(solarData, Length(solarData) - 1);
  263.      If Check_Value_Is_Valid_Real(solarData,solarI) Then
  264.      begin
  265.        gud := true;
  266.        break;
  267.      end;
  268.    end;
  269.  
  270.    if gud = false then
  271.    begin
  272.      Working_Data.Free;
  273.      Exit(False);
  274.    end
  275.    else
  276.    begin
  277.      //mA to A
  278.      if solarI < 0 then solarI := 0;
  279.      solarI := solarI/100;
  280.      ElecForm.SolarIval.Caption  := FloatToStrF(solarI, ffFixed,3,2) + 'A';
  281.      ElecForm.SolarBar.Progress := 2 * round(solarI);
  282.    end;
  283.     {StatusList.Strings[19] := ls + 'Load = ' + ElecForm.LBIValue.Caption
  284.         + ', Mchg = ???A, ' + 'Schg = ' + ElecForm.SolarIval.Caption + le;}
  285.    Result:=True;
  286.  end;
  287. end;
  288.  
Title: Re: Hopeless Debugging
Post by: Thaddy on March 11, 2018, 10:38:25 am
@BLL
If now you know have a range check error, you are in luck because range checks are really easy to debug and can also be caught at compile time if you define the expected range instead of an integer type.
Can you show us the line where that happens (compile with -glh and {$R+} and the types used on that line?
My first entry in the wiki about defensive programming addresses that issue more or less. http://wiki.freepascal.org/Defensive_programming_techniques

(Next week there will be much more, but the range check code and text stays the same because it has been scrutinized by our peers)
Title: Re: Hopeless Debugging
Post by: BLL on March 11, 2018, 03:55:43 pm
Hi all,
Thanks for the help. Great minds must think alike. I am now sure that the problem is a bad string. To this end, I have written a function not unlike one of the replies. It takes the string and the function either returns a real, if the string is valid, -1 for an empty string and any non numeric characters are deleted. I wrote it as a little test program and gave i all sorts of numbers, characters, spaces etc and it performed fine. I have this morning replaced all the Val routines in doElecState with calls to this test function. The program has so far been running for a few hours with no error dialogue.

An interesting spinoff of this is that some of the data comes via Modbus/RS485 from my solar charger. Previously, it took up to 6 read requests to get back valid data. Now it works perfectly each and every tiime. There is a problem with the PIC, which I have not been able to track down, despite many hours of trying: My program sends a character to the PIC. An interrupt is generated whenever characters arrive at the PIC and the PIC then sends back the string of data. Problem is that sometimes it returns nothing or only part of the data. I have tried various baud rates to no avail, so until I can track that problem down, I have to check the received data. I guess that a checksum might well be the way to go. Thanks Josh & Engkin for your ideas and code which I will go and study.

I really do appreciate the help I am getting - thank you all very much.

Brian
Title: Re: Hopeless Debugging
Post by: Josh on March 11, 2018, 07:57:58 pm
Hi

Just knocked up a quick project that show an alternative method, whether its usefull I do not know.

i have created a record that has all the values and wether they are valid.

You populate an string array, call one routine, it then validates each one, removes any non numerical characters first, and popuilate the record.

You can then check ie if Current_Data.Voltage.Valid then voltage_guage.progress:=Current_data.voltage.value;

It may be of use. Just an idea.

Attached project
Title: Re: Hopeless Debugging
Post by: avra on March 12, 2018, 09:45:00 am
I tried in vain to use pascal serial routines. sdposerial was completely unreliable, often seizing up ports, which is why I went to calling C progs instead.
I am using synaser for years with various loggers, custom made AVR and ARM devices, with MODBUS and other protocols over real and USB COM ports. You should really try it instead of external C progs, because it is rock solid. You could also take a look at some threads and follow wiki links mentioned there:
https://forum.lazarus.freepascal.org/index.php/topic,33423.msg266332.html#msg266332
https://forum.lazarus.freepascal.org/index.php/topic,36885.msg246260.html#msg246260
https://forum.lazarus.freepascal.org/index.php/topic,36523.msg245030.html#msg245030
Title: Re: Hopeless Debugging
Post by: Thaddy on March 12, 2018, 10:32:12 am
@Avra
@BLL
That's good advice, Avra. I would recommend the same. It is rock solid. (It is also maintained, but doesn't need frequent updates so people think synapse/synaser is not maintained)

Anyway: last status was range error: that is easy to debug.
Title: Re: Hopeless Debugging
Post by: BLL on March 12, 2018, 10:50:00 am
Hi
Thanks again for the replies. I tried synaser and it was hopeless - perhaps it doesn't like the RasPi? Have you used it on a Raspi3 with jessie?
I have now got some of the program running from within lazarus, which has already thrown up some errors. However, one page of my program displays weather data from a weather station. There is a command line utility called te923con which reads data from the weather station and displays it to screen as a colon delimited string. Problem is that it requires sudo or it returns a configuration error and a result of -1. Now, as I am running my program as ordinary user pi, I don't get the weather data. Is there a way of calling te923con from within my program but as an elevated user and if so, how please? If this problem can be overcome then I can debug the whole program from within lazarus.

Brian
Title: Re: Hopeless Debugging
Post by: avra on March 12, 2018, 11:30:40 am
I tried synaser and it was hopeless - perhaps it doesn't like the RasPi?
You didn't follow my links. First link directly mentions RPi, second link mentions it indirectly. So yes, synapse runs fine on RPi. I am sorry to hear that you have such a different experience. If you follow the links you will find simple examples, wiki articles, most common mistakes and educational discussion. Use it.
Title: Re: Hopeless Debugging
Post by: Thaddy on March 12, 2018, 11:48:03 am
@BLL
I answer from a Raspberry Pi (can be zero, 1,2 or 3) and any examples I gave for Synapse were written on a Raspberry Pi 3, if necessary only checked against Windows 10......
If you still have problems provide code to reproduce it. I can check it and put you on the right track. Basically the RPi's are issue-free with Synapse/Synaser..
Title: Re: Hopeless Debugging
Post by: Bram71 on March 12, 2018, 12:13:02 pm
In order for a "normal" user to use the serial port under Linux you have to add the user to the group "dailout".

Like the command below:

sudo adduser rpi dialout

After that you no longer need to run your program with sudo
Title: Re: Hopeless Debugging
Post by: BLL on March 12, 2018, 12:49:06 pm
When I do sudo adduser pi dialout, it says that pi is already a member!

Brian
Title: Re: Hopeless Debugging
Post by: BLL on March 12, 2018, 01:03:32 pm
I looked at permissions for te923con.
It says the owner is root and the group is root.
Access is for anyone.

I don't profess to understand linux owners and groups - I am just confused!
Do these need changing and if so, how?
Thanks
Brian
Title: Re: Hopeless Debugging
Post by: BLL on March 15, 2018, 08:17:13 pm
The program is now seizing with no error messages whatsoever. How the hell is one supposed to debug the program? What a load of crap lazarus appears to be! How the hell do you debug a program when it gives you no help at all?

In absolute frustration.
Brian
TinyPortal © 2005-2018