HTTP calls for Solar Inverter

I've been trying to communicate to my inverter to get access to lots of goodies. I have the direct HTTP which will return JSON data in a browser so I thought I'd simply add the calls into a Device Driver but things aren't working as expected. I'm not experienced but blundering my way through trial and error!
So to access in a browser I first type in:-
http://m.ginlong.com/cpro/login/validateLogin.json?userName=xxxxxxxx@yahoo.co.uk&password=xxxxxxx&lan=2&domain=m.ginlong.com&userType=C
This seems to set up a session and returns a confirmed login in JSON format then I call the next in my browser and it shows LOADS of JSON information which is just what I need:-
http://m.ginlong.com/cpro/epc/plantDetail/showPlantDetailAjax.json?plantId=xxxxxx&APP_ID=xxxxxxxxxxxxxxx&APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

So I looked at how other people had done stuff and thought I'd try to just start with the bare minimum to extract the information and then I can probably work out the rest later. Below is what I have created and it seems to do the Login part without any problems but then comes up with an error:-
Something went wrong: groovyx.net.http.HttpResponseException: Not Found

I'd appreciate some guidance please, as I'm way out of my depth!!

metadata {
definition (name: "aaa solar prototype", namespace: "Ginlong API", author: "") {
capability "Refresh"
capability "Sensor"
command "getStatus"
attribute "voltage", "number"
attribute "current", "number"
}
}

def getSolarData() {
def params = [
uri: "http://m.ginlong.com/cpro/epc/plantDetail/showPlantDetailAjax.json?plantId=xxxxxx&APP_ID=xxxxxxxxxxxxxxx&APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
headers: [
'Accept': '/',
'DNT': '1',
'Cache' : 'false',
'dataType': 'json',
'Accept-Encoding': 'plain',
'Connection': 'keep-alive',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36']
]

try {
   httpGet(params) { responseGD ->
    log.info "Stage 2 done, $responseGD.data"}
  }
catch (e) {log.warn "Error: $er"}

}

def refresh() {
login()
getSolarData()}

def login() {
def params = [
uri: 'http://m.ginlong.com/cpro/login/validateLogin.json?userName=xxxxxxxx@yahoo.co.uk&password=xxxxxxx&lan=2&domain=m.ginlong.com&userType=C',
headers: [
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json, text/javascript, /; q=0.01', // */
'Accept-Encoding': 'sdch',
'DNT': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
] ]
try {
httpGet(params) { responseLI ->
logDebug "Stage1 done, $responseLI.data"
}}
catch (e) {log.warn "Error: $er "
}
}

I'd remove those headers. Unless something specific is required (which is somewhat rare), you don't need them.

The "Not Found" message in that exception print is an HTTP 404 error. You can get the full exception message to work if you structure it more like this (not sure what is assigned to er, if anything):

catch (e)
{
    log.warn "Error: ${e.message}")
}

If your first call to login() succeeds, you can get at the JSON just like you started, and the Http methods in Hubitat will usually pre-parse it into a map like the output of JsonSlurper, so no need to double-parse it.

tomw,
Many thansks for the quick response, I have done as you suggest and my code is now as follows. Nothing has really changed and nothing is reported in the e.message section, it just says Error: Not Found. I appreciate your help so far but is there anything further you can suggest, it all looks simple but I just don't get why it won't accept the second call?

metadata {
definition (name: "aaa solar prototype", namespace: "Ginlong API", author: "") {
    capability "Refresh"
    capability "Sensor"
    command    "getStatus"
    attribute  "voltage", "number"
    attribute  "current", "number"
}

}
def refresh() {
login()
getSolarData()}
def login() {
log.info "Login code section entered"
try {
httpGet(uri: 'http://m.ginlong.com/cpro/login/validateLogin.json?userName=Xxxx@yahoo.co.uk&password=Xxxx=2&domain=m.ginlong.com&userType=C') { response ->
if (response.data.result.isAccept ==1){
log.debug "LOGIN SUCCESSFUL,$response.data.result.isActived, $response.data.result.email, isAccepted must be ${response.data.result.isAccept}"}
}}
catch (e) { log.warn "Error: ${e.message}"}
}
def getSolarData() {
log.info "getSolar Data entered"
try {
httpGet(uri: 'http://m.ginlong.com/cpro/epc/plantDetail/showPlantDetailAjax.json?plantId=Xxx&APP_ID=Xxx_SECRET=Xxxxxx') { response ->
log.info "httpGet called, Stage 2 done, $response.data"
}}
catch (e) {log.warn "Error: ${e.message}"}
}

You may want to remove the email and password info from your last post, unless it is already changed to be a placeholder.

How do you pass data from login() to getSolarData()? Specifically the plantId, APP_ID, and APP_SECRET? I suspect that you should be storing those away in a globally-accessible area (perhaps device state, like this: state.plantId = response.data.<path to plantId in Json response>). It would be weird if an authorization failure showed up as a 404 instead of 401 (Unauthorized) or something, but maybe the server responds that way if invalid credentials are embedded in the URL.

When I put the information directly into the web browser there is no need to pass password etc for the second call so I did not do that in the code.
I’ve joined the login and the getsolar data into a single function now and the second call generates error:Not found. Ps thanks for the tip off on passwords etc, I’ve now changed them all.!

Let me make sure I understand- the response from the login() entrypoint (which includes password and email among other parameters) probably includes the plantId, APP_ID, and APP_SECRET. Correct?

In your new implementation, do you parse out those values from the reply and then insert them into the second API call, which originally appeared in the getSolarData() entrypoint? I'm wondering if those tokens vary per login, and you may be using ones from an old session which is no longer valid.

Let me make sure I understand- the response from the login() entrypoint (which includes password and email among other parameters) probably includes the plantId, APP_ID, and APP_SECRET. Correct?
No those fields are known to me, the solar company provided them I don’t get them from the first login response.

In your new implementation, do you parse out those values from the reply and then insert them into the second API call, which originally appeared in the getSolarData() entrypoint? I'm wondering if those tokens vary per login, and you may be using ones from an old session which is no longer valid.
No I don’t. The two entries are fairly independent. When I put the first one into Chrome I get a Json response then I can put in the second and then I get all the data from the inverter. If I don’t put the first one in then I get no response to the second so I’m guessing it creates a session that can then take subsequent calls. Does that make sense?

Yes, that makes more sense. Thanks for clarifying.

I'm guessing something is not formed correctly in the request. We'll have to track that down.

Instead of the URLs for validateLogin.json and showPlantDeviceAjax.json, replace the m.ginlong.com part prior to the parameters (up to and including the question mark) with https://postman-echo.com/get?foo1=bar1&foo2=bar2 .

If you visit those URLs in the browser and post the output here and also via your Hubitat driver and post that output here, we should be able to track down the parts that are different.

Not sure what magic this is but I’m very grateful for your tome and effort, So I put in-
httpGet(uri: 'https://postman-echo.com/get?foo1=bar1&foo2=bar2&userName=Xxxx@yahoo.co.uk&password=Xxxx&lan=2&domain=m.ginlong.com&userType=C' ) {response1 ->

And got:-

[args:[foo1:bar1, foo2:bar2, userName:xxxx@yahoo.co.uk, password:xxxx, lan:2, domain:m.ginlong.com, userType:C], headers:[x-forwarded-proto:https, x-forwarded-port:443, host:postman-echo.com, x-amzn-trace-id:Root=1-5f84caa6-2475492477b2b124538ff88f, accept:/, user-agent:Apache-HttpClient/4.5.2 (Java/1.8.0_221), accept-encoding:gzip,deflate], url:https://postman-echo.com/get?foo1=bar1&foo2=bar2&userName=Xxxxx@yahoo.co.uk&password=Xxx&lan=2&domain=m.ginlong.com&userType=C]

Then I put it In the web browser I got :-

{"args":{"foo1":"bar1","foo2":"bar2","userName":"xxxxxxx@yahoo.co.uk","password":"xxxxxx","lan":"2","domain":"m.ginlong.com","userType":"C"},"headers":{"x-forwarded-proto":"https","x-forwarded-port":"443","host":"postman-echo.com","x-amzn-trace-id":"Root=1-5f84c7e6-20114acb2ceb38b4670d47b4","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8","user-agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1","accept-language":"en-gb","accept-encoding":"gzip, deflate, br","cookie":"sails.sid=s%3AAAaVn-gj_3E3JolssSalVdAC7vZVW-wD.%2B1apUdRXCILtEMcOKkKiFtUKmn5y0m%2FVwPRi9vgXQLY"},"url":"https://postman-echo.com/get?foo1=bar1&foo2=bar2&userName=Xxxxxx@yahoo.co.uk&password=Xxxx&lan=2&domain=m.ginlong.com&userType=C"}

Hopefully that all means something to you ha ha.

Please do the same thing for the second request in both the browser and Hubitat, too.

For this one, try adding contentType : 'text/html' to the params map for the request.

Here's a good reference on this: Common Methods Object - Hubitat Documentation

We may have to tweak either or both of contentType and requestContentType

I’d love to understand what this is all about once we get to the bottom of it!
The login part yields the following from Hubitat:-

args:[foo1:bar1, foo2:bar2, ?plantId:xxx, APP_ID:xxxx, APP_SECRET:xxxx], headers:[x-forwarded-proto:https, x-forwarded-port:443, host:postman-echo.com, x-amzn-trace-id:Root=1-5f84cf87-7a9c2e4741e809ef17d393ae, accept:/, user-agent:Apache-HttpClient/4.5.2 (Java/1.8.0_221), accept-encoding:gzip,deflate], url:https://postman-echo.com/get?foo1=bar1&foo2=bar2&?plantId=Xxx&APP_ID=Xxxx&APP_SECRET=Xxxx]

And from the browser I got this:-

{"args":{"foo1":"bar1","foo2":"bar2","?plantId":". Xxx","APP_ID":"xxx","APP_SECRET":"xxx"},"headers":{"x-forwarded-proto":"https","x-forwarded-port":"443","host":"postman-echo.com","x-amzn-trace-id":"Root=1-5f84ce79-6a25a0af039dff500ce0639b","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8","user-agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1","accept-language":"en-gb","accept-encoding":"gzip, deflate, br","cookie":"sails.sid=s%3AUNzEFZlZZD7CPgoI0w50CxKh8Ofs88QR.Q4lasrzVgIBTty50mLTnm7UcfaEYd5R%2FoUyn1kTOoao"},"url":"https://postman-echo.com/get?foo1=bar1&foo2=bar2&?plantId=Xxx&APP_ID=Xxx&APP_SECRET=Xxx"}

Tom.
Did the above help? I’m stuck what to do next and would appreciate your help please.

I don't see anything really obvious. It's a long shot, but you could try specifying the request type instead of letting Hubitat try to guess the best one. I added this in a post edit that probably came after you read it.

For this one, try adding contentType : 'text/html' to the params map for the request.

Here's a good reference on this: Common Methods Object - Hubitat Documentation

We may have to tweak either or both of contentType and requestContentType

If that doesn't make any difference, maybe we can take it to PMs to troubleshoot further.