Comments (36)
Hi all, sorry for the late response. Currently (still) sick and there for do not have time and energy to work on it. Maybe someone can make a pull request and test it with an SAJ portal user. I am okay to merge it. Otherwise somewhere next weekend I might be able to work on it.
from sajesolar.
thank you for merging and releasing @djansen1987
from sajesolar.
I managed to make it work temporarily by:
- replacing the domain in the sensor.py
- forcing it not to verify SSL certificates (seems like the cert chain is broken and HASS rejects it):
session = async_create_clientsession(hass,False).
from sajesolar.
The same is happening to me. My plant is gone in the "old platform".
from sajesolar.
I managed to make it work temporarily by:
* replacing the domain in the sensor.py * forcing it not to verify SSL certificates (seems like the cert chain is broken and HASS rejects it): session = async_create_clientsession(hass,False).
Hola puede explicarlo mejor, es que no lo pillo gracias
from sajesolar.
@teamMOYA is your kit branded as SAJ or some other white label (eg. Greenheiss)?
from sajesolar.
Hi, the exact same thing is happening to me but I did what you said and it still doesn't work. Can you explain exactly what needs to be changed?
I have GreenHeiss.
Thanks!
from sajesolar.
@teamMOYA is your kit branded as SAJ or some other white label (eg. Greenheiss)?
Mine are rebranded as "Solarprofit", but they don't have a web like Saj and Greenheiss. I'll have to wait for the new web access update.
from sajesolar.
I managed to make it work temporarily by:
- replacing the domain in the sensor.py
- forcing it not to verify SSL certificates (seems like the cert chain is broken and HASS rejects it):
session = async_create_clientsession(hass,False).
Where do you change the domain to inside sensor.py?
Do you change https://fop.saj-electric.com/saj to https://esaj-home.saj-electric.com/ or something?
from sajesolar.
Hello, I also had that problem.
My Greenheiss h1 inverter. I use an old integration 1.3.1 which I already have working.
Right now everything works perfectly for me
replacing the domain in the sensor.py
I leave a copy of my sensor.py, in case it helps you, but remember that I use 1.3.1
"""
Alternative for the SAJ local API sensor. Unfortunally there is no public api.
This Sensor will read the private api of the eSolar portal at https://fop.saj-electric.com/
"""
import asyncio
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from datetime import timedelta
import datetime
import calendar
from functools import reduce
import logging
from typing import Final
import aiohttp
import async_timeout
import voluptuous as vol
import re
from bs4 import BeautifulSoup
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import (
CONF_RESOURCES,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SENSORS,
CONF_DEVICE_ID,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
ENERGY_KILO_WATT_HOUR,
POWER_WATT,
PERCENTAGE,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle, dt
def add_months(sourcedate, months):
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
day = min(sourcedate.day, calendar.monthrange(year,month)[1])
return datetime.date(year, month, day)
def add_years(d, years):
try:
return d.replace(year = d.year + years)
except ValueError:
return d + (date(d.year + years, 1, 1) - date(d.year, 1, 1))
BASE_URL = 'https://inversores-style.greenheiss.com/cloud/login'
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=3)
SENSOR_PREFIX = 'esolar '
ATTR_MEASUREMENT = "measurement"
ATTR_SECTION = "section"
SENSOR_LIST = {
"nowPower",
"runningState",
"devOnlineNum",
"todayElectricity",
"monthElectricity",
"yearElectricity",
"totalElectricity",
"todayGridIncome",
"income",
"lastUploadTime",
"totalPlantTreeNum",
"totalReduceCo2",
"todayAlarmNum",
"plantuid",
"plantname",
"currency",
"address",
"isOnline",
"status",
"peakPower",
#sec & h1
"pvElec",
"useElec",
"buyElec",
"sellElec",
"buyRate",
"sellRate",
"selfUseRate",
"totalBuyElec",
"totalConsumpElec",
"totalSellElec",
"selfConsumedRate1",
"selfConsumedRate2",
"selfConsumedEnergy1",
"selfConsumedEnergy2",
"plantTreeNum",
"reduceCo2",
"totalGridPower",
"totalLoadPower",
"totalPvgenPower",
"totalPvEnergy",
"totalLoadEnergy",
"totalBuyEnergy",
"totalSellEnergy",
#h1
"chargeElec",
"dischargeElec",
"batCapcity",
"isAlarm",
"batCurr",
"batEnergyPercent",
"batteryDirection",
"batteryPower",
"gridDirection",
"gridPower",
"h1Online",
"outPower",
"outPutDirection",
"pvDirection",
"pvPower",
"solarPower",
#h1s2
"h1s2_pv_volt_1",
"h1s2_pv_current_1",
"h1s2_pv_power_1",
"h1s2_pv_volt_2",
"h1s2_pv_current_2",
"h1s2_pv_power_2",
"h1s2_pv_volt_3",
"h1s2_pv_current_3",
"h1s2_pv_power_3",
"h1s2_bat_volt",
"h1s2_bat_current",
"h1s2_bat_power",
"h1s2_bat_total_charge",
"h1s2_bat_total_discharge",
"h1s2_load_power",
"h1s2_grid_volt_1",
"h1s2_grid_current_1",
"h1s2_grid_power_1",
"h1s2_grid_volt_2",
"h1s2_grid_current_2",
"h1s2_grid_power_2",
"h1s2_grid_volt_3",
"h1s2_grid_current_3",
"h1s2_grid_power_3",
}
SENSOR_TYPES: Final[tuple[SensorEntityDescription]] = (
SensorEntityDescription(
key="nowPower",
name="nowPower",
icon="mdi:solar-power",
native_unit_of_measurement=POWER_WATT,
device_class=DEVICE_CLASS_POWER,
),
SensorEntityDescription(
key="runningState",
name="runningState",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="devOnlineNum",
name="devOnlineNum",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="todayElectricity",
name="todayElectricity",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="monthElectricity",
name="monthElectricity",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="yearElectricity",
name="yearElectricity",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="totalElectricity",
name="totalElectricity",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="selfUseRate",
name="selfUseRate",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="totalBuyElec",
name="totalBuyElec",
icon="mdi:solar-panel",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="totalConsumpElec",
name="totalConsumpElec",
icon="mdi:solar-panel",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="totalSellElec",
name="totalSellElec",
icon="mdi:solar-panel",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="todayGridIncome",
name="todayGridIncome",
icon="mdi:currency-eur",
),
SensorEntityDescription(
key="income",
name="income",
icon="mdi:currency-eur",
),
SensorEntityDescription(
key="lastUploadTime",
name="lastUploadTime",
icon="mdi:timer-sand",
),
SensorEntityDescription(
key="totalPlantTreeNum",
name="totalPlantTreeNum",
icon="mdi:tree",
),
SensorEntityDescription(
key="totalReduceCo2",
name="totalReduceCo2",
icon="mdi:molecule-co2",
),
SensorEntityDescription(
key="todayAlarmNum",
name="todayAlarmNum",
icon="mdi:alarm",
),
SensorEntityDescription(
key="plantuid",
name="plantuid",
icon="mdi:api",
),
SensorEntityDescription(
key="plantname",
name="plantname",
icon="mdi:api",
),
SensorEntityDescription(
key="currency",
name="currency",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="address",
name="address",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="isOnline",
name="isOnline",
icon="mdi:api",
),
SensorEntityDescription(
key="status",
name="status",
icon="mdi:api",
),
SensorEntityDescription(
key="peakPower",
name="peakPower",
icon="mdi:solar-panel",
native_unit_of_measurement=POWER_WATT,
device_class=DEVICE_CLASS_POWER,
),
SensorEntityDescription(
key="pvElec",
name="pvElec",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="useElec",
name="useElec",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="buyElec",
name="buyElec",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="sellElec",
name="sellElec",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="buyRate",
name="buyRate",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="sellRate",
name="sellRate",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="selfConsumedRate1",
name="selfConsumedRate1",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="selfConsumedRate2",
name="selfConsumedRate2",
icon="mdi:solar-panel",
),
SensorEntityDescription(
key="selfConsumedEnergy1",
name="selfConsumedEnergy1",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="selfConsumedEnergy2",
name="selfConsumedEnergy2",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
),
SensorEntityDescription(
key="plantTreeNum",
name="plantTreeNum",
icon="mdi:tree",
),
SensorEntityDescription(
key="reduceCo2",
name="reduceCo2",
icon="mdi:molecule-co2",
),
SensorEntityDescription(
key="totalGridPower",
name="totalGridPower",
icon="mdi:solar-panel",
native_unit_of_measurement=POWER_WATT,
),
SensorEntityDescription(
key="totalLoadPower",
name="totalLoadPower",
icon="mdi:solar-panel",
native_unit_of_measurement=POWER_WATT,
device_class=DEVICE_CLASS_POWER,
),
SensorEntityDescription(
key="totalPvgenPower",
name="totalPvgenPower",
icon="mdi:solar-panel",
native_unit_of_measurement=POWER_WATT,
),
SensorEntityDescription(
key="totalPvEnergy",
name="totalPvEnergy",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="totalLoadEnergy",
name="totalLoadEnergy",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="totalBuyEnergy",
name="totalBuyEnergy",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="totalSellEnergy",
name="totalSellEnergy",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
#h1
SensorEntityDescription(
key="batCapcity",
name="batCapcity",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A·h"
),
SensorEntityDescription(
key="isAlarm",
name="isAlarm",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="batCurr",
name="batCurr",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="batEnergyPercent",
name="batEnergyPercent",
icon="mdi:solar-panel-large",
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
key="batteryDirection",
name="batteryDirection",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="batteryPower",
name="batteryPower",
icon="mdi:solar-panel-large",
native_unit_of_measurement=POWER_WATT,
),
SensorEntityDescription(
key="gridPower",
name="gridPower",
icon="mdi:solar-panel-large",
native_unit_of_measurement=POWER_WATT,
),
SensorEntityDescription(
key="h1Online",
name="h1Online",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="outPower",
name="outPower",
icon="mdi:solar-panel-large",
native_unit_of_measurement=POWER_WATT,
),
SensorEntityDescription(
key="outPutDirection",
name="outPutDirection",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="pvPower",
name="pvPower",
icon="mdi:solar-panel-large",
native_unit_of_measurement=POWER_WATT,
),
SensorEntityDescription(
key="solarPower",
name="solarPower",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="chargeElec",
name="chargeElec",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
SensorEntityDescription(
key="dischargeElec",
name="dischargeElec",
icon="mdi:solar-panel-large",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
#h1s2
SensorEntityDescription(
key="h1s2_pv_volt_1",
name="h1s2_pv_volt_1",
icon="mdi:solar-panel-large",
native_unit_of_measurement="V"
),
SensorEntityDescription(
key="h1s2_pv_current_1",
name="h1s2_pv_current_1",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="h1s2_pv_power_1",
name="h1s2_pv_power_1",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
SensorEntityDescription(
key="h1s2_pv_volt_2",
name="h1s2_pv_volt_2",
icon="mdi:solar-panel-large",
native_unit_of_measurement="V"
),
SensorEntityDescription(
key="h1s2_pv_current_2",
name="h1s2_pv_current_2",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="h1s2_pv_power_2",
name="h1s2_pv_power_2",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
SensorEntityDescription(
key="h1s2_pv_volt_3",
name="h1s2_pv_volt_3",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="h1s2_pv_current_3",
name="h1s2_pv_current_3",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="h1s2_pv_power_3",
name="h1s2_pv_power_3",
icon="mdi:solar-panel-large",
),
SensorEntityDescription(
key="h1s2_bat_volt",
name="h1s2_bat_volt",
icon="mdi:solar-panel-large",
native_unit_of_measurement="V"
),
SensorEntityDescription(
key="h1s2_bat_current",
name="h1s2_bat_current",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="h1s2_bat_power",
name="h1s2_bat_power",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
SensorEntityDescription(
key="h1s2_bat_total_charge",
name="h1s2_bat_total_charge",
icon="mdi:solar-panel-large",
native_unit_of_measurement="Ah"
),
SensorEntityDescription(
key="h1s2_bat_total_discharge",
name="h1s2_bat_total_discharge",
icon="mdi:solar-panel-large",
native_unit_of_measurement="Ah"
),
SensorEntityDescription(
key="h1s2_load_power",
name="h1s2_load_power",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
SensorEntityDescription(
key="h1s2_grid_volt_1",
name="h1s2_grid_volt_1",
icon="mdi:solar-panel-large",
native_unit_of_measurement="V"
),
SensorEntityDescription(
key="h1s2_grid_current_1",
name="h1s2_grid_current_1",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="h1s2_grid_power_1",
name="h1s2_grid_power_1",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
SensorEntityDescription(
key="h1s2_grid_volt_2",
name="h1s2_grid_volt_2",
icon="mdi:solar-panel-large",
native_unit_of_measurement="V"
),
SensorEntityDescription(
key="h1s2_grid_current_2",
name="h1s2_grid_current_2",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="h1s2_grid_power_2",
name="h1s2_grid_power_2",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
SensorEntityDescription(
key="h1s2_grid_volt_3",
name="h1s2_grid_volt_3",
icon="mdi:solar-panel-large",
native_unit_of_measurement="V"
),
SensorEntityDescription(
key="h1s2_grid_current_3",
name="h1s2_grid_current_3",
icon="mdi:solar-panel-large",
native_unit_of_measurement="A"
),
SensorEntityDescription(
key="h1s2_grid_power_3",
name="h1s2_grid_power_3",
icon="mdi:solar-panel-large",
native_unit_of_measurement="W"
),
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_RESOURCES, default=list(SENSOR_LIST)): vol.All(
cv.ensure_list, [vol.In(SENSOR_LIST)]
),
vol.Optional(CONF_SENSORS, default="None"): cv.string,
vol.Optional(CONF_DEVICE_ID, default="None"): cv.string,
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Setup the SAJ eSolar sensors."""
session = async_create_clientsession(hass,False)
data = SAJeSolarMeterData(session, config.get(CONF_USERNAME), config.get(CONF_PASSWORD), config.get(CONF_SENSORS), config.get(CONF_DEVICE_ID))
await data.async_update()
entities = []
for description in SENSOR_TYPES:
if description.key in config[CONF_RESOURCES]:
sensor = SAJeSolarMeterSensor(description, data, config.get(CONF_SENSORS))
entities.append(sensor)
async_add_entities(entities, True)
return True
class SAJeSolarMeterData(object):
"""Handle eSolar object and limit updates."""
def __init__(self, session, username, password, sensors, sec_sn):
"""Initialize the data object."""
self._session = session
self._url = BASE_URL
self.username = username
self.password = password
self.sensors = sensors
self.sec_sn = sec_sn
self._data = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
"""Download and update data from SAJeSolar."""
try:
today = datetime.date.today()
clientDate = today.strftime('%Y-%m-%d')
# Login to eSolar API
url = 'https://inversores-style.greenheiss.com/cloud/login'
payload = {
'lang': 'en',
'username': self.username,
'password': self.password,
'rememberMe': 'true'
}
headers_login = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': 'org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=en; op_esolar_lang=en',
'DNT': '1',
'Host': 'inversores-style.greenheiss.com',
'Origin': 'https://inversores-style.greenheiss.com',
'Referer': 'https://inversores-style.greenheiss.com/cloud/login',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
'sec-ch-ua-mobile': '?0',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent'
: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
}
response = await self._session.post(url, headers=headers_login, data=payload)
if response.status != 200:
_LOGGER.error(f"{response.url} returned {response.status}")
return
# Get API Plant info from Esolar Portal
url2 = 'https://inversores-style.greenheiss.com/cloud/monitor/site/getUserPlantList'
headers = {
'Connection': 'keep-alive',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'DNT': '1',
'X-Requested-With': 'XMLHttpRequest',
'sec-ch-ua-mobile': '?0',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Origin': 'https://inversores-style.greenheiss.com',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'Referer': 'https://inversores-style.greenheiss.com/cloud/monitor/home/index',
'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7'
}
payload2= f"pageNo=&pageSize=&orderByIndex=&officeId=&clientDate={clientDate}&runningState=&selectInputType=1&plantName=&deviceSn=&type=&countryCode=&isRename=&isTimeError=&systemPowerLeast=&systemPowerMost="
response2 = await self._session.post(url2, headers=headers, data=payload2)
if response2.status != 200:
_LOGGER.error(f"{response2.url} returned {response2.status}")
return
plantInfo = await response2.json()
plantuid = plantInfo['plantList'][0]['plantuid']
# Get API Plant Solar Details
url3 = "https://inversores-style.greenheiss.com/cloud/monitor/site/getPlantDetailInfo"
payload3= f"plantuid={plantuid}&clientDate={clientDate}"
response3 = await self._session.post(url3, headers=headers, data=payload3)
if response3.status != 200:
_LOGGER.error(f"{response3.url} returned {response3.status}")
return
plantDetails = await response3.json()
plantDetails.update(plantInfo)
# getPlantDetailChart2
plantuid = plantDetails['plantList'][0]['plantuid']
deviceSnArr = plantDetails['plantDetail']['snList'][0]
previousChartDay = today - timedelta(days=1)
nextChartDay = today + timedelta(days = 1)
chartDay = today.strftime('%Y-%m-%d')
previousChartMonth = add_months(today,-1).strftime('%Y-%m')
nextChartMonth = add_months(today, 1).strftime('%Y-%m')
chartMonth = today.strftime('%Y-%m')
previousChartYear = add_years(today, -1).strftime('%Y')
nextChartYear = add_years(today, 1).strftime('%Y')
chartYear = today.strftime('%Y')
epochmilliseconds = round(int((datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)).total_seconds() * 1000))
url4 = f"https://inversores-style.greenheiss.com/cloud/monitor/site/getPlantDetailChart2?plantuid={plantuid}&chartDateType=1&energyType=0&clientDate={clientDate}&deviceSnArr={deviceSnArr}&chartCountType=2&previousChartDay={previousChartDay}&nextChartDay={nextChartDay}&chartDay={chartDay}&previousChartMonth={previousChartMonth}&nextChartMonth={nextChartMonth}&chartMonth={chartMonth}&previousChartYear={previousChartYear}&nextChartYear={nextChartYear}&chartYear={chartYear}&elecDevicesn=&_={epochmilliseconds}"
response4 = await self._session.post(url4, headers=headers)
if response4.status != 200:
_LOGGER.error(f"{response4.url} returned {response4.status}")
return
plantcharts = await response4.json()
plantDetails.update(plantcharts)
# H1 Module
if self.sensors == "h1":
# getStoreOrAcDevicePowerInfo
url_getStoreOrAcDevicePowerInfo = f"https://inversores-style.greenheiss.com/cloud/monitor/site/getStoreOrAcDevicePowerInfo?plantuid=&devicesn={deviceSnArr}&_={epochmilliseconds}"
response_getStoreOrAcDevicePowerInfo = await self._session.post(url_getStoreOrAcDevicePowerInfo, headers=headers)
if response_getStoreOrAcDevicePowerInfo.status != 200:
_LOGGER.error(f"{response_getStoreOrAcDevicePowerInfo.url} returned {response_getStoreOrAcDevicePowerInfo.status}")
return
result_getStoreOrAcDevicePowerInfo = await response_getStoreOrAcDevicePowerInfo.json()
plantDetails.update(result_getStoreOrAcDevicePowerInfo)
_LOGGER.debug(result_getStoreOrAcDevicePowerInfo)
url5 = f"https://inversores-style.greenheiss.com/cloud/cloudMonitor/deviceInfo?devicesn={deviceSnArr}"
response5 = await self._session.get(url5, headers=headers)
if response5.status != 200:
_LOGGER.error(f"{response5.url} returned {response5.status}")
html = await response5.text()
soup = BeautifulSoup(html, "lxml")
span = soup.select(".real_num span")
h1s2= {
"h1s2": {
"h1s2_pv_volt_1":re.sub(r'[^0-9.]', '', (span[0].text).split("/")[0]),
"h1s2_pv_current_1":re.sub(r'[^0-9.]', '', (span[0].text).split("/")[1]),
"h1s2_pv_power_1":re.sub(r'[^0-9.]', '', (span[0].text).split("/")[2]),
"h1s2_pv_volt_2":re.sub(r'[^0-9.]', '', (span[1].text).split("/")[0]),
"h1s2_pv_current_2":re.sub(r'[^0-9.]', '', (span[1].text).split("/")[1]),
"h1s2_pv_power_2":re.sub(r'[^0-9.]', '', (span[1].text).split("/")[2]),
"h1s2_pv_volt_3":re.sub(r'[^0-9.]', '', (span[2].text).split("/")[0]),
"h1s2_pv_current_3":re.sub(r'[^0-9.]', '', (span[2].text).split("/")[1]),
"h1s2_pv_power_3":re.sub(r'[^0-9.]', '', (span[2].text).split("/")[2]),
"h1s2_bat_volt":re.sub(r'[^0-9.]', '', (span[3].text).split("/")[0]),
"h1s2_bat_current":re.sub(r'[^0-9.\-]', '', (span[3].text).split("/")[1]),
"h1s2_bat_power":re.sub(r'[^0234567891.\-]', '', (span[3].text).split("/")[2]),
"h1s2_bat_total_charge":re.sub(r'[^0-9.]', '', (span[4].text).split("/")[0]),
"h1s2_bat_total_discharge":re.sub(r'[^0-9.]', '', (span[4].text).split("/")[1]),
"h1s2_load_power":re.sub(r'[^0-9.]', '', span[5].text),
"h1s2_grid_volt_1":re.sub(r'[^0-9.]', '', (span[6].text).split("/")[0]),
"h1s2_grid_current_1":re.sub(r'[^0-9.]', '', (span[6].text).split("/")[1]),
"h1s2_grid_power_1":re.sub(r'[^0-9.]', '', (span[6].text).split("/")[2]),
"h1s2_grid_volt_2":re.sub(r'[^0-9.]', '', (span[7].text).split("/")[0]),
"h1s2_grid_current_2":re.sub(r'[^0-9.]', '', (span[7].text).split("/")[1]),
"h1s2_grid_power_2":re.sub(r'[^0-9.]', '', (span[7].text).split("/")[2]),
"h1s2_grid_volt_3":re.sub(r'[^0-9.]', '', (span[8].text).split("/")[0]),
"h1s2_grid_current_3":re.sub(r'[^0-9.]', '', (span[8].text).split("/")[1]),
"h1s2_grid_power_3":re.sub(r'[^0-9.]', '', (span[8].text).split("/")[2]),
"h1s2_pv_volt_1":re.sub(r'[^0-9.]', '', (span[0].text).split("/")[0]),
"h1s2_pv_current_1":re.sub(r'[^0-9.]', '', (span[0].text).split("/")[1]),
"h1s2_pv_power_1":re.sub(r'[^0-9.]', '', (span[0].text).split("/")[2]),
"h1s2_pv_volt_2":re.sub(r'[^0-9.]', '', (span[1].text).split("/")[0]),
"h1s2_pv_current_2":re.sub(r'[^0-9.]', '', (span[1].text).split("/")[1]),
"h1s2_pv_power_2":re.sub(r'[^0-9.]', '', (span[1].text).split("/")[2]),
"h1s2_pv_volt_3":re.sub(r'[^0-9.]', '', (span[2].text).split("/")[0]),
"h1s2_pv_current_3":re.sub(r'[^0-9.]', '', (span[2].text).split("/")[1]),
"h1s2_pv_power_3":re.sub(r'[^0-9.]', '', (span[2].text).split("/")[2]),
"h1s2_bat_volt":re.sub(r'[^0-9.]', '', (span[3].text).split("/")[0]),
"h1s2_bat_current":re.sub(r'[^0-9.]', '', (span[3].text).split("/")[1]),
"h1s2_bat_power":re.sub(r'[^0-9.]', '', (span[3].text).split("/")[2]),
"h1s2_bat_total_charge":re.sub(r'[^0-9.]', '', (span[4].text).split("/")[0]),
"h1s2_bat_total_discharge":re.sub(r'[^0-9.]', '', (span[4].text).split("/")[1]),
"h1s2_load_power":re.sub(r'[^0-9.]', '', span[5].text),
"h1s2_grid_volt_1":re.sub(r'[^0-9.]', '', (span[6].text).split("/")[0]),
"h1s2_grid_current_1":re.sub(r'[^0-9.]', '', (span[6].text).split("/")[1]),
"h1s2_grid_power_1":re.sub(r'[^0-9.]', '', (span[6].text).split("/")[2]),
"h1s2_grid_volt_2":re.sub(r'[^0-9.]', '', (span[7].text).split("/")[0]),
"h1s2_grid_current_2":re.sub(r'[^0-9.]', '', (span[7].text).split("/")[1]),
"h1s2_grid_power_2":re.sub(r'[^0-9.]', '', (span[7].text).split("/")[2]),
"h1s2_grid_volt_3":re.sub(r'[^0-9.]', '', (span[8].text).split("/")[0]),
"h1s2_grid_current_3":re.sub(r'[^0-9.]', '', (span[8].text).split("/")[1]),
"h1s2_grid_power_3":re.sub(r'[^0-9.]', '', (span[8].text).split("/")[2])
}
}
plantDetails.update(h1s2)
elif self.sensors == "None":
self._data = plantDetails
else:
# Data = plantdetails
self._data = plantDetails
# Sec module
if self.sensors == "saj_sec":
# getPlantMeterModuleList
url_module = "https://inversores-style.greenheiss.com/cloud/saj/cloudmonitor/plantMeterModule/getPlantMeterModuleList"
payload_module = f"pageNo=&pageSize=&plantUid={plantuid}"
response_module = await self._session.post(url_module, headers=headers, data=payload_module)
if response_module.status != 200:
_LOGGER.error(f"{response_module.url} returned {response_module.status}")
return
getPlantMeterModuleList = await response_module.json()
temp_getPlantMeterModuleList = dict()
temp_getPlantMeterModuleList["getPlantMeterModuleList"] = getPlantMeterModuleList
plantDetails.update(temp_getPlantMeterModuleList)
moduleSn = plantDetails["getPlantMeterModuleList"]['moduleList'][0]['moduleSn']
# -Debug- Sec module serial number
_LOGGER.debug(moduleSn)
# findDevicePageList
url_findDevicePageList = "https://inversores-style.greenheiss.com/cloud/cloudMonitor/device/findDevicePageList"
payload_findDevicePageList = f"officeId=1&pageNo=&pageSize=&orderName=1&orderType=2&plantuid={plantuid}&deviceStatus=&localDate={chartMonth}&localMonth={chartMonth}"
response_findDevicePageList = await self._session.post(url_findDevicePageList, headers=headers, data=payload_findDevicePageList)
if response_findDevicePageList.status != 200:
_LOGGER.error(f"{response_findDevicePageList.url} returned {response_findDevicePageList.status}")
return
findDevicePageList = await response_findDevicePageList.json()
temp_findDevicePageList = dict()
temp_findDevicePageList["findDevicePageList"] = findDevicePageList
plantDetails.update(temp_findDevicePageList)
# getPlantMeterDetailInfo
url_getPlantMeterDetailInfo = "https://inversores-style.greenheiss.com/cloud/monitor/site/getPlantMeterDetailInfo"
payload_getPlantMeterDetailInfo = f"plantuid={plantuid}&clientDate={clientDate}"
response_getPlantMeterDetailInfo = await self._session.post(url_getPlantMeterDetailInfo, headers=headers, data=payload_getPlantMeterDetailInfo)
if response_getPlantMeterDetailInfo.status != 200:
_LOGGER.error(f"{response_getPlantMeterDetailInfo.url} returned {response_getPlantMeterDetailInfo.status}")
return
getPlantMeterDetailInfo = await response_getPlantMeterDetailInfo.json()
temp_getPlantMeterDetailInfo = dict()
temp_getPlantMeterDetailInfo["getPlantMeterDetailInfo"] = getPlantMeterDetailInfo
plantDetails.update(temp_getPlantMeterDetailInfo)
# getPlantMeterEnergyPreviewInfo
url_getPlantMeterEnergyPreviewInfo = f"https://inversores-style.greenheiss.com/cloud/monitor/site/getPlantMeterEnergyPreviewInfo?plantuid={plantuid}&moduleSn={moduleSn}&_={epochmilliseconds}"
response_getPlantMeterEnergyPreviewInfo = await self._session.get(url_getPlantMeterEnergyPreviewInfo, headers=headers)
if response_getPlantMeterEnergyPreviewInfo.status != 200:
_LOGGER.error(f"{response_getPlantMeterEnergyPreviewInfo.url} returned {response_getPlantMeterEnergyPreviewInfo.status}")
return
getPlantMeterEnergyPreviewInfo = await response_getPlantMeterEnergyPreviewInfo.json()
temp_getPlantMeterEnergyPreviewInfo = dict()
temp_getPlantMeterEnergyPreviewInfo["getPlantMeterEnergyPreviewInfo"] = getPlantMeterEnergyPreviewInfo
plantDetails.update(temp_getPlantMeterEnergyPreviewInfo)
# Get Sec Meter details
url_getPlantMeterChartData = f"https://inversores-style.greenheiss.com/cloud/monitor/site/getPlantMeterChartData?plantuid={plantuid}&chartDateType=1&energyType=0&clientDate={clientDate}&deviceSnArr=&chartCountType=2&previousChartDay={previousChartDay}&nextChartDay={nextChartDay}&chartDay={chartDay}&previousChartMonth={previousChartMonth}&nextChartMonth={nextChartMonth}&chartMonth={chartMonth}&previousChartYear={previousChartYear}&nextChartYear={nextChartYear}&chartYear={chartYear}&moduleSn={moduleSn}&_={epochmilliseconds}"
response_getPlantMeterChartData = await self._session.post(url_getPlantMeterChartData, headers=headers)
if response_getPlantMeterChartData.status != 200:
_LOGGER.error(f"{response_getPlantMeterChartData.url} returned {response_getPlantMeterChartData.status}")
return
getPlantMeterChartData = await response_getPlantMeterChartData.json()
temp_getPlantMeterChartData = dict()
temp_getPlantMeterChartData["getPlantMeterChartData"] = getPlantMeterChartData
plantDetails.update(temp_getPlantMeterChartData)
# Data = plantdetails including Sec module
self._data = plantDetails
elif self.sensors == "None":
self._data = plantDetails
else:
# Data = plantdetails Wtihout Sec module
self._data = plantDetails
# Error logging
except aiohttp.ClientError:
_LOGGER.error("Cannot poll eSolar using url: %s")
return
except asyncio.TimeoutError:
_LOGGER.error("Timeout error occurred while polling eSolar using url: %s")
return
except Exception as err:
_LOGGER.error("Unknown error occurred while polling eSolar: %s", err)
self._data = None
return
# -Debug- Cookies and Data
_LOGGER.debug(self._session.cookie_jar.filter_cookies("https://inversores-style.greenheiss.com"))
_LOGGER.debug(self._data)
# logout session
url_logout = "https://inversores-style.greenheiss.com/cloud/logout"
response_logout = await self._session.post(url_logout, headers=headers)
if response_logout.status != 200:
_LOGGER.error(f"{response_logout.url} returned {response_logout.status}")
return
# Clear session and cookies
self._session.cookie_jar.clear()
self._session.close()
@property
def latest_data(self):
"""Return the latest data object."""
if self._data:
return self._data
_LOGGER.error("return data NONE")
return None
class SAJeSolarMeterSensor(SensorEntity):
"""Collecting data and return sensor entity."""
def __init__(self, description: SensorEntityDescription, data, sensors):
"""Initialize the sensor."""
self.entity_description = description
self._data = data
self._state = None
self.sensors = sensors
self._type = self.entity_description.key
self._attr_icon = self.entity_description.icon
self._attr_name = SENSOR_PREFIX + self.entity_description.name
self._attr_state_class = self.entity_description.state_class
self._attr_native_unit_of_measurement = self.entity_description.native_unit_of_measurement
self._attr_device_class = self.entity_description.device_class
self._attr_unique_id = f"{SENSOR_PREFIX}_{self._type}"
self._discovery = False
self._dev_id = {}
@property
def state(self):
"""Return the state of the sensor. (total/current power consumption/production or total gas used)"""
return self._state
async def async_update(self):
"""Get the latest data and use it to update our sensor state."""
await self._data.async_update()
energy = self._data.latest_data
if energy:
if self._type == 'devOnlineNum':
if 'devOnlineNum' in energy['plantDetail']:
if energy['plantDetail']["devOnlineNum"] is not None:
self._state = int(energy['plantDetail']["devOnlineNum"])
if self._type == 'nowPower':
if 'nowPower' in energy['plantDetail']:
if energy['plantDetail']["nowPower"] is not None:
self._state = float(energy['plantDetail']["nowPower"])
if self._type == 'runningState':
if 'runningState' in energy['plantDetail']:
if energy['plantDetail']["runningState"] is not None:
self._state = int(energy['plantDetail']["runningState"])
if self._type == 'todayElectricity':
if 'todayElectricity' in energy['plantDetail']:
if energy['plantDetail']["todayElectricity"] is not None:
self._state = float(energy['plantDetail']["todayElectricity"])
if self._type == 'monthElectricity':
if 'monthElectricity' in energy['plantDetail']:
if energy['plantDetail']["monthElectricity"] is not None:
self._state = float(energy['plantDetail']["monthElectricity"])
if self._type == 'yearElectricity':
if 'yearElectricity' in energy['plantDetail']:
if energy['plantDetail']["yearElectricity"] is not None:
self._state = float(energy['plantDetail']["yearElectricity"])
if self._type == 'totalElectricity':
if 'totalElectricity' in energy['plantDetail']:
if energy['plantDetail']["totalElectricity"] is not None:
self._state = float(energy['plantDetail']["totalElectricity"])
if self._type == 'todayGridIncome':
if 'todayGridIncome' in energy['plantDetail']:
if energy['plantDetail']["todayGridIncome"] is not None:
self._state = float(energy['plantDetail']["todayGridIncome"])
if self._type == 'income':
if 'income' in energy['plantDetail']:
if energy['plantDetail']["income"] is not None:
self._state = float(energy['plantDetail']["income"])
if self._type == 'selfUseRate':
if 'selfUseRate' in energy['plantDetail']:
if energy['plantDetail']["selfUseRate"] is not None:
self._state = energy['plantDetail']["selfUseRate"]
if self._type == 'totalBuyElec':
if 'totalBuyElec' in energy['plantDetail']:
if energy['plantDetail']["totalBuyElec"] is not None:
self._state = float(energy['plantDetail']["totalBuyElec"])
if self._type == 'totalConsumpElec':
if 'totalConsumpElec' in energy['plantDetail']:
if energy['plantDetail']["totalConsumpElec"] is not None:
self._state = float(energy['plantDetail']["totalConsumpElec"])
if self._type == 'totalSellElec':
if 'totalSellElec' in energy['plantDetail']:
if energy['plantDetail']["totalSellElec"] is not None:
self._state = float(energy['plantDetail']["totalSellElec"])
if self._type == 'todayAlarmNum':
if 'todayAlarmNum' in energy['plantDetail']:
if energy['plantDetail']["todayAlarmNum"] is not None:
self._state = (energy['plantDetail']["todayAlarmNum"])
if self._type == 'todayAlarmNum':
if 'todayAlarmNum' in energy['plantDetail']:
if energy['plantDetail']["todayAlarmNum"] is not None:
self._state = (energy['plantDetail']["todayAlarmNum"])
if self._type == 'lastUploadTime':
if 'lastUploadTime' in energy['plantDetail']:
if energy['plantDetail']["lastUploadTime"] is not None:
self._state = (energy['plantDetail']["lastUploadTime"])
if self._type == 'totalPlantTreeNum':
if 'totalPlantTreeNum' in energy['plantDetail']:
if energy['plantDetail']["totalPlantTreeNum"] is not None:
self._state = (energy['plantDetail']["totalPlantTreeNum"])
if self._type == 'totalReduceCo2':
if 'totalReduceCo2' in energy['plantDetail']:
if energy['plantDetail']["totalReduceCo2"] is not None:
self._state = (energy['plantDetail']["totalReduceCo2"])
if self._type == 'todayAlarmNum':
if 'todayAlarmNum' in energy['plantDetail']:
if energy['plantDetail']["todayAlarmNum"] is not None:
self._state = (energy['plantDetail']["todayAlarmNum"])
if self._type == 'currency':
if 'currency' in energy['plantList'][0]:
if energy['plantList'][0]["currency"] is not None:
self._state = (energy['plantList'][0]["currency"])
if self._type == 'plantuid':
if 'plantuid' in energy['plantList'][0]:
if energy['plantList'][0]["plantuid"] is not None:
self._state = (energy['plantList'][0]["plantuid"])
if self._type == 'plantname':
if 'plantname' in energy['plantList'][0]:
if energy['plantList'][0]["plantname"] is not None:
self._state = (energy['plantList'][0]["plantname"])
if self._type == 'currency':
if 'currency' in energy['plantList'][0]:
if energy['plantList'][0]["currency"] is not None:
self._state = (energy['plantList'][0]["currency"])
if self._type == 'isOnline':
if 'isOnline' in energy['plantList'][0]:
if energy['plantList'][0]["isOnline"] is not None:
self._state = (energy['plantList'][0]["isOnline"])
if self._type == 'address':
if 'address' in energy['plantList'][0]:
if energy['plantList'][0]["address"] is not None:
self._state = (energy['plantList'][0]["address"])
if self._type == 'peakPower':
if 'peakPower' in energy:
if energy["peakPower"] is not None:
self._state = float(energy["peakPower"])
if self._type == 'status':
if 'status' in energy:
if energy["status"] is not None:
self._state = (energy["status"])
########################################################################## SAJ h1
if self.sensors == "h1":
if self._type == 'chargeElec':
if 'chargeElec' in energy['viewBean']:
if energy['viewBean']["chargeElec"] is not None:
self._state = float(energy['viewBean']["chargeElec"])
if self._type == 'dischargeElec':
if 'dischargeElec' in energy['viewBean']:
if energy['viewBean']["dischargeElec"] is not None:
self._state = float(energy['viewBean']["dischargeElec"])
if self._type == 'buyElec':
if 'pvElec' in energy['viewBean']:
if energy['viewBean']["buyElec"] is not None:
self._state = float(energy['viewBean']["buyElec"])
if self._type == 'buyRate':
if 'buyRate' in energy['viewBean']:
if energy['viewBean']["buyRate"] is not None:
self._state = energy['viewBean']["buyRate"]
if self._type == 'pvElec':
if 'pvElec' in energy['viewBean']:
if energy['viewBean']["pvElec"] is not None:
self._state = float(energy['viewBean']["pvElec"])
if self._type == 'selfConsumedEnergy1':
if 'selfConsumedEnergy1' in energy['viewBean']:
if energy['viewBean']["selfConsumedEnergy1"] is not None:
self._state = float(energy['viewBean']["selfConsumedEnergy1"])
if self._type == 'selfConsumedEnergy2':
if 'selfConsumedEnergy2' in energy['viewBean']:
if energy['viewBean']["selfConsumedEnergy2"] is not None:
self._state = float(energy['viewBean']["selfConsumedEnergy2"])
if self._type == 'selfConsumedRate1':
if 'selfConsumedRate1' in energy['viewBean']:
if energy['viewBean']["selfConsumedRate1"] is not None:
self._state = energy['viewBean']["selfConsumedRate1"]
if self._type == 'selfConsumedRate2':
if 'selfConsumedRate2' in energy['viewBean']:
if energy['viewBean']["selfConsumedRate2"] is not None:
self._state = energy['viewBean']["selfConsumedRate2"]
if self._type == 'sellElec':
if 'sellElec' in energy['viewBean']:
if energy['viewBean']["sellElec"] is not None:
self._state = float(energy['viewBean']["sellElec"])
if self._type == 'sellRate':
if 'sellRate' in energy['viewBean']:
if energy['viewBean']["sellRate"] is not None:
self._state = energy['viewBean']["sellRate"]
if self._type == 'useElec':
if 'useElec' in energy['viewBean']:
if energy['viewBean']["useElec"] is not None:
self._state = float(energy['viewBean']["useElec"])
# storeDevicePower
if self._type == 'batCapcity':
if 'batCapcity' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['batCapcity'] is not None:
self._state = float(energy["storeDevicePower"]["batCapcity"])
if self._type == 'isAlarm':
if 'isAlarm' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['isAlarm'] is not None:
self._state = int(energy["storeDevicePower"]["isAlarm"])
if self._type == 'batCurr':
if 'batCurr' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['batCurr'] is not None:
self._state = float(energy["storeDevicePower"]["batCurr"])
if self._type == 'batEnergyPercent':
if 'batEnergyPercent' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['batEnergyPercent'] is not None:
self._state = float(energy["storeDevicePower"]["batEnergyPercent"])
if self._type == 'batteryDirection':
if 'batteryDirection' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['batteryDirection'] is not None:
if energy["storeDevicePower"]["batteryDirection"] == 0:
self._state = "Standby"
elif energy["storeDevicePower"]["batteryDirection"] == 1:
self._state = "Descarga"
elif energy["storeDevicePower"]["batteryDirection"] == -1:
self._state = "Carga"
else:
self._state = f'Unknown: {energy["storeDevicePower"]["batteryDirection"]}'
if self._type == 'batteryPower':
if 'batteryPower' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['batteryPower'] is not None:
self._state = float(energy["storeDevicePower"]["batteryPower"])
if self._type == 'gridDirection':
if 'gridDirection' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['gridDirection'] is not None:
self._state = float(energy["storeDevicePower"]["gridDirection"])
if self._type == 'gridPower':
if 'gridPower' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['gridPower'] is not None:
self._state = float(energy["storeDevicePower"]["gridPower"])
if self._type == 'h1Online':
if 'isOnline' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['isOnline'] is not None:
self._state = int(energy["storeDevicePower"]["isOnline"])
if self._type == 'outPower':
if 'outPower' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['outPower'] is not None:
self._state = float(energy["storeDevicePower"]["outPower"])
if self._type == 'outPutDirection':
if 'outPutDirection' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['outPutDirection'] is not None:
self._state = float(energy["storeDevicePower"]["outPutDirection"])
if self._type == 'pvDirection':
if 'pvDirection' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['pvDirection'] is not None:
self._state = int(energy["storeDevicePower"]["pvDirection"])
if self._type == 'pvPower':
if 'pvPower' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['pvPower'] is not None:
self._state = float(energy["storeDevicePower"]["pvPower"])
if self._type == 'solarPower':
if 'solarPower' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['solarPower'] is not None:
self._state = float(energy["storeDevicePower"]["solarPower"])
if self._type == 'totalLoadPower':
if 'totalLoadPower' in energy["storeDevicePower"]:
if energy["storeDevicePower"]['totalLoadPower'] is not None:
self._state = float(energy["storeDevicePower"]['totalLoadPower'])
########################################################################## CUSTOM SENSORS H1 S2
if self._type == 'h1s2_pv_volt_1':
if 'h1s2_pv_volt_1' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_volt_1'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_volt_1"])
if self._type == 'h1s2_pv_current_1':
if 'h1s2_pv_current_1' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_current_1'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_current_1"])
if self._type == 'h1s2_pv_power_1':
if 'h1s2_pv_power_1' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_power_1'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_power_1"])
if self._type == 'h1s2_pv_volt_2':
if 'h1s2_pv_volt_2' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_volt_2'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_volt_2"])
if self._type == 'h1s2_pv_current_2':
if 'h1s2_pv_current_2' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_current_2'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_current_2"])
if self._type == 'h1s2_pv_power_2':
if 'h1s2_pv_power_2' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_power_2'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_power_2"])
if self._type == 'h1s2_pv_volt_3':
if 'h1s2_pv_volt_3' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_volt_3'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_volt_3"])
if self._type == 'h1s2_pv_current_3':
if 'h1s2_pv_current_3' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_current_3'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_current_3"])
if self._type == 'h1s2_pv_power_3':
if 'h1s2_pv_power_3' in energy["h1s2"]:
if energy["h1s2"]['h1s2_pv_power_3'] is not None:
self._state = float(energy["h1s2"]["h1s2_pv_power_3"])
if self._type == 'h1s2_bat_volt':
if 'h1s2_bat_volt' in energy["h1s2"]:
if energy["h1s2"]['h1s2_bat_volt'] is not None:
self._state = float(energy["h1s2"]["h1s2_bat_volt"])
if self._type == 'h1s2_bat_current':
if 'h1s2_bat_current' in energy["h1s2"]:
if energy["h1s2"]['h1s2_bat_current'] is not None:
self._state = float(energy["h1s2"]["h1s2_bat_current"])
if self._type == 'h1s2_bat_power':
if 'h1s2_bat_power' in energy["h1s2"]:
if energy["h1s2"]['h1s2_bat_power'] is not None:
self._state = float(energy["h1s2"]["h1s2_bat_power"])
if self._type == 'h1s2_bat_total_charge':
if 'h1s2_bat_total_charge' in energy["h1s2"]:
if energy["h1s2"]['h1s2_bat_total_charge'] is not None:
self._state = float(energy["h1s2"]["h1s2_bat_total_charge"])
if self._type == 'h1s2_bat_total_discharge':
if 'h1s2_bat_total_discharge' in energy["h1s2"]:
if energy["h1s2"]['h1s2_bat_total_discharge'] is not None:
self._state = float(energy["h1s2"]["h1s2_bat_total_discharge"])
if self._type == 'h1s2_load_power':
if 'h1s2_load_power' in energy["h1s2"]:
if energy["h1s2"]['h1s2_load_power'] is not None:
self._state = float(energy["h1s2"]["h1s2_load_power"])
if self._type == 'h1s2_grid_volt_1':
if 'h1s2_grid_volt_1' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_volt_1'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_volt_1"])
if self._type == 'h1s2_grid_current_1':
if 'h1s2_grid_current_1' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_current_1'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_current_1"])
if self._type == 'h1s2_grid_power_1':
if 'h1s2_grid_power_1' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_power_1'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_power_1"])
if self._type == 'h1s2_grid_volt_2':
if 'h1s2_grid_volt_2' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_volt_2'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_volt_2"])
if self._type == 'h1s2_grid_current_2':
if 'h1s2_grid_current_2' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_current_2'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_current_2"])
if self._type == 'h1s2_grid_power_2':
if 'h1s2_grid_power_2' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_power_2'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_power_2"])
if self._type == 'h1s2_grid_volt_3':
if 'h1s2_grid_volt_3' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_volt_3'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_volt_3"])
if self._type == 'h1s2_grid_current_3':
if 'h1s2_grid_current_3' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_current_3'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_current_3"])
if self._type == 'h1s2_grid_power_3':
if 'h1s2_grid_power_3' in energy["h1s2"]:
if energy["h1s2"]['h1s2_grid_power_3'] is not None:
self._state = float(energy["h1s2"]["h1s2_grid_power_3"])
########################################################################## Sec module Sensors:
if self.sensors == "saj_sec":
# getPlantMeterChartData - viewBeam
if self._type == 'pvElec':
if 'pvElec' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["pvElec"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["pvElec"])
if self._type == 'useElec':
if 'useElec' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["useElec"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["useElec"])
if self._type == 'buyElec':
if 'buyElec' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["buyElec"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["buyElec"])
if self._type == 'sellElec':
if 'sellElec' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["sellElec"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["sellElec"])
if self._type == 'selfConsumedEnergy1':
if 'selfConsumedEnergy1' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["selfConsumedEnergy1"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["selfConsumedEnergy1"])
if self._type == 'selfConsumedEnergy2':
if 'selfConsumedEnergy2' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["selfConsumedEnergy2"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["selfConsumedEnergy2"])
if self._type == 'reduceCo2':
if 'reduceCo2' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["reduceCo2"] is not None:
self._state = float(energy["getPlantMeterChartData"]['viewBean']["reduceCo2"])
if self._type == 'buyRate':
if 'buyRate' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["buyRate"] is not None:
self._state = (energy["getPlantMeterChartData"]['viewBean']["buyRate"])
if self._type == 'sellRate':
if 'sellRate' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["sellRate"] is not None:
self._state = (energy["getPlantMeterChartData"]['viewBean']["sellRate"])
if self._type == 'selfConsumedRate1':
if 'selfConsumedRate1' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["selfConsumedRate1"] is not None:
self._state = (energy["getPlantMeterChartData"]['viewBean']["selfConsumedRate1"])
if self._type == 'selfConsumedRate2':
if 'selfConsumedRate2' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["selfConsumedRate2"] is not None:
self._state = (energy["getPlantMeterChartData"]['viewBean']["selfConsumedRate2"])
if self._type == 'plantTreeNum':
if 'plantTreeNum' in energy["getPlantMeterChartData"]['viewBean']:
if energy["getPlantMeterChartData"]['viewBean']["plantTreeNum"] is not None:
self._state = (energy["getPlantMeterChartData"]['viewBean']["plantTreeNum"])
# dataCountList
if self._type == 'totalGridPower':
if 'dataCountList' in energy:
if energy["getPlantMeterChartData"]['dataCountList'][4][-1] is not None:
self._state = float(energy["getPlantMeterChartData"]['dataCountList'][3][-1])
if self._type == 'totalLoadPower':
if 'dataCountList' in energy:
if energy["getPlantMeterChartData"]['dataCountList'][4][-1] is not None:
self._state = float(energy["getPlantMeterChartData"]['dataCountList'][2][-1])
if self._type == 'totalPvgenPower':
if 'dataCountList' in energy:
if energy["getPlantMeterChartData"]['dataCountList'][4][-1] is not None:
self._state = float(energy["getPlantMeterChartData"]['dataCountList'][4][-1])
# getPlantMeterDetailInfo
if self._type == 'totalPvEnergy':
if 'totalPvEnergy' in energy["getPlantMeterDetailInfo"]['plantDetail']:
if energy["getPlantMeterDetailInfo"]['plantDetail']["totalPvEnergy"] is not None:
self._state = (energy["getPlantMeterDetailInfo"]['plantDetail']["totalPvEnergy"])
if self._type == 'totalLoadEnergy':
if 'totalLoadEnergy' in energy["getPlantMeterDetailInfo"]['plantDetail']:
if energy["getPlantMeterDetailInfo"]['plantDetail']["totalLoadEnergy"] is not None:
self._state = (energy["getPlantMeterDetailInfo"]['plantDetail']["totalLoadEnergy"])
if self._type == 'totalBuyEnergy':
if 'totalBuyEnergy' in energy["getPlantMeterDetailInfo"]['plantDetail']:
if energy["getPlantMeterDetailInfo"]['plantDetail']["totalBuyEnergy"] is not None:
self._state = (energy["getPlantMeterDetailInfo"]['plantDetail']["totalBuyEnergy"])
if self._type == 'totalSellEnergy':
if 'totalSellEnergy' in energy["getPlantMeterDetailInfo"]['plantDetail']:
if energy["getPlantMeterDetailInfo"]['plantDetail']["totalSellEnergy"] is not None:
self._state = (energy["getPlantMeterDetailInfo"]['plantDetail']["totalSellEnergy"])
# -Debug- adding sensor
_LOGGER.debug(f"Device: {self._type} State: {self._state}")
from sajesolar.
@teamMOYA check if by any chance your credentials work on the greenheiss url mentioned in my first comment. you might be lucky and could use the modified version that @miguelzx posted (which I guess is the same I did)
from sajesolar.
@teamMOYA seems like solar the profit login is at https://inversor.saj-electric.com/cloud?login
my credentials work as well there. pretty sure they just reskin the system with different logos and colors. you probably can use the greenheiss url or this one.
from sajesolar.
@teamMOYA check if by any chance your credentials work on the greenheiss url mentioned in my first comment. you might be lucky and could use the modified version that @miguelzx posted (which I guess is the same I did)
Hello, yes that's correct, that's what you said, thanks for the help.
That is the file as I have it, in case it is of any help to you.
Since yesterday I spent a hellish afternoon. Restoring backups, and thinking that the problem is that I had updated home assistant
from sajesolar.
i have a couple of account to test and develop with but all of them seem to work on the original portal so i am guessing it is only the greenheiss that stopped working. If there is anyone willing to share their login details i will see if i can add a option to the configuration yaml to switch to the correct url.
from sajesolar.
Hello, I also had that problem.
My Greenheiss h1 inverter. I use an old integration 1.3.1 which I already have working.
Right now everything works perfectly for me
replacing the domain in the sensor.py
I leave a copy of my sensor.py, in case it helps you, but remember that I use 1.3.1
Thank you!!
from sajesolar.
@arnauplan @santiagozky @teamMOYA
Please be aware that the sensor.py above has more changes then only the domainname/url. !!Use at your own risk!!.
@miguelzx could you explain why you have change the H1 part and use beautiful soup to do this? Per my understanding the use of beautiful soup is not permitted by home assistant.
addition: the code used is a very old version of my custom integration. It still uses the old energy StateClass which are not supported by home assistant
from sajesolar.
The same is happening to me. My plant is gone in the "old platform".
Is your plant available in the new portal or do you have to go to the greenheiss portal ?
from sajesolar.
Is your plant available in the new portal or do you have to go to the greenheiss portal ?
- https://esaj-home.saj-electric.com/ - available
- https://inversor.saj-electric.com/ - available in "shared plants" but only general data. I can't see the detailed data of the inversor+battery
- https://fop.saj-electric.com/saj - no plant/no data
- https://inversores-style.greenheiss.com/ - no plant/no data
from sajesolar.
@teamMOYA is your kit branded as SAJ or some other white label (eg. Greenheiss)?
Mine are rebranded as "Solarprofit", but they don't have a web like Saj and Greenheiss. I'll have to wait for the new web access update.
You can still use any of the saj websites with your Solarprofit credentials.
So I guess having a way to configure our endpoint in the addon should suffice, although some websites may have different structure or calls, and that could complicate things.
from sajesolar.
@djansen1987 I could provide you my credentials if that would help
from sajesolar.
Has the easiest way been found to be able to see the sensors again? I am new to home assistant.
from sajesolar.
@Txetxo1979 so far, it seems to find out in which portal you can find your info (greenheiss, solarprofit, etc) and then modify the sensor.py file of the extension to replace the SAJ url with the one that works for you.
from sajesolar.
@djansen1987 I could provide you my credentials if that would help
That would help a lot. Could you send it to [email protected] promise to handle it with care!
from sajesolar.
@Txetxo1979 so far, it seems to find out in which portal you can find your info (greenheiss, solarprofit, etc) and then modify the sensor.py file of the extension to replace the SAJ url with the one that works for you.
My URL is https://esaj-home.saj-electric.com/login
but I don't know where I can go to change the extension in sensor.py
from sajesolar.
@Txetxo1979 so far, it seems to find out in which portal you can find your info (greenheiss, solarprofit, etc) and then modify the sensor.py file of the extension to replace the SAJ url with the one that works for you.
My URL is https://esaj-home.saj-electric.com/login but I don't know where I can go to change the extension in sensor.py
This is the new portal, the api is completely changed on that site. Implementing that is almost like starting from scratch.. don't know if i got time for this soon.
from sajesolar.
@arnauplan @santiagozky @teamMOYA
Please be aware that the sensor.py above has more changes then only the domainname/url. !!Use at your own risk!!.
@miguelzx could you explain why you have change the H1 part and use beautiful soup to do this? Per my understanding the use of beautiful soup is not permitted by home assistant.
addition: the code used is a very old version of my custom integration. It still uses the old energy StateClass which are not supported by home assistant
Hello, my language is not English, so I hope the translator works well.
The version that is theirs, which as I mentioned is 1.3.1, which has worked well for me, until that happened. I use the latest version of home assistan and with my inverter, which is a saj clone, it works perfectly.
What I did was put it in sensor.py, it was replace
https://inversor.saj-electric.com/cloud?login, by https://inversores-style.greenheiss.com/cloud/logou.
and this session = async_create_clientsession(hass,False)
What did the previous colleague say?
from sajesolar.
@Txetxo1979 so far, it seems to find out in which portal you can find your info (greenheiss, solarprofit, etc) and then modify the sensor.py file of the extension to replace the SAJ url with the one that works for you.
My URL is https://esaj-home.saj-electric.com/login but I don't know where I can go to change the extension in sensor.py
This is the new portal, the api is completely changed on that site. Implementing that is almost like starting from scratch.. don't know if i got time for this soon.
Thank you so much.
I just saw that I also have the information at https://inversor.saj-electric.com/cloud?login
What should I do then to be able to have the information in home assistant?
from sajesolar.
@miguelzx
Hola Miguel,
Yo tambien uso 1.3.1. no he actualizado a la nueva version.
el codigo tiene puesto directamente el dominio de saj en varios lugares. Tengo una version que lo actualiza con el dominio de greenheiss.
CUIDADO:tiene algunos cambios mas porque en mi caso que no tengo bateria, reportaba algunos valores en el sensor equivocado (despues de la linea 1136).
Anexo mi fichero tal cual lo tengo. Compararlo con el fichero que tienes en custom_components/saj_esolar/sensor.py
de tu instalación de Home Assistant.
mi sensor.py tal cual lo tengo. it https://gist.github.com/santiagozky/311e830cffd0b54d2fa115162c5ad73c
from sajesolar.
@Txetxo1979 if you dont mind reverting to 1.3.1. (which is what I still use), you could adjust my gist above to point to your url. just adjust these lines:
BASE_HOST = 'inversores-style.greenheiss.com'
BASE_DOMAIN = 'https://'+BASE_HOST
BASE_URL = BASE_DOMAIN+'/cloud'
caution: I did additional changes between lines 1138 and 1150 because of a mismatch in my data, probably because I dont have a battery (see #41), you could skip those changes.
from sajesolar.
@santiagozky why are you recommending people downgrading and updating a lower version? Latest version can be changed and fixed the same way 😕
from sajesolar.
@elboletaire I havent recommended anyone to downgrade. Im merely offering the solution I have for myself. Im on 1.3.1 because I had previous changes that I dont want to reapply on newer versions.
from sajesolar.
Hello, when will the esolar platform be updated so that the sensors work well?
from sajesolar.
I tried making a change but I've never done anything in python or HASS, so it's taking me quite a long.
I might keep giving it a shot, but I cannot make promises on how long it will take me. for those waiting, if you have a minimum programming experience you might be able to adjust it manually replacing all the urls in the sensor.py file.
from sajesolar.
I opened a PR for this. @djansen1987 feel free to make any adjustment or recommendation you wish. particularly the configuration part.
#67
from sajesolar.
Good evening. I have an h1 inverter and valid credentials for both the https://esaj-home.saj-electric.com/index portal and for the old https://fop.saj-electric.com/saj.
If I can help, I'd be happy to help
from sajesolar.
Hi All, thanks to the work of @santiagozky this should now working in the latest version. Please read the release notes to known what to do. Thanks all for the input and patients!
https://github.com/djansen1987/SAJeSolar/releases/tag/v1.5.1
from sajesolar.
Related Issues (20)
- uitlezen SAJ converter HOT 12
- Lettura non riuscita Aiuto! HOT 2
- more plants HOT 2
- Peakpower not showing HOT 2
- rename of resources and historical dashboard data HOT 1
- Add more states to runningState handling HOT 6
- Add uuid param to reduce calls load and allow unpaired users to use the plugin HOT 3
- AIO3 supported? HOT 1
- pvElec and useElec mulitplied by 1000? HOT 2
- HA Energy Dashboard: which sensor do I add? HOT 1
- All data lose -New platform migrate. HOT 20
- SAJ Portal Server changes HOT 3
- warnings on deprecated units HOT 5
- PV1 Power HOT 13
- Help new system HOT 5
- R6 Series Inverter HOT 1
- Incorrect Reporting of totalGridPower and totalLoadPower in SAJ eSolar Integration HOT 2
- Unknown error occurred while polling eSolar: 'plantDetail' HOT 1
- Error SAJ_SOLAR HAC HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from sajesolar.