priiduonu / ihcclient

client for IHCServer
3 stars 0 forks source link

Status Outputs #2

Closed nisupa135 closed 2 weeks ago

nisupa135 commented 1 month ago

Thank you very much for your work. I was able to integrate my old system, and it works correctly in Home Assistant. The only problem is that I cannot see the status of the outputs; they do not update. If I action the output from Home Assistant, it works, but if, for example, a light is turned on or off with a switch or another system, it does not show as on. Is there a solution? On the IHCServer Webinterface, the status of the output is visible and changes correctly.

priiduonu commented 1 month ago

Thanks,

If everything is configured correctly, it should work regardless of which method was used to change the IHC module output status.

Can you post the relevant config sections for the outputs (for Home Assistant: command_lines.yaml, main configuration.yaml, for IHCclient: modules.yaml, settings.yaml)?

nisupa135 commented 1 month ago

Thank you for replying, this is what i have in the files: command_lines.yaml

- switch: 
    name: 1_1
    command_on: curl --silent 'http://192.168.1.110:8081/ihcrequest' --data-binary '{"type":"setOutput","moduleNumber":1,"ioNumber":1,"state":true}'
    command_off: curl --silent 'http://192.168.1.110:8081/ihcrequest' --data-binary '{"type":"setOutput","moduleNumber":1,"ioNumber":1,"state":false}'

- switch: 
    name: 1_2
    command_on: curl --silent 'http://192.168.1.110:8081/ihcrequest' --data-binary '{"type":"setOutput","moduleNumber":1,"ioNumber":2,"state":true}'
    command_off: curl --silent 'http://192.168.1.110:8081/ihcrequest' --data-binary '{"type":"setOutput","moduleNumber":1,"ioNumber":2,"state":false}'

- switch: 
    name: 1_3
    command_on: curl --silent 'http://192.168.1.110:8081/ihcrequest' --data-binary '{"type":"setOutput","moduleNumber":1,"ioNumber":3,"state":true}'
    command_off: curl --silent 'http://192.168.1.110:8081/ihcrequest' --data-binary '{"type":"setOutput","moduleNumber":1,"ioNumber":3,"state":false}'

configuration.yaml

# Loads default set of integrations. Do not remove.
default_config:

# Load frontend themes from the themes folder
frontend:
  themes: !include_dir_merge_named themes

automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
command_line: !include command_lines.yaml
shell_command: !include shell_commands.yaml
homeassistant: 
  customize: !include customize.yaml

modules.yaml

#######################################
# outputs
#######################################

# IHC output module 1, output 1
# llum pilot sofa
- type: outputState
  moduleNumber: 1
  ioNumber: 1
  entity: switch.1_1

# IHC output module 1, output 2
# llum zona estar
- type: outputState
  moduleNumber: 1
  ioNumber: 2
  entity: switch.1_2

# IHC output module 1, output 3
# llum zona estar totes
- type: outputState
  moduleNumber: 1
  ioNumber: 3
  entity: switch.1_3

settings.yaml

# yaml configuration file for ihcclient

# IHCServer settings
ihc:
  server: 192.168.1.110
  port: 8081
  username:
  password:

# HASS server settings
hass:
  server: 192.168.1.111
  port: 8123
  token: xxxx (i have the real token here)

# logfile name:
log_file: /var/log/ihc.log

Let's see if we're lucky and can find what's wrong.

priiduonu commented 1 month ago

No errors detected here.

So, if you switch the IHC output from HA, it switches correctly but the HA entity does not indicate status change?

Maybe try to add the customize.yaml as described in my guide and set the assumed_state: false for the switches? Also take a look at the log file and try to debug with the wscat utility.

Side tip - please use the triple backticks to include code listings for readability.

priiduonu commented 1 month ago

Did you solve this? Also worth checking if your hass token in settings.yaml is of correct length (should be 183 characters) and surrounded by single quotes (').

nisupa135 commented 3 weeks ago

Hello,

I hadn't been home, so I wasn't able to test it before. After checking everything and adding the customize.yaml, it still wasn't working. My programming knowledge is limited, and using ChatGPT, it recommended adding the following modification to the ihcclient.py, and now it works perfectly.

def setHAState(entity, state):
    # Note that we must first get the attributes, otherwise they will get lost
    # if we later post only the state of the entity!
    response = session.get('http://' + hass_server + '/api/states/' + entity, headers=hass_headers, verify=False)
    json_data = response.json()
    print(json_data)  # For debugging
    if 'attributes' in json_data:
        attributes = json_data['attributes']
        # Now we can post state AND attributes:
        session.post('http://' + hass_server + '/api/states/' + entity, json={'attributes': attributes, 'state': state}, headers=hass_headers, verify=False)
        logging.info('%s - %s %s', attributes['friendly_name'], entity, state)
    else:
        print(f"No attributes found in the state for entity {entity}")
        logging.warning('No attributes found for entity %s', entity)

I don't quite understand why this modification makes it work, but it truly does. Maybe you can identify the reason.

This is the complete code of ihcclient.py:


#!/usr/bin/env python3

# listens to IHC events and updates the HA switch and sensor states accordingly

import sys
import logging
import json
import yaml
import requests
import websocket
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def readYamlConfig(configfile):
    try:
        with open(configfile, 'r') as file:
            config_data = yaml.safe_load(file)
        return config_data
    except FileNotFoundError:
        sys.exit("Error: file '" + configfile + "' not found")
    except yaml.YAMLError as error:
        sys.exit(error)
    return None

# read main configuration file:
settings = readYamlConfig('settings.yaml')

ihc_server = settings['ihc']['server'] + ":" + str(settings['ihc']['port'])
ihc_username = settings['ihc']['username']
ihc_password = settings['ihc']['password']

hass_server = settings['hass']['server'] + ":" + str(settings['hass']['port'])
hass_token = settings['hass']['token']

log_file = settings['log_file']

# read IHC I/O configuration file:
pins = readYamlConfig('modules.yaml')

# set headers for HASS instance:
hass_headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + hass_token
}

# map IHC states to HASS states:
states = {
    'True': 'on',
    'False': 'off'
}

# set up logging:
logging.basicConfig(filename=log_file,
                    format='%(asctime)s %(levelname)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',
                    level=logging.INFO)
console = logging.StreamHandler()
logging.getLogger('').addHandler(console)

def setHAState(entity, state):
    # Note that we must first get the attributes, otherwise they will get lost
    # if we later post only the state of the entity!
    response = session.get('http://' + hass_server + '/api/states/' + entity, headers=hass_headers, verify=False)
    json_data = response.json()
    print(json_data)  # For debugging
    if 'attributes' in json_data:
        attributes = json_data['attributes']
        # Now we can post state AND attributes:
        session.post('http://' + hass_server + '/api/states/' + entity, json={'attributes': attributes, 'state': state}, headers=hass_headers, verify=False)
        logging.info('%s - %s %s', attributes['friendly_name'], entity, state)
    else:
        print(f"No attributes found in the state for entity {entity}")
        logging.warning('No attributes found for entity %s', entity)

def getIHCStates(moduleType):
    for module in initial_states['modules'][moduleType + 'Modules']:
        ioType = moduleType + 'State'
        if module['state'] is True:
            moduleNumber = module['moduleNumber']
            for state in module[moduleType + 'States']:
                ioNumber = state[moduleType + 'Number']
                pin = next((e for e in pins if (
                    e['type'] == ioType and
                    e['moduleNumber'] == moduleNumber and
                    e['ioNumber'] == ioNumber)
                ), False)

                if pin:
                    entity = pin['entity']
                    state = states.get(str(state[moduleType + 'State']))
                    setHAState(entity, state)

def createHTTPSession():
    session = requests.Session()
    retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
    session.mount('http://', HTTPAdapter(max_retries=retries))
    return session

session = createHTTPSession()

# read initial states from IHC controller:
print("Reading initial IHC states...")
response = session.post('http://' + ihc_server + '/ihcrequest', json={'type': 'getAll'}, auth=(ihc_username, ihc_password))
initial_states = response.json()
# print(initial_states)

getIHCStates('input')
getIHCStates('output')

# wait for events over websocket
ws = websocket.create_connection('ws://' + ihc_server + '/ihcevents-ws')
print("Waiting for IHC events...")

try:
    while True:
        event = ws.recv()
        json_data = json.loads(event)
        # print("Received", json_data)

        ioType = json_data['type']
        if ioType == 'ping':
            ws.send(json.dumps({'type': 'pong'}))
            continue
        moduleNumber = json_data['moduleNumber']
        ioNumber = json_data['ioNumber']
        ioState = json_data['state']

        state = states.get(str(ioState))

        pin = next((e for e in pins if (
            e['type'] == ioType and
            e['moduleNumber'] == moduleNumber and
            e['ioNumber'] == ioNumber)
        ), False)

        if pin:
            entity = pin['entity']
            setHAState(entity, state)

except KeyboardInterrupt:
    print("\nClosing connection...")
except Exception as error:
    print("Error in WebSocket connection:", error)
finally:
    ws.close()
    print("Connection closed.")
priiduonu commented 3 weeks ago

The modification suggested by ChatGPT does not make much sense to me.

It basically checks if the entity has any attributes defined before it updates the status. But when the binary sensors and command line switches have already been defined in the configuration files, they will always have attributes (at least the friendly_name which defaults to entity name).

I experimented with the current code today, adding some new IHC outputs without customize.yaml settings and everything worked as expected. The key is to restart both the HA and the IHCClient after modifications, so HA will know about the new entities.

Glad You got it working, I think the HA just needed a restart.

Your comments are welcome and please let me know if I may close the issue. Or You can do it yourself.