SAJ Solar Panel Integration

It’s the same file. Maybe you get an other exception or it is not used. I added a catch all for exceptions and a log entry to check if the function read is used. Could you check if those entries are listed in the HA logs?

(I did only list the read function … the rest can be the same)

    async def read(self, sensors):
        """Returns necessary sensors from SAJ inverter"""
        _LOGGER.warning('The pysaj read function is called')

        try:
            timeout = aiohttp.ClientTimeout(total=5)
            async with aiohttp.ClientSession(timeout=timeout,
                                             raise_for_status=True) as session:
                current_url = self.url_info
                async with session.get(current_url) as response:
                    data = await response.text()

                    if self.wifi:
                        csv_data = StringIO(data)
                        reader = csv.reader(csv_data)

                        for row in reader:
                            self.serialnumber = row.pop(0)
                    else:
                        xml = ET.fromstring(data)

                        find = xml.find("SN")
                        if find is not None:
                            self.serialnumber = find.text

                    _LOGGER.debug("Inverter SN: %s", self.serialnumber)

                current_url = self.url
                async with session.get(current_url) as response:
                    data = await response.text()
                    at_least_one_enabled = False

                    if self.wifi:
                        csv_data = StringIO(data)
                        reader = csv.reader(csv_data)
                        ncol = len(next(reader))
                        csv_data.seek(0)

                        values = []

                        for row in reader:
                            for (i, v) in enumerate(row):
                                values.append(v)

                        for sen in sensors:
                            if ncol < 24:
                                if sen.csv_1_key != -1:
                                    try:
                                        v = values[sen.csv_1_key]
                                    except IndexError:
                                        v = None
                                else:
                                    v = None
                            else:
                                if sen.csv_2_key != -1:
                                    try:
                                        v = values[sen.csv_2_key]
                                    except IndexError:
                                        v = None
                                else:
                                    v = None

                            if v is not None:
                                if sen.name == "state":
                                    sen.value = MAPPER_STATES[v]
                                else:
                                    sen.value = eval(
                                        "{0}{1}".format(v, sen.factor)
                                    )
                                sen.date = date.today()
                                sen.enabled = True
                                at_least_one_enabled = True
                    else:
                        xml = ET.fromstring(data)

                        for sen in sensors:
                            find = xml.find(sen.key)
                            if find is not None:
                                sen.value = find.text
                                sen.date = date.today()
                                sen.enabled = True
                                at_least_one_enabled = True

                    if not at_least_one_enabled:
                        if self.wifi:
                            raise csv.Error
                        else:
                            raise ET.ParseError

                    if sen.enabled:
                        _LOGGER.debug("Got new value for sensor %s: %s",
                                      sen.name, sen.value)

                    return True
        except (asyncio.exceptions.TimeoutError,
                concurrent.futures._base.TimeoutError):
            # Connection to inverter not possible.
            # This can be "normal" - so warning instead of error - as SAJ
            # inverters are powered by DC and thus have no power after the sun
            # has set.
            _LOGGER.warning("Connection to SAJ inverter is not possible. " +
                            "The inverter may be offline due to darkness. " +
                            "Otherwise check host/ip address.")
            return False
        except aiohttp.client_exceptions.ClientResponseError as err:
            # 401 Unauthorized: wrong username/password
            if err.status == 401:
                raise UnauthorizedException(err)
            else:
                raise UnexpectedResponseException(err)
        except csv.Error:
            # CSV is not valid
            raise UnexpectedResponseException(
                str.format("No valid CSV received from {0} at {1}", self.host,
                           current_url)
            )
        except ET.ParseError:
            # XML is not valid or even no XML at all
            raise UnexpectedResponseException(
                str.format("No valid XML received from {0} at {1}", self.host,
                           current_url)
            )
        except Exception as e:
            _LOGGER.warning('Uncaught Exception Found: {}'.format(type(e)))

Thanks again. I added the line (learning a new programming language :-)) and restarted HA. This is what I see:

So, the function is called! Some minutes later, it showed up 5 times.

edit: I only added the LOGGER part if the function is called.

Very strange. I see this in HA after the night:
image

While in the meantime the current status in SAJ is:

So it seems HA picks up the status in the morning, but freezes then as the status stays on Wait.

And this is in the log:

So around 8:34 latest communication with SAJ, while it just up and running.

Yes your right, the fix to catch the exception gives an improvement but does not fix everything. The problem seems to be that the sensor is not scheduled again. Still searching what the cause is of this.

What seems to be the second problem, is that the saj component stays in retrying mode till the converter is up. After the saj is setup, it attached the update function to the EVENT_HOMEASSISTANT_START event. But when HA is already finished with the startup it will not be fired again. So my (evil?) solution is to just add the update function without using the start event …

For this i changed the file ‘homeassistant/components/saj/sensor.py’, which is part of the HA project.
I commented 1 line and added a line to directly start the update.

    #hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_update_interval)
    start_update_interval(None)
    hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, stop_update_interval)

Cool. Gonna try that too. That will be tomorrow.

update: Commented 1 line, and added the new one. Let’s see…

I did the changes yesterday evening and restarted HA. This morning a saw the status normal, so it was picked up! I will let it go today, no restart, and curious about tomorrow.

@ferdinand Do you know how often the sensor polls SAJ? It looks like it does it every second, a bit too often if you ask me.

Good to hear, it works now fine for me too.

About the sensor polls, there is a timeout (seconds) in the sensor.py code, you could tweak it, but i doubt that they will change it upstream.

MIN_INTERVAL = 5
MAX_INTERVAL = 300

I found that in de code too, was wondering if that would do anything. Well I leave it for now, to see what happens tomorrow. after that I will play with the intervals. Thanks!

If the connection is failing it increases the timeout till the max, if everything is ok again it resets to the min.

Too bad, this morning in the logs:

So the status is unavailble again. Strange thing is, that (not 100% sure) it is just the time the inverter is starting up.

Did you upgrade to the next version? Because that overwrites the file. I’m tying to get my first patch approved … working on it to make it part of HA.

See: https://github.com/home-assistant/core/pull/65796

Whoops. Yes I had en upgrade (twice). Just a beginner LOL.

Hopefully, it will be implemented soon.

HA is fast! It’s already released in 2022.2.3:

So if you upgrade you have the “slightly” improved version.

… only the maintainer of the external SAJ module is not responding so the exception bug is still a manual fix.

2 Likes

@ferdinand Is it still working for you? The last days SAJ is unavailable again. This could be the updates from HA, but it also could be the way watchtower is updating HA. Not sure. I will stop watchtower for some days and hopefully it is working again then.

This morning SAJ was unavailable again and I stopped watchtower 3 days ago. Not sure what goes wrong, but it’s not 100% working yet.

Update, even after a restart it doesn’t work anymore. I can reach the GUI of SAJ via browser. Weird.

Update2: I moved the part in configuration.yaml to the end of the file and now it works again. Yesterday evening I added a utility meter part and that seems to be the problem. Why is that?

Fixed the issue: must be a beginner mistake… I had 2 times sensor: in my config. :crazy_face:

Hi All, I started with Home Assistant not so long ago so everything is still very new to me. I also have the SAJ integration running, and it seemed to work better with the changes implemented by @ferdinand. But now this week I had 2 days where I had to restart Home Assistant twice before the SAJ sensors came back online. I didn’t change anything in the yaml files in the meantime. So maybe @Geeforce you had a similar issue. I haven’t got enough time or experience to start debugging myself, so for the moment I’m hoping somebody else comes with a fix for this problem :slight_smile:

Any update on this?

I am using latest version of Home Assistant (as installed through Hyper-V) and it is a pain having to restart Home Assistant every morning once the sun is up and the SAJ inverter has booted.

I could change the files myself, but I do not know how to access the files in the container.

Of course it would be best if this patch of exceptions would be incorporated into the official release.

I finally managed to make some time to read through enough forum posts to implement the change proposed by @ferdinand into the pysaj package. Steps below here, it wasn’t easy for me, as I have almost no experience with this kind of stuff. HA OS on Rpi3.

  • Enable “Advanced Mode” for the user profile in HA
  • Install an add-on called “SSH & Web Terminal” (not “Terminal & SSH”)
  • Disable Protection Mode for the add-on
  • Under configuration of the addon change the password to a password
  • Start the addon and use following commands
    • docker exec -it homeassistant bash
  • to find the location of the pysaj folder
    • find / -name pysaj ()
  • go to the directory and open the file with vi editor
    • cd /usr/local/lib/python3.10/site-packages/pysaj
    • vi __init__.py
  • add asyncio.exceptions.TimeoutError, to line 226
  • Following commands can be used in the vi editor
    • i to edit, esc to command
    • :wq to save and exit
    • :q! to exit

It seems to solve the issue, so I’m quite happy. Only problem is that you need to redo this after every core update I think. Anybody has an idea how to get this bugfix added to the pysaj code GitHub - fredericvl/pysaj. It seems that the codeowner isn’t active anymore.

1 Like