Closed hawkeye100 closed 2 years ago
Hi @hawkeye100,
It looks like HA cannot find the solis integration. Please (double) check the following:
manifest.json
(should show v 0.2.2 if you read it)const.py
platform2_portal.py
(version 0.0.5)sensor.py
(version 0.2.2)__init__.py
Your configuration.yaml should have a block like this, use "check configuration" in your config menu to check if it's correct:
- platform: solis
name: "Solis"
portal_domain: "m.ginlong.com"
portal_username: "your_user_name"
portal_password: "your password"
portal_plant_id: "your_plant_id"
inverter_serial: "your_inverter_serial"
sensors:
temperature:
dcinputvoltagepv1:
dcinputcurrentpv1:
acoutputvoltage3:
acoutputcurrent3:
actualpower:
energylastmonth:
energytoday:
energythismonth:
energythisyear:
energytotal:
If that all doesn't help then please provide a anonymized copy of your configuration.yaml and your custom_components layout.
Thanks for the prompt reply. Overnight I wondered about where in the process the restart should be. So I removed the info from the configuration.yaml. The config check was then OK and so I restarted from the UI. Then I added it back. This time the following error was thrown:
Invalid config for [sensor.solis]: expected int for dictionary value @ data['portal_plant_id']. Got '10F45A'. (See ?, line ?).``
The UI then prevented a restart.
The error looks like a data type issue - expecting integer when appearing to get a string (as it is in quotes) - though the data itself looks like hex and might need converting first? (I could be way off here)
Wondering if the same will apply to the inverter serial.
We are definitely closer, but any further help will be welcome. TIA
Can you confirm that on m.ginlong.com, tab "plant info" your plant ID is '10F45A'? If so then you're the first one where the plantID is not base10 numerical. I always assumed it was a number in normal digits.
So this is interesting.
Short story resolution in case it helps others:
Thanks for your help and patience in getting us this far. (as always, sometimes the conversation just helps clarify the problem and suggest possible solutions)
Thanks for your elaborate answer, very useful. I'm working on integrating the solis integration into HACS. This requires some editing of the documentation anyhow. Ideal moment to include your findings in the documentation.
Have fun with the Solis integration and Home Assistant.
I think my issue most likely arose because I had to go back and re-create a plant in m.ginlong.com (after solis migrated my plant to soliscloud.com), thus ending up with different plant ID numbers - it may be the soliscloud plant ID is the same as the original m.ginlong.com plant (but will never know)
I forgot to say that I also placed the file custom_components.json in the config folder (same level as configuration.yaml) - I don't know if that helped or not (the contents suggested it might be relevant)
Wondering if your code might also work with soliscloud.com (as that is also solarman), with an adjustment to handle hex plant IDs.
Not sure if solis are going to deprecate the m.ginlong.com site eventually; it is interesting our data is processed by both systems - guessing soliscloud is getting it from a common collection point within their ecosystem. Solis also migrated my plant unilaterally, without notice. Took me a few days to regain access.
(exporting data from soliscloud inverter page includes more headings than that from m.ginlong.com - again, guessing some of this may only be supplied by some logger/inverter combinations. I can send a comparison list if it helps, though you may already know this; I am interested in a couple of items in m.ginlong.com not on your sensor list, but need to check if they are calculated rather than sent from the PV system - will post separate question if I get stuck)
Thanks again for guiding me through this.
Hey @hawkeye100 ,
I had a look into SolisCloud. Did not dare to login as I was afraid it would start migrating and blocking me from using ginlong, but googled around what the iea behind Soliscloud is. From what I understand it is indeed the successor of the ginlong service, but they'll continue to support ginlong for 5 years to come.
That will give the community some time to figure out how to scrape the Soliscloud portal or access its backend. For now I'll wait for https://github.com/dkruyt/ginlong-scraper.
FYI: I removed custom_components.json. Was legacy, no longer needed.
Cheers
I saw that issue, yes. Scraping the soliscloud frontend can indeed also be done, but is a completely different solution. I'd prefer if someone figures out how to interact with the service backend, so that we get similar JSON as for the ginlong backend. Much easier and much more reliable. If you scrape the frontend then language, UI changes, popups etc all can cause issues. Very difficult to get robust.
Cheers, Peter
I have the soliscloud API document if that is of interest (it seems it is available to anyone who has an account) - if quicker than you asking, I can send via email (or does github have a PM feature?)
(yes - soliscloud is more complex/comprehensive than m.ginlong.com - I should imagine screen/page scraping would not be easy)
@hawkeye100 I've just got my system installed and am using soliscloud I'd love to get the API document and endpoints if you'd be willing to share
@hawkeye100 , @LucidityCrash : Oww, I missed your offer. Yes please, if you can share? You can attach to this issue (or a new one) Alternatively PM me at peter.vanHulten[at]gmx.net
I'm currently rewriting the integration to decouple the API from the actual integration. This would make it easier to also support Soliscloud.
I found the API Doc (attached) but there are 3 key bits of info missing .... the actual URL and the KeyID and KeySecret - all of which the doc says to contact support to obtain .... KeyID and KeySecret look to be used instead of username and password. SolisCloud API 1.0.8.pdf
Hi - just catching up. I wasn't sure the doc had any commercially sensitive info in it but on checking again it seems OK. Yes it needs API and key - they supplied this reasonably promptly by email when requested. (I'll find their email if that would help)
Decoupling might well be useful - I am also thinking of using a Raspberry pi (with LED matrix) to display some key stats (without HA - it is a very old Pi so quite limited in RAM and CPU but OK with some python) - wondering if it would be feasible to run the code (adapted) in some way.
Thanks for the API documentation, it looks feasible. It's my intent to migrate the ginlong portal 2API to PyPI. The same could be done for the soliscloud API and then any project, being your rPI or my HomeAssistant integration can use it.
I'm looking for help to get this done. Got some time, but implementation of the soliscloud API and debugging it will take some time.
@hawkeye100 : From your experience, could I use soliscloud and the ginlong portal 2 (m.ginlong.com) at the same time or can one only use one of the two?
I'll reopen the issue and rename it
@hawkeye100 : From your experience, could I use soliscloud and the ginlong portal 2 (m.ginlong.com) at the same time or can one only use one of the two?
I am successfully accessing both portals (as websites/platform) using different credentials for each, though pointing at the same inverter ID. Then also using the same ginlong credentials for your integration. They all appear to show the same data.
Happy to try some testing, ideally if it can be done alongside the existing working integration e.g. in a separate Lovelace dashboard perhaps rather than spinning up a separate HA as mine is running on an already stretched Win10 home host (in Virtual Box).
Hi @hawkeye100 & @LucidityCrash I just kind of finished the rewrite of my code. It's currently under test and available in https://github.com/hultenvp/solis-sensor/tree/48_Rewrite_api_code_and_introduce_mypy_supported_typing/custom_components/solis Portal code is now much better isolated and easier now to set up a different API in parallel. We could start writing a Soliscloud API, similar tot the Ginlong API in my new code and first test it in isolation before integrating it. I assume the most difficult step is getting working code that can access some part of the API. Extending that afterwards to full support will be less complicated. Proposal would be to start from commandline, just some test code that sets up the connection and reads some data.
I've made some progress with python testing app.
import hashlib
from hashlib import sha1
import hmac
import base64
from datetime import datetime
from datetime import timezone
import requests
import json
KeyId = "xxxxx"
secretKey = b'xxxxx'
VERB="POST"
now = datetime.now(timezone.utc)
Date = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
#Body = '{"userId":"xxxxx"}'
#Body = '{"id":"xxxxx"}'
#Body = '{"stationId":"xxxxxx"}'
Body='{"id":"xxxxx","sn":"xxxxx"}'
#Body='{"userName":"xxxxx","userType":0}'
Content_MD5 = base64.b64encode(hashlib.md5(Body.encode('utf-8')).digest()).decode('utf-8')
Content_Type = "application/json"
#CanonicalizedResource = "/v1/api/userStationList"
#CanonicalizedResource = "/v1/api/stationDetail"
#CanonicalizedResource = "/v1/api/inveterList"
CanonicalizedResource = "/v1/api/inveterDetail"
#CanonicalizedResource = "/v1/api/addUser"
encryptStr = (VERB + "\n"
+ Content_MD5 + "\n"
+ Content_Type + "\n"
+ Date + "\n"
+ CanonicalizedResource)
h = hmac.new(secretKey, msg=encryptStr.encode('utf-8'), digestmod=hashlib.sha1)
Sign = base64.b64encode(h.digest())
Authorization = "API " + KeyId + ":" + Sign.decode('utf-8')
requestStr = (VERB + " " + CanonicalizedResource + "\n"
+ "Content-MD5: " + Content_MD5 + "\n"
+ "Content-Type: " + Content_Type + "\n"
+ "Date: " + Date + "\n"
+ "Authorization: "+ Authorization + "\n"
+ "Body:" + Body)
header = { "Content-MD5":Content_MD5,
"Content-Type":Content_Type,
"Date":Date,
"Authorization":Authorization
}
header1 = { "Content-Type":Content_Type,
"Date":Date,
"Authorization":Authorization
}
print (requestStr)
url = 'https://www.soliscloud.com:13333'
req = url + CanonicalizedResource
x = requests.post(req, data=Body, headers=header)
print ("")
print(json.dumps(x.json(),indent=4, sort_keys=True))
In order to find your userId use the Body='{"userName":"xxxxx","userType":0}' where username is your account username and the CanonicalizedResource = "/v1/api/addUser", it wont add the user but it will return the userId.
Note: I would be very hesitant posting ANY id's here I'm not sure how well the user auth seperates data I used the wrong userId with the userStationList endpoint and it returns data (all zeros but that may just be because the userid doesn't exist)
Wow you already made quite some progress! Indeed, let's not post any id's here.
Some questions about your code. Maybe it's in the API docs, but did not yet have time to look into those.
Really interested in the output of that inveterDetails call. Perhaps I can make a start with the parser.
did a little edit ... the Body and CanonicalizedResource variables are now in the same order they need to be used in. ie if you want to use /v1/api/userStationList then the Body is {"userId":"xxxxx"}
- The ID in your body, is that the same as userID?
id and userId are not the same ... and it also depends on the endpoint. userId is used with /v1/api/userStationList and is the numerical representation of the user account. id is used with /v1/api/stationDetail and is a unique id for a station associated with your account. when id and sn is used with /v1/api/inveterDetail id is then the unique id of the inverter. (got by sending the stationId to /v1/api/inveterList)
- the canonicalizedResource refers to endpoint inveterDetails (without an 'R' ). Is that a mistake in their implementation?
Sorry not sure what you mean by this.
The output is specified in the Doc ... it is just output as JSON key value pairs. An example output from my code is (I'm sorting the Keys alphabetically ... they don't naturally come out in this order) :
{
"code": "0",
"data": {
"acOutputType": 1,
"apparentPower": 0.0,
"apparentPowerStr": "VA",
"batteryCapacitySoc": 17.0,
"batteryChargeEnergy": 0,
"batteryChargeEnergyStr": "kWh",
"batteryChargingCurrent": 25.0,
"batteryChargingCurrentStr": "A",
"batteryDischargeEnergy": 0,
"batteryDischargeEnergyStr": "kWh",
"batteryDischargeLimiting": 25.0,
"batteryDischargeLimitingStr": "A",
"batteryFailureInformation01": "0",
"batteryFailureInformation02": "0",
"batteryHealthSoh": 100.0,
"batteryMonthChargeEnergy": 0,
"batteryMonthChargeEnergyStr": "kWh",
"batteryMonthDischargeEnergy": 0,
"batteryMonthDischargeEnergyStr": "kWh",
"batteryPower": -0.073,
"batteryPowerBms": 0,
"batteryPowerBmsStr": "kW",
"batteryPowerFu": 73.0,
"batteryPowerPec": "1",
"batteryPowerStr": "kW",
"batteryPowerZheng": 0,
"batteryTodayChargeEnergy": 1.5,
"batteryTodayChargeEnergyStr": "kWh",
"batteryTodayDischargeEnergy": 2.1,
"batteryTodayDischargeEnergyStr": "kWh",
"batteryTotalChargeEnergy": 30.0,
"batteryTotalChargeEnergyStr": "kWh",
"batteryTotalDischargeEnergy": 64.0,
"batteryTotalDischargeEnergyStr": "kWh",
"batteryType": 0,
"batteryVoltage": 48.93,
"batteryVoltageStr": "V",
"batteryYearChargeEnergy": 0,
"batteryYearChargeEnergyStr": "kWh",
"batteryYearDischargeEnergy": 0,
"batteryYearDischargeEnergyStr": "kWh",
"batteryYesterdayChargeEnergy": 2.2,
"batteryYesterdayChargeEnergyStr": "kWh",
"batteryYesterdayDischargeEnergy": 2.8,
"batteryYesterdayDischargeEnergyStr": "kWh",
"bstteryCurrent": 0.4,
"bstteryCurrentStr": "A",
"bypassAcCurrent": 0.0,
"bypassAcCurrentStr": "A",
"bypassAcVoltage": 238.8,
"bypassAcVoltageStr": "V",
"bypassLoadPower": 0.017,
"bypassLoadPowerStr": "kW",
"collectorId": "xxxxx",
"collectorsn": "xxxxx",
"currentState": "0",
"dataTimestamp": "1642448261000",
"daylight": "0",
"dcBus": 0.0,
"dcBusHalf": 0.0,
"dcBusHalfStr": "V",
"dcBusStr": "V",
"dcInputtype": 1,
"dispersionRate": 0.0,
"eMonth": 36.0,
"eMonthStr": "kWh",
"eToday": 0,
"eTodayStr": "kWh",
"eTotal": 54.0,
"eTotalStr": "kWh",
"eYear": 36.0,
"eYearStr": "kWh",
"epmFailSafe": 0,
"fac": 49.97,
"facStr": "Hz",
"familyLoadPercent": 0,
"familyLoadPower": 0.796,
"familyLoadPowerPec": "1",
"familyLoadPowerStr": "kW",
"fullHour": 0.8,
"fullHourStr": "h",
"gridPurchasedEnergy": 0,
"gridPurchasedEnergyStr": "kWh",
"gridPurchasedMonthEnergy": 0,
"gridPurchasedMonthEnergyStr": "kWh",
"gridPurchasedTodayEnergy": 8.5,
"gridPurchasedTodayEnergyStr": "kWh",
"gridPurchasedTotalEnergy": 491.0,
"gridPurchasedTotalEnergyStr": "kWh",
"gridPurchasedYearEnergy": 0,
"gridPurchasedYearEnergyStr": "kWh",
"gridPurchasedYesterdayEnergy": 0.0,
"gridPurchasedYesterdayEnergyStr": "kWh",
"gridSellEnergy": 0,
"gridSellEnergyStr": "kWh",
"gridSellMonthEnergy": 0,
"gridSellMonthEnergyStr": "kWh",
"gridSellTodayEnergy": 0.0,
"gridSellTodayEnergyStr": "kWh",
"gridSellTotalEnergy": 199.0,
"gridSellTotalEnergyStr": "kWh",
"gridSellYearEnergy": 0,
"gridSellYearEnergyStr": "kWh",
"gridSellYesterdayEnergy": 0.0,
"gridSellYesterdayEnergyStr": "kWh",
"homeLoadEnergy": 0,
"homeLoadEnergyStr": "kWh",
"homeLoadTodayEnergy": 12.7,
"homeLoadTodayEnergyStr": "kWh",
"homeLoadTotalEnergy": 577.0,
"homeLoadTotalEnergyStr": "kWh",
"homeLoadYesterdayEnergy": 18.9,
"homeLoadYesterdayEnergyStr": "kWh",
"iAc1": 0.1,
"iAc1Str": "A",
"iAc2": 0.0,
"iAc2Str": "A",
"iAc3": 0.0,
"iAc3Str": "A",
"iPv1": 0,
"iPv10": 0,
"iPv10Str": "A",
"iPv11": 0,
"iPv11Str": "A",
"iPv12": 0,
"iPv12Str": "A",
"iPv13": 0,
"iPv13Str": "A",
"iPv14": 0,
"iPv14Str": "A",
"iPv15": 0,
"iPv15Str": "A",
"iPv16": 0,
"iPv16Str": "A",
"iPv17": 0,
"iPv17Str": "A",
"iPv18": 0,
"iPv18Str": "A",
"iPv19": 0,
"iPv19Str": "A",
"iPv1Str": "A",
"iPv2": 0,
"iPv20": 0,
"iPv20Str": "A",
"iPv21": 0,
"iPv21Str": "A",
"iPv22": 0,
"iPv22Str": "A",
"iPv23": 0,
"iPv23Str": "A",
"iPv24": 0,
"iPv24Str": "A",
"iPv25": 0,
"iPv25Str": "A",
"iPv26": 0,
"iPv26Str": "A",
"iPv27": 0,
"iPv27Str": "A",
"iPv28": 0,
"iPv28Str": "A",
"iPv29": 0,
"iPv29Str": "A",
"iPv2Str": "A",
"iPv3": 0,
"iPv30": 0,
"iPv30Str": "A",
"iPv31": 0,
"iPv31Str": "A",
"iPv32": 0,
"iPv32Str": "A",
"iPv3Str": "A",
"iPv4": 0,
"iPv4Str": "A",
"iPv5": 0,
"iPv5Str": "A",
"iPv6": 0,
"iPv6Str": "A",
"iPv7": 0,
"iPv7Str": "A",
"iPv8": 0,
"iPv8Str": "A",
"iPv9": 0,
"iPv9Str": "A",
"id": "xxxxx",
"insulationResistance": 0,
"inverterMeterModel": 5,
"inverterTemperature": 15.1,
"inverterTemperatureUnit": "\u2103",
"model": "F2",
"nationalStandardstr": "G98",
"oneSelf": 0.0,
"pac": 0.0,
"pacPec": "1",
"pacStr": "kW",
"pepm": 0.0,
"pepmSet": 0.0,
"pepmSetStr": "kW",
"pepmStr": "kW",
"porwerPercent": 0.0,
"pow1": 0,
"pow10": 0,
"pow10Str": "W",
"pow11": 0,
"pow11Str": "W",
"pow12": 0,
"pow12Str": "W",
"pow13": 0,
"pow13Str": "W",
"pow14": 0,
"pow14Str": "W",
"pow15": 0,
"pow15Str": "W",
"pow16": 0,
"pow16Str": "W",
"pow17": 0,
"pow17Str": "W",
"pow18": 0,
"pow18Str": "W",
"pow19": 0,
"pow19Str": "W",
"pow1Str": "W",
"pow2": 0,
"pow20": 0,
"pow20Str": "W",
"pow21": 0,
"pow21Str": "W",
"pow22": 0,
"pow22Str": "W",
"pow23": 0,
"pow23Str": "W",
"pow24": 0,
"pow24Str": "W",
"pow25": 0,
"pow25Str": "W",
"pow26": 0,
"pow26Str": "W",
"pow27": 0,
"pow27Str": "W",
"pow28": 0,
"pow28Str": "W",
"pow29": 0,
"pow29Str": "W",
"pow2Str": "W",
"pow3": 0,
"pow30": 0,
"pow30Str": "W",
"pow31": 0,
"pow31Str": "W",
"pow32": 0,
"pow32Str": "W",
"pow3Str": "W",
"pow4": 0,
"pow4Str": "W",
"pow5": 0,
"pow5Str": "W",
"pow6": 0,
"pow6Str": "W",
"pow7": 0,
"pow7Str": "W",
"pow8": 0,
"pow8Str": "W",
"pow9": 0,
"pow9Str": "W",
"power": 4.6,
"powerFactor": 1.0,
"powerPec": "1",
"powerStr": "kWp",
"productModel": "F2",
"psum": -0.829,
"psumCal": -0.829,
"psumCalPec": "1",
"psumCalStr": "kW",
"psumStr": "kW",
"reactivePower": 0.0,
"reactivePowerStr": "Var",
"rs485ComAddr": "101",
"sirRealtime": 0,
"sn": "xxxxx",
"sno": "xxxxx",
"socChargingSet": 0,
"socDischargeSet": 0,
"state": 1,
"stationId": "xxxxx",
"stationName": "xxxxx",
"storageBatteryCurrent": -1.5,
"storageBatteryCurrentStr": "A",
"storageBatteryVoltage": 49.2,
"storageBatteryVoltageStr": "V",
"tag": "YingZhen",
"temp": 95.0,
"tempName": "inverter internal operating ambient temperature",
"timeStr": "2022-01-17 19:37:41",
"timeZone": 0.0,
"uAc1": 238.6,
"uAc1Str": "V",
"uAc2": 0.0,
"uAc2Str": "V",
"uAc3": 0.0,
"uAc3Str": "V",
"uInitGnd": 0,
"uInitGndStr": "V",
"uPv1": 0,
"uPv10": 0,
"uPv10Str": "V",
"uPv11": 0,
"uPv11Str": "V",
"uPv12": 0,
"uPv12Str": "V",
"uPv13": 0,
"uPv13Str": "V",
"uPv14": 0,
"uPv14Str": "V",
"uPv15": 0,
"uPv15Str": "V",
"uPv16": 0,
"uPv16Str": "V",
"uPv17": 0,
"uPv17Str": "V",
"uPv18": 0,
"uPv18Str": "V",
"uPv19": 0,
"uPv19Str": "V",
"uPv1Str": "V",
"uPv2": 0,
"uPv20": 0,
"uPv20Str": "V",
"uPv21": 0,
"uPv21Str": "V",
"uPv22": 0,
"uPv22Str": "V",
"uPv23": 0,
"uPv23Str": "V",
"uPv24": 0,
"uPv24Str": "V",
"uPv25": 0,
"uPv25Str": "V",
"uPv26": 0,
"uPv26Str": "V",
"uPv27": 0,
"uPv27Str": "V",
"uPv28": 0,
"uPv28Str": "V",
"uPv29": 0,
"uPv29Str": "V",
"uPv2Str": "V",
"uPv3": 0,
"uPv30": 0,
"uPv30Str": "V",
"uPv31": 0,
"uPv31Str": "V",
"uPv32": 0,
"uPv32Str": "V",
"uPv3Str": "V",
"uPv4": 0,
"uPv4Str": "V",
"uPv5": 0,
"uPv5Str": "V",
"uPv6": 0,
"uPv6Str": "V",
"uPv7": 0,
"uPv7Str": "V",
"uPv8": 0,
"uPv8Str": "V",
"uPv9": 0,
"uPv9Str": "V",
"userId": "xxxxx",
"version": "xxxxx"
},
"msg": "success",
"success": true
}
I wanna know why my "gridSellTotalEnergy" = 199.0 ... I've not even had this a month and given its winter I've barely been covering the base usage and sometimes charging the battery never mind actually exporting to the grid 😁
Thanks for sharing. I'll start with the parser, hopefully later this week.
I assume you know, but to state the obvious: that gridSellTotalEnergy is not only this year but what the system returned during its lifetime.
the canonicalizedResource refers to endpoint inveterDetails (without an 'R' ). Is that a mistake in their implementation?
Sorry not sure what you mean by this.
There seems to be a typo in the endpoint name: inveterDetails instead of inve_r_terDetails. Since your test code works i guess it's a thing in the API?
Thanks for sharing. I'll start with the parser, hopefully later this week.
I assume you know, but to state the obvious: that gridSellTotalEnergy is not only this year but what the system returned during its lifetime.
yeah but its lifetime is < 1 month :)
There seems to be a typo in the endpoint name: inveterDetails instead of inve_r_terDetails. Since your test code works i guess it's a thing in the API?
doh I completly missed that 😀 ... I just copy/pasted from the PDF so that is a problem with their API
I see indeed that gridSellYearEnergy is 0. Perhaps you are not the real first owner and they forgot to do a factory reset?
I've created branch 18_soliscloud_api_support. Feel free to start contributing and creating PR's!
Hi, I'm trying to access SolisCloud API (https://www.soliscloud.com:13333), but can't find how to obtain the KeyID and KeySecret. I am a plant owner and using their web (m.ginlong... and soliscloud), but would like to have more freedom to integrate my PV system to smart home scenarios. API doc says only to contact the technical support, but I can't find the corresponding contacts. Does anyone of you have experience in contacting Ginlong staff for this matter? Would be grateful for any help with obtaining the mentioned keys.
I guess is dependes on your country ... I contacted euservice@solisinverters.com for another question they created an account for me on a support portal and from there you can create tickets ... one of the options is an API access request, though if you email for api access I guess you wouldn't need to create a ticket 😁 . look at https://www.ginlong.com/global/aftersales.html for the initial contact email address in your region
Thanks a lot! Will try to contact them.
I created the code in https://github.com/hultenvp/solis-sensor/tree/18_soliscloud_api_support
sensor:
- platform: solis
name: "My Solis Inverter"
portal_domain: "www.soliscloud.com:1333"
portal_username: "my_portal_username"
portal_key_id: "my_portal_key_id"
portal_secret: "portal secret"
portal_plant_id: plantId/stationID as string
Don't have secret and keyid myself yet, so if you could debug the code that would be great. De
I'm getting an error in the logs :
Traceback (most recent call last):
File "/config/custom_components/solis/service.py", line 128, in async_update
if await self._login():
File "/config/custom_components/solis/service.py", line 76, in _login
if await self._api.login(async_get_clientsession(self._hass)):
File "/config/custom_components/solis/soliscloud_api.py", line 185, in login
result = await self._post_data_json(canonicalized_resource, params)
File "/config/custom_components/solis/soliscloud_api.py", line 410, in _post_data_json
header: dict[str, str] = self._prepare_header(params, canonicalized_resource)
File "/config/custom_components/solis/soliscloud_api.py", line 389, in _prepare_header
hmac_obj = hmac.new(
File "/usr/local/lib/python3.9/hmac.py", line 170, in new
return HMAC(key, msg, digestmod)
File "/usr/local/lib/python3.9/hmac.py", line 53, in __init__
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
TypeError: key: expected bytes or bytearray, but got 'str'
Looks like the secret is being passed to hmac as a string not a byte array ... I'm sorry my Python skills are not that good and I'm still trying to work out your code 😁 it looks to me like self.config.secret should be a byte array but I'm not 100% on the syntax you are using.
Welp! I thought I had that one covered. I'll have a look
I've fixed the string type error.
It should work as follows: portal_secret: 'xxxxxxxxxxx' in configuration.yaml is converted in python to portal_secret = b'xxxxxxxxxxx'
There was also a mixup of secret and key_id, also fixed that.
Looking forward to the next stacktrace ;-)
https://github.com/hultenvp/solis-sensor/blob/d9dc48ec3ed35918903c36fe0d320124385f4b96/custom_components/solis/sensor.py#L142 - should be : portal_secret: bytes = bytes(config.get(CONF_SECRET), 'utf-8')
Because you are storing the body as real json you need to make json.dumps have exactly the right format else the md5 sum doesn't work right (no spaces after separators)
https://github.com/hultenvp/solis-sensor/blob/d9dc48ec3ed35918903c36fe0d320124385f4b96/custom_components/solis/soliscloud_api.py#L379 - should be : hashlib.md5(json.dumps(body,separators=(",", ":")).encode('utf-8')).digest()
https://github.com/hultenvp/solis-sensor/blob/d9dc48ec3ed35918903c36fe0d320124385f4b96/custom_components/solis/soliscloud_api.py#L418 - probably should be : resp = await self._session.post(url, data=json.dumps(params,separators=(",", ":")), headers=header) NB it is headers not header as the parameter name and I've switched to data=body as a string rather than json ... this is what my test code does ... your way is propper but I wanted to make sure it was as close to my working code as possible.
That said it still doesn't work - Headers look good, Content-MD5 matches my test code, Sign string matches against my test code but I just get a Timeout error from line 416. been shoving in debug statements left and right but I just can't see what is wrong.
Thanks for the fixes. I agree moving away from json is best for now. Let's first get this working and then we can make it nice and shiny.
I'm equally confused about the timeouts. If there's something wrong with the signatures or body then I'd expect an HTTP error. Not using aiohttp and using requests would be a temporary solution to try things out, won't work for the long term as sync calls lock up the HA main thread.
Maybe increase timeout for debugging to 30secs or so and see if the server just needs more time to respond. Other things that pop up:
If you know how, could you create a Pull Request for your fixes? I'll integrate them in the branch.
Cheers, Peter
I increased the timout to 30 seconds already :) Still the same. I also got it to output the full header and body as text which I then injected into my test code so it made exactly the same request (it is the addUser request done as your login function) and it worked fine. If you leave it too long after generating the Auth string the authorization times out and you start getting 403's. Having an error in any part (Header, body, MD5, Auth String etc) always results in some kind of json being returned almost immediately.
I'll try and get a pull request in soon.
Doh I'm an idiot ... missed a 3 on the port number for the URL !!! Fixed some more issues ... will redo the Pull request
Will check the actual data later.
Ah, that explains a lot ;-)
Hopefully you see something useful coming in.
EDIT: I mean the missing '3' explains a lot 😄
EDIT: I mean the missing '3' explains a lot 😄
yep. I tell you something words fail me about the SolisCloud/Ginlong monitoring platform. I keep getting my daily totals not being reset at midnight, which really mucks up the data, the SolisCloud not as much as it just resets to 0 at the next measurement but the m.ginlong one keeps the same daily total for the whole day unless it goes beyond what the max was. And looking at the graphs and exported xls from soliscloud my "Daily Energy from Grid" is always 0 despite the "Total Energy from Grid" continually increasing ... BUT the api is currently returning a real value.
This is what I'm seeing in HA :
Some things don't seem to be coming through that I'd expect - pSum (GRID_TOTAL_POWER) being an obvious example. Also somehow Phase 3 V and I is not being removed but Phase 2 V and I are there just unknown. Other things I've noticed ... familyLoadPower and pSum appear to be in kWh in the returned JSON and need 3 decimal places for the precision.
EDIT: I'll try and do some tweaking later this week and raise PR's as I fix things.
I'm pleasantly surprised the rest generally seems to work as intended. Thanks for your work! The configuration table still needs review, there will definitely be mistakes in the table. At some point while trying to match the grid data fields from soliscloud to m.ginlong.m I just gave up. It was just impossible to find the correlation without real measurements to do the comparison, so I just filled in some best guesses with the right units and focus on getting things running first.
On the topic of removing phases: On the ginlong site all 3 phases are always reported, but 2 are always 0.0 if only one phase is used. That's not necessarily Phase 1, in my case it is phase 3. Not sure if Soliscloud does the same. I found at least a pointer that indicates how many phases are in use, but no indication of which one, so for now I stuck to checking if all are reporting 0.0 and if so, removing them from the results.
On the topics of "unexpected behaviour": In the ginlong API I also have some code in place to correct the wrong reporting of energy today. There were still some race conditions, which I believe I've tackled now. I'll release those fixes today and merge them up to the development branch for Soliscloud we are using. The solution is generic and should also work for Soliscloud, if needed.
I also found one issue with power unit of measurement changing from W to kW if the solar panels are producing a lot. May need to introduce some unit checking and correction somehow as HA sensors expect static unit of measurement. Created a ticket for that.
Regarding the daily energy from grid I have no clue, looks like that's a bug on their side. For the energy dashboard you could also use Total energy, assuming you do not lose precision (ginlong does). The dashboard just expects an increasing value so it can calculate delta's. Periodic resets in that value are allowed.
I'm also on discord if you need help: Peter van Hulten#8923
Just an update I removed my config, and deleted all the sensors then re-added my config ... all the red arrow'd sensors no longer show up - I'd used the old style config specifying the sensors initially, and then realised that they wern't needed any more. Yay me for not reading the docs 😁
For now trying to work out why pSum (GRID_TOTAL_POWER) is not showing as a sensor when it is in the returned JSON.
Hey @LucidityCrash,
Is there something I can help with? Got some time at hand. Is pSum now the only sensor not working yet and is the rest working fine or is there more still to do?
no psum works fine now ... I think everything looks good. The monthly energy purchased and yearly energy purchased are always 0 (from the backend not a code error) ... but in the exported data from the website there is only daily and total from grid so while those exist in the API I'm not sure that they would ever be non Zero.
Personaly I've got an issue with the daily energy exported to grid being always being 0 but again that is a SolisCloud backend issue and I have a ticket open with them that they seem to be ignoring. That said for the Daily Purchased from Grid my XML shows 0 while my API returns the right value. Looks like they are having some major backend issues at the moment.
Without more people using it and getting feedback its as good as it is gonna get right now - ie it works for me :)
I'd propose to launch it as beta then. I'll attach a discussion where we can mention the known limitations.
Thanks a lot for the effort you put in, much appreciated.
Cheers
Merged to main. Let's open new issues for whatever we need to fix more.
Hi hultenvp, i have a problem with the Plant ID "XXXXXX" i have just 6 digits, its the same error like https://github.com/hultenvp/solis-sensor/issues/18#issuecomment-948517171
i had installed with HACS and the directories in the custom_components are right.
hope someone can help me
Hi @Kaya2017,
Are you using the current m.ginlong.com portal or are you using the new soliscloud.com portal? If you use the latter you should look for the stationId, not plantId.
Cheers
I just use the Soliscloud Dashboard. I have the inverter about 3 months. And on the Soliscloud Dashboard i just find my plantID. Example 11F4S6
I did search on the complete Dashboard Sites and SiteCoding to find any ID. Maybe i am blind of to much looking
Have a look at this discussion: https://github.com/hultenvp/solis-sensor/discussions/71#discussioncomment-2281489
Might be that that's the problem. If not then please open a discussion there, under beta: soliscloud. Others, who have been working on the support can then respond and help you, they do not see your comments here in this closed issue and I do not have soliscloud running myself.
I have been following this as i currently use m.g and considering moving to solis cloud (the android app i have uses solis cloud).
I checked in solis cloud and see my "solis inverter ID" is hex format. I searched and in reports i added the "plant id" to the report and it was the same so guess they renamed it so i will have the same issue as commented.
ok my system had a bit of a glitch for the last 3 days and wasn't picking anything up, but I've just restarted it and everything is working fine. I'm guessing you have the config wrong
Hi @hultenvp ,
Thank you so much for this and for helping us all out. I am really hoping to get the battery SOC into home assistant. Are you taking donations?
I think I am having the same problem with the thing I am calling my PlantID having letters. I agree that it might be hex.
"Are you using the current m.ginlong.com portal or are you using the new soliscloud.com portal? If you use the latter you should look for the stationId, not plantId."
Can you please tell me where the stationid can be found or what else it might show up as?
Thank you so much.
As far as I can tell, I have followed the instructions - files downloaded and put in the correct place on my HA install including the config in configuration.yaml. Checked via File Editor as well as Samba share. HA is 2021.10.6 on Virtualbox vdi. [Other changes on HA are OK including adding integrations from UI].
When I check the config, I get the following:
The full log shows:
I am afraid this does not mean much to me as a HA newbie (and rusty programmer).
I have created a new account in m.ginlong.com and checked it logs on OK to web interface. Those credentials are in the config. (this is because Solis moved my main account to soliscloud.com recently)
Any help (in layman's terms) would be much appreciated. Thanks