I want to use this ST DTH to HE. Could you help me?

/**

  • SmartWeather Station For Korea
  • Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  • in compliance with the License. You may obtain a copy of the License at:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.
  • Based on original DH codes by SmartThings and SeungCheol Lee(slasher)
    /
    public static String version() { return "v0.0.16.20200522" }
    /
  • 2020/05/22 >>> v0.0.16 - Explicit displayed flag
  • 2019/04/28 >>> v0.0.15 - Updarte reference table
    */

metadata {
definition (name: "SmartWeather Station For Korea", namespace: "WooBooung", author: "Booung", ocfDeviceType: "x.com.st.d.airqualitysensor") {
capability "Air Quality Sensor"
capability "Carbon Monoxide Detector" // co : clear, detected
capability "Dust Sensor" // fineDustLevel : PM 2.5 dustLevel : PM 10
capability "Illuminance Measurement"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Ultraviolet Index"
capability "Polling"
capability "Configuration"
capability "Refresh"
capability "Sensor"

	// Air Korea infos for WebCore
	attribute "airQualityStatus", "string"
	attribute "pm25_value", "number"
    attribute "pm10_value", "number"
    attribute "o3_value", "number"
	attribute "no2_value", "number"
	attribute "so2_value", "number"
    attribute "co_value", "number"
    
    // Weather Station infos
    attribute "localSunrise", "string"
	attribute "localSunset", "string"
    attribute "city", "string"
	attribute "timeZoneOffset", "string"
	attribute "weather", "string"
	attribute "wind", "number"
	attribute "weatherIcon", "string"
	attribute "forecastIcon", "string"
	attribute "feelsLike", "number"
	attribute "percentPrecip", "number"
    
    command "refresh"
    command "pollAirKorea"
    command "pollWunderground"
}

preferences {
	input "accessKey", "text", type: "password", title: "AirKorea API Key", description: "www.data.go.krμ—μ„œ apikey λ°œκΈ‰ λ°›μœΌμ„Έμš”", required: true 
	input "stationName", "text", title: "Station name", description: "μΈ‘μ²­μ†Œ 이름", required: true
    input "fakeStationName", "text", title: "Fake Station name(option)", description: "Tile에 λ³΄μ—¬μ§ˆ 이름 μž…λ ₯ν•˜μ„Έμš”", required: false
    input name: "refreshRateMin", title: "Update time in every hour", type: "enum", options:[0 : "0", 15 : "15", 30 : "30"], defaultValue: "15", displayDuringSetup: true
    input "coThresholdValue", "decimal", title: "CO Detect Threshold", defaultValue: 0.0, description: "λͺ‡ μ΄μƒμΌλ•Œ Detected둜 할지 μ μœΌμ„Έμš” default:0.0", required: false
    input type: "paragraph", element: "paragraph", title: "μΈ‘μ •μ†Œ 쑰회 방법", description: "λΈŒλΌμš°μ € 톡해 μ›ν•˜μ‹œλŠ” 지역을 μž…λ ₯ν•˜μ„Έμš”\n http://www.airkorea.or.kr/web/realSearch", displayDuringSetup: false
	input type: "paragraph", element: "paragraph", title: "좜처", description: "Airkorea\nλ°μ΄ν„°λŠ” μ‹€μ‹œκ°„ κ΄€μΈ‘λœ 자료이며 μΈ‘μ •μ†Œ ν˜„μ§€ μ‚¬μ •μ΄λ‚˜ λ°μ΄ν„°μ˜ μˆ˜μ‹ μƒνƒœμ— 따라 λ―Έμˆ˜μ‹ λ  수 μžˆμŠ΅λ‹ˆλ‹€.", displayDuringSetup: false
    input type: "paragraph", element: "paragraph", title: "Version", description: version(), displayDuringSetup: false
}

simulator {
	// TODO: define status and reply messages here
}

tiles {
	multiAttributeTile(name:"airQuality", type:"generic", width:6, height:4) {
        // onaldo Version
        tileAttribute("device.airQualityStatus", key: "PRIMARY_CONTROL") {
            attributeState "μ’‹μŒ", label:'${name}', icon:"http://postfiles12.naver.net/MjAxODAzMTdfMjYx/MDAxNTIxMjUzNjI3NDY3.buZqB49WFRlPSJejVL3v6grlgL6ElOMY7DyWR4ZHMwgg.A0Oc0Tv6PEvxGGf1wzaGxUX4YyJWMayLbXMoIx1Ulj4g.PNG.fuls/Good.png?type=w2", backgroundColor:"#7EC6EE"
            attributeState "보톡", label:'${name}', icon:"http://postfiles10.naver.net/MjAxODAzMTdfOTIg/MDAxNTIxMjUzODM2NjE3.uKxYFh-UKOU_8rVL11jRwEpXamq16Zh2j3tjep0_eaIg.RkHNjXtsLpTIpadPWlVcUYCRPc9q5gpK4XDCsb4_rccg.PNG.fuls/nomal.png?type=w2", backgroundColor:"6ECA8F"
            attributeState "λ‚˜μ¨", label:'${name}', icon:"http://postfiles7.naver.net/MjAxODAzMTdfMjA2/MDAxNTIxMjU0NDQyNjg1.tQqUGjj_sMgr6-5s_NI5Bs7hIE6GuAJGwMVmUiDnL-Eg.HJfx-MyfH3GIoxbBPZPNa-Jfk-oPszVXV3XPMc55rNIg.PNG.fuls/812527_fall_512x512.png?type=w2", backgroundColor:"#E5C757"
            attributeState "λ§€μš°λ‚˜μ¨", label:'${name}', icon:"http://postfiles4.naver.net/MjAxODAzMTdfMTk1/MDAxNTIxMjU0NDQyNTEy.F1no5ZbsQK4Yle3mfc3XAKMTlKVrKSS1NTpWPmY_Qzgg.oujDDUVV4nuAUfuECNpCXXfXRdTIPN-4xpigosU-jDsg.PNG.fuls/gasmask.png?type=w2", backgroundColor:"##E40000"
            attributeState "μ•Œμˆ˜μ—†μŒ", label:'${name}', icon:"https://www.shareicon.net/data/128x128/2017/01/23/874894_question_512x512.png", backgroundColor:"#C4BBB5"
        }

		tileAttribute("device.data_time", key: "SECONDARY_CONTROL") {
       		attributeState("default", label:'${currentValue}')
        }
	}
    
    valueTile("airquality_infos", "", width: 5, height: 1, decoration: "flat") {
        state "default", label:'> λŒ€κΈ° μ˜€μ—Ό 정보 <'
    }
    
    standardTile("refresh_air_value", "device.weather", decoration: "flat") {
		state "default", label: "", action: "pollAirKorea", icon:"st.secondary.refresh"
	}
    
    /*
    valueTile("airQuality_label", "", decoration: "flat") {
        state "default", label:'λŒ€κΈ°μ§ˆ 수치'
    }
    
    valueTile("airQuality_value", "device.airQuality", decoration: "flat") {
    	state "default", label:'${currentValue}', backgroundColors:[
            [value: -1, color: "#C4BBB5"],
        	[value: 0, color: "#7EC6EE"],
        	[value: 50, color: "#6ECA8F"],
        	[value: 100, color: "#E5C757"],
        	[value: 150, color: "#E40000"],
        	[value: 200, color: "#970203"]
        ]
    }
  */
  
    valueTile("pm10_label", "", decoration: "flat") {
        state "default", label:'PM10\n㎍/γŽ₯'
    }
    
    valueTile("pm10_value", "device.pm10_value", decoration: "flat") {
    	state "default", label:'${currentValue}', unit:"㎍/γŽ₯", backgroundColors:[
			[value: -1, color: "#C4BBB5"],
        	[value: 0, color: "#7EC6EE"],
        	[value: 31, color: "#6ECA8F"],
        	[value: 51, color: "#E5C757"],
        	[value: 101, color: "#E40000"],
        	[value: 200, color: "#970203"]
        ]
    }

    valueTile("pm25_label", "", decoration: "flat") {
        state "default", label:'PM2.5\n㎍/γŽ₯'
    }

	valueTile("pm25_value", "device.fineDustLevel", decoration: "flat") {
    	state "default", label:'${currentValue}', unit:"㎍/γŽ₯", backgroundColors:[
			[value: -1, color: "#C4BBB5"],
        	[value: 0, color: "#7EC6EE"],
        	[value: 16, color: "#6ECA8F"],
        	[value: 26, color: "#E5C757"],
        	[value: 51, color: "#E40000"],
        	[value: 100, color: "#970203"]
        ]
    }
    
    valueTile("pm25_grade", "device.dustLevel", decoration: "flat") {
        state "default", label:'${currentValue}'
    }
            
    valueTile("o3_label", "", decoration: "flat") {
        state "default", label:'였쑴\nppm'
    }

    valueTile("o3_value", "device.o3_value", decoration: "flat") {
        state "default", label:'${currentValue}', backgroundColors:[
			[value: -1, color: "#C4BBB5"],
        	[value: 0.001, color: "#7EC6EE"],
        	[value: 0.03, color: "#6ECA8F"],
        	[value: 0.10, color: "#E5C757"],
        	[value: 0.15, color: "#E40000"],
        	[value: 0.5, color: "#970203"]
        ]
    }
   
    valueTile("no2_label", "", decoration: "flat") {
        state "default", label:'μ΄μ‚°ν™”μ§ˆμ†Œ\nppm'
    }

    valueTile("no2_value", "device.no2_value", decoration: "flat") {
        state "default", label:'${currentValue}', backgroundColors:[
			[value: -1, color: "#C4BBB5"],
        	[value: 0, color: "#7EC6EE"],
        	[value: 0.03, color: "#6ECA8F"],
        	[value: 0.05, color: "#E5C757"],
        	[value: 0.2, color: "#E40000"],
        	[value: 0.5, color: "#970203"]
        ]
    }
    
    valueTile("so2_label", "", decoration: "flat") {
        state "default", label:'이산화황\nppm'
    }
    
    valueTile("so2_value", "device.so2_value", decoration: "flat") {
        state "default", label:'${currentValue}', backgroundColors:[
			[value: -1, color: "#C4BBB5"],
        	[value: 0, color: "#7EC6EE"],
        	[value: 0.02, color: "#6ECA8F"],
        	[value: 0.04, color: "#E5C757"],
        	[value: 0.15, color: "#E40000"],
        	[value: 0.5, color: "#970203"]
        ]
    }
           
    valueTile("co_label", "", decoration: "flat") {
        state "default", label:'μΌμ‚°ν™”νƒ„μ†Œ\nppm'
    }

    valueTile("co_value", "device.co_value", decoration: "flat") {
        state "default", label:'${currentValue}', backgroundColors:[
			[value: -1, color: "#C4BBB5"],
        	[value: 0, color: "#7EC6EE"],
        	[value: 0.2, color: "#6ECA8F"],
        	[value: 0.7, color: "#E5C757"],
        	[value: 1.5, color: "#E40000"],
        	[value: 5.0, color: "#970203"]
        ]
    }
    
    valueTile("wunderground_infos", "", width: 5, height: 1, decoration: "flat") {
        state "default", label:'> 날씨 정보 <'
    }
    
    standardTile("refresh_weather_value", "device.weather", decoration: "flat") {
		state "default", label: "", action: "pollWunderground", icon:"st.secondary.refresh"
	}
    
    valueTile("temperature_label", "", decoration: "flat") {
        state "default", label:'μ˜¨λ„'
    }

    valueTile("temperature_value", "device.temperature") {
		state "default", label:'${currentValue}Β°',
			backgroundColors:[
				[value: 31, color: "#153591"],
				[value: 44, color: "#1e9cbb"],
				[value: 59, color: "#90d2a7"],
				[value: 74, color: "#44b621"],
				[value: 84, color: "#f1d801"],
				[value: 95, color: "#d04e00"],
				[value: 96, color: "#bc2323"]
			]
	}
    
    valueTile("humidity_label", "", decoration: "flat") {
        state "default", label:'μŠ΅λ„'
    }

	valueTile("humidity_value", "device.humidity", decoration: "flat") {
		state "default", label:'${currentValue}%'
	}
    
    valueTile("weatherIcon_label", "", decoration: "flat") {
        state "default", label:'날씨Icon'
    }

	standardTile("weatherIcon", "device.weatherIcon", decoration: "flat") {
    	//날씨 μ•„μ΄μ½˜ μˆ˜μ •
        state "00", icon:"https://smartthings-twc-icons.s3.amazonaws.com/00.png", label: ""
        state "01", icon:"https://smartthings-twc-icons.s3.amazonaws.com/01.png", label: ""
        state "02", icon:"https://smartthings-twc-icons.s3.amazonaws.com/02.png", label: ""
        state "03", icon:"https://smartthings-twc-icons.s3.amazonaws.com/03.png", label: ""
        state "04", icon:"https://smartthings-twc-icons.s3.amazonaws.com/04.png", label: ""
        state "05", icon:"https://smartthings-twc-icons.s3.amazonaws.com/05.png", label: ""
        state "06", icon:"https://smartthings-twc-icons.s3.amazonaws.com/06.png", label: ""
        state "07", icon:"https://smartthings-twc-icons.s3.amazonaws.com/07.png", label: ""
        state "08", icon:"https://smartthings-twc-icons.s3.amazonaws.com/08.png", label: ""
        state "09", icon:"https://smartthings-twc-icons.s3.amazonaws.com/09.png", label: ""
        state "10", icon:"https://smartthings-twc-icons.s3.amazonaws.com/10.png", label: ""
        state "11", icon:"https://smartthings-twc-icons.s3.amazonaws.com/11.png", label: ""
        state "12", icon:"https://smartthings-twc-icons.s3.amazonaws.com/12.png", label: ""
        state "13", icon:"https://smartthings-twc-icons.s3.amazonaws.com/13.png", label: ""
        state "14", icon:"https://smartthings-twc-icons.s3.amazonaws.com/14.png", label: ""
        state "15", icon:"https://smartthings-twc-icons.s3.amazonaws.com/15.png", label: ""
        state "16", icon:"https://smartthings-twc-icons.s3.amazonaws.com/16.png", label: ""
        state "17", icon:"https://smartthings-twc-icons.s3.amazonaws.com/17.png", label: ""
        state "18", icon:"https://smartthings-twc-icons.s3.amazonaws.com/18.png", label: ""
        state "19", icon:"https://smartthings-twc-icons.s3.amazonaws.com/19.png", label: ""
        state "20", icon:"https://smartthings-twc-icons.s3.amazonaws.com/20.png", label: ""
        state "21", icon:"https://smartthings-twc-icons.s3.amazonaws.com/21.png", label: ""
        state "22", icon:"https://smartthings-twc-icons.s3.amazonaws.com/22.png", label: ""
        state "23", icon:"https://smartthings-twc-icons.s3.amazonaws.com/23.png", label: ""
        state "24", icon:"https://smartthings-twc-icons.s3.amazonaws.com/24.png", label: ""
        state "25", icon:"https://smartthings-twc-icons.s3.amazonaws.com/25.png", label: ""
        state "26", icon:"https://smartthings-twc-icons.s3.amazonaws.com/26.png", label: ""
        state "27", icon:"https://smartthings-twc-icons.s3.amazonaws.com/27.png", label: ""
        state "28", icon:"https://smartthings-twc-icons.s3.amazonaws.com/28.png", label: ""
        state "29", icon:"https://smartthings-twc-icons.s3.amazonaws.com/29.png", label: ""
        state "30", icon:"https://smartthings-twc-icons.s3.amazonaws.com/30.png", label: ""
        state "31", icon:"https://smartthings-twc-icons.s3.amazonaws.com/31.png", label: ""
        state "32", icon:"https://smartthings-twc-icons.s3.amazonaws.com/32.png", label: ""
        state "33", icon:"https://smartthings-twc-icons.s3.amazonaws.com/33.png", label: ""
        state "34", icon:"https://smartthings-twc-icons.s3.amazonaws.com/34.png", label: ""
        state "35", icon:"https://smartthings-twc-icons.s3.amazonaws.com/35.png", label: ""
        state "36", icon:"https://smartthings-twc-icons.s3.amazonaws.com/36.png", label: ""
        state "37", icon:"https://smartthings-twc-icons.s3.amazonaws.com/37.png", label: ""
        state "38", icon:"https://smartthings-twc-icons.s3.amazonaws.com/38.png", label: ""
        state "39", icon:"https://smartthings-twc-icons.s3.amazonaws.com/39.png", label: ""
        state "40", icon:"https://smartthings-twc-icons.s3.amazonaws.com/40.png", label: ""
        state "41", icon:"https://smartthings-twc-icons.s3.amazonaws.com/41.png", label: ""
        state "42", icon:"https://smartthings-twc-icons.s3.amazonaws.com/42.png", label: ""
        state "43", icon:"https://smartthings-twc-icons.s3.amazonaws.com/43.png", label: ""
        state "44", icon:"https://smartthings-twc-icons.s3.amazonaws.com/44.png", label: ""
        state "45", icon:"https://smartthings-twc-icons.s3.amazonaws.com/45.png", label: ""
        state "46", icon:"https://smartthings-twc-icons.s3.amazonaws.com/46.png", label: ""
        state "47", icon:"https://smartthings-twc-icons.s3.amazonaws.com/47.png", label: ""
        state "na", icon:"https://smartthings-twc-icons.s3.amazonaws.com/na.png", label: ""
	}
    
	valueTile("feelsLike_label", "", decoration: "flat") {
		state "default", label:'체감 μ˜¨λ„'
	}
    
    valueTile("feelsLike_value", "device.feelsLike", decoration: "flat") {
		state "default", label:'${currentValue}Β°'
	}
    
    valueTile("wind_label", "", decoration: "flat") {
		state "default", label:'λ°”λžŒμ„ΈκΈ°\nmph'
	}

	valueTile("wind_value", "device.wind", decoration: "flat") {
		state "default", label:'${currentValue}'
	}

	valueTile("weather_label", "", decoration: "flat") {
		state "default", label:'날씨'
	}

	valueTile("weather_value", "device.weather", decoration: "flat") {
		state "default", label:'${currentValue}'
	}

	valueTile("percentPrecip_label", "", decoration: "flat") {
		state "default", label:'κ°•μˆ˜ν™•μœ¨'
	}

	valueTile("percentPrecip_value", "device.percentPrecip", decoration: "flat") {
		state "default", label:'${currentValue}%'
	}

	valueTile("ultravioletIndex_label", "", decoration: "flat") {
		state "default", label:'UV\nμžμ™Έμ„ '
	}

	valueTile("ultravioletIndex_value", "device.ultravioletIndex", decoration: "flat") {
		state "default", label:'${currentValue}'
	}
	
    valueTile("illuminance_label", "", decoration: "flat") {
		state "default", label:'쑰도\nlux'
	}
    
    valueTile("illuminance_value", "device.illuminance", decoration: "flat") {
		state "default", label:'${currentValue}'
	}
    /*
    valueTile("city_label", "", decoration: "flat") {
		state "default", label:'Station'
	}

    valueTile("city_value", "device.city", decoration: "flat") {
		state "default", label:'${currentValue}'
	}*/
    
    valueTile("rise_label", "", width: 2, decoration: "flat") {
		state "default", label:'일좜'
	}

	valueTile("rise_value", "device.localSunrise", width: 2, decoration: "flat") {
		state "default", label:'${currentValue}'
	}

	valueTile("set_label", "", width: 2, decoration: "flat") {
		state "default", label:'일λͺ°'
	}

	valueTile("set_value", "device.localSunset", width: 2, decoration: "flat") {
		state "default", label:'${currentValue}'
	}
    
    valueTile("color_infos", "", width: 6, height: 1, decoration: "flat") {
        state "default", label:'λŒ€κΈ° μ˜€μ—Ό 색상 정보'
    }

    valueTile("cai_infos", "", decoration: "flat") {
        state "default", label:'λŒ€κΈ°κ³΅κΈ°\nCAI'
    }
    
    valueTile("cai_0_value", "", decoration: "flat") {
        state "default", label:'였λ₯˜', backgroundColor: "#C4BBB5"
    }
	
    valueTile("cai_1_value", "", decoration: "flat") {
        state "default", label:'μ’‹μŒ\n0~50', backgroundColor: "#7EC6EE"
    }
    
    valueTile("cai_2_value", "", decoration: "flat") {
        state "default", label:'보톡\n51~100', backgroundColor: "#6ECA8F"
    }
    
    valueTile("cai_3_value", "", decoration: "flat") {
        state "default", label:'λ‚˜μ¨\n101~250', backgroundColor: "#E5C757"
    }
    
    valueTile("cai_4_value", "", decoration: "flat") {
        state "default", label:'λ§€μš°λ‚˜μ¨\n251~', backgroundColor: "#E40000"
    }
                   
    valueTile("pm10_kor_infos", "", decoration: "flat") {
        state "default", label:'PM10'
    }
    
    valueTile("pm10_kor", "", decoration: "flat") {
        state "default", label:'ν•œκ΅­'
    }

    valueTile("pm10_kor_1_value", "", decoration: "flat") {
        state "default", label:'μ’‹μŒ\n0~30', backgroundColor: "#7EC6EE"
    }
	
    valueTile("pm10_kor_2_value", "", decoration: "flat") {
        state "default", label:'보톡\n31~80', backgroundColor: "#6ECA8F"
    }
    
    valueTile("pm10_kor_3_value", "", decoration: "flat") {
        state "default", label:'λ‚˜μ¨\n81~150', backgroundColor: "#E5C757"
    }
    
    valueTile("pm10_kor_4_value", "", decoration: "flat") {
        state "default", label:'μ•„μ£Όλ‚˜μ¨\n151~', backgroundColor: "#E40000"
    }
    
    valueTile("pm10_who_infos", "", decoration: "flat") {
        state "default", label:'PM10'
    }
    
    valueTile("pm10_who", "", decoration: "flat") {
        state "default", label:'who'
    }

    valueTile("pm10_who_1_value", "", decoration: "flat") {
        state "default", label:'μ’‹μŒ\n0~30', backgroundColor: "#7EC6EE"
    }
	
    valueTile("pm10_who_2_value", "", decoration: "flat") {
        state "default", label:'보톡\n31~50', backgroundColor: "#6ECA8F"
    }
    
    valueTile("pm10_who_3_value", "", decoration: "flat") {
        state "default", label:'λ‚˜μ¨\n51~100', backgroundColor: "#E5C757"
    }
    
    valueTile("pm10_who_4_value", "", decoration: "flat") {
        state "default", label:'μ•„μ£Όλ‚˜μ¨\n101~', backgroundColor: "#E40000"
    }
    
    valueTile("pm25_kor_infos", "", decoration: "flat") {
        state "default", label:'PM2.5'
    }
    
    valueTile("pm25_kor", "", decoration: "flat") {
        state "default", label:'ν•œκ΅­'
    }

    valueTile("pm25_kor_1_value", "", decoration: "flat") {
        state "default", label:'μ’‹μŒ\n0~15', backgroundColor: "#7EC6EE"
    }
	
    valueTile("pm25_kor_2_value", "", decoration: "flat") {
        state "default", label:'보톡\n16~35', backgroundColor: "#6ECA8F"
    }
    
    valueTile("pm25_kor_3_value", "", decoration: "flat") {
        state "default", label:'λ‚˜μ¨\n36~75', backgroundColor: "#E5C757"
    }
    
    valueTile("pm25_kor_4_value", "", decoration: "flat") {
        state "default", label:'λ§€μš°λ‚˜μ¨\n76~', backgroundColor: "#E40000"
    }
    
    valueTile("pm25_who_infos", "", decoration: "flat") {
        state "default", label:'PM2.5'
    }
    
    valueTile("pm25_who", "", decoration: "flat") {
        state "default", label:'who'
    }

    valueTile("pm25_who_1_value", "", decoration: "flat") {
        state "default", label:'μ’‹μŒ\n0~15', backgroundColor: "#7EC6EE"
    }
	
    valueTile("pm25_who_2_value", "", decoration: "flat") {
        state "default", label:'보톡\n16~25', backgroundColor: "#6ECA8F"
    }
    
    valueTile("pm25_who_3_value", "", decoration: "flat") {
        state "default", label:'λ‚˜μ¨\n26~50', backgroundColor: "#E5C757"
    }
    
    valueTile("pm25_who_4_value", "", decoration: "flat") {
        state "default", label:'λ§€μš°λ‚˜μ¨\n51~', backgroundColor: "#E40000"
    }
           
    /*
	valueTile("refresh_label", "", width: 2, decoration: "flat") {
		state "default", label:'Refresh', action: "linkStationSearch"
	} */
    
    standardTile("refresh_value", "device.weather", width: 2, decoration: "flat") {
		state "default", label: "", action: "pollWunderground", icon:"st.secondary.refresh"
	}

	main (["airQuality"])
	details(["airQuality",
    		"airquality_infos", "refresh_air_value",
            "pm10_label", "pm25_label", "o3_label", "no2_label", "so2_label", "co_label",
            "pm10_value", "pm25_value", "o3_value", "no2_value", "so2_value", "co_value",
            "wunderground_infos", "refresh_weather_value",
            "weatherIcon", "temperature_label", "humidity_label", "ultravioletIndex_label", "illuminance_label", "feelsLike_label",
            "weather_value", "temperature_value", "humidity_value", "ultravioletIndex_value", "illuminance_value", "feelsLike_value",
            "wind_label", "percentPrecip_label", "rise_label", "set_label",
            "wind_value", "percentPrecip_value", "rise_value", "set_value",
            "color_infos",
            "cai_infos", "cai_0_value", "cai_1_value", "cai_2_value", "cai_3_value", "cai_4_value", 
            "pm10_kor_infos", "pm10_kor", "pm10_kor_1_value", "pm10_kor_2_value", "pm10_kor_3_value", "pm10_kor_4_value",
            "pm10_who_infos", "pm10_who", "pm10_who_1_value", "pm10_who_2_value", "pm10_who_3_value", "pm10_who_4_value",
            "pm25_kor_infos", "pm25_kor", "pm25_kor_1_value", "pm25_kor_2_value", "pm25_kor_3_value", "pm25_kor_4_value",
            "pm25_who_infos", "pm25_who", "pm25_who_1_value", "pm25_who_2_value", "pm25_who_3_value", "pm25_who_4_value",
            ])}

}

// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
}

def installed() {
refresh()
}

def uninstalled() {
unschedule()
}

def updated() {
log.debug "updated()"
refresh()
}

def refresh() {
log.debug "refresh()"
unschedule()

def airKoreaHealthCheckInterval = 15

if ($settings != null && $settings.refreshRateMin != null) {
	airKoreaHealthCheckInterval = Integer.parseInt($settings.refreshRateMin)
}

log.debug "airKoreaHealthCheckInterval $airKoreaHealthCheckInterval"

def wunderGroundHealthCheckInterval = airKoreaHealthCheckInterval + 1
schedule("0 $airKoreaHealthCheckInterval * * * ?", pollAirKorea)
log.debug "wunderGroundHealthCheckInterval $wunderGroundHealthCheckInterval"
schedule("0 $wunderGroundHealthCheckInterval * * * ?", pollWunderground)

}

def configure() {
log.debug "Configuare()"
}

// Air Korea handle commands
def pollAirKorea() {
log.debug "pollAirKorea()"
def dthVersion = "0.0.11"
if (stationName && accessKey) {
def params = [
uri: "http://openapi.airkorea.or.kr/openapi/services/rest/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?stationName=${stationName}&dataTerm=DAILY&pageNo=1&numOfRows=1&ServiceKey=${accessKey}&ver=1.3&_returnType=json",
contentType: 'application/json'
]

    try {
    	log.debug "uri: ${params.uri}"
        
        httpGet(params) {resp ->
            resp.headers.each {
                log.debug "${it.name} : ${it.value}"
            }
            // get the contentType of the response
            log.debug "response contentType: ${resp.contentType}"
            // get the status code of the response
            log.debug "response status code: ${resp.status}"
            if (resp.status == 200) {
                // get the data from the response body
                //log.debug "response data: ${resp.data}"
          
                if( resp.data.list[0].pm10Value != "-" ) {
                    log.debug "PM10 value: ${resp.data.list[0].pm10Value}"
                    sendEvent(name: "pm10_value", value: resp.data.list[0].pm10Value as Integer, unit: "㎍/γŽ₯")
                    sendEvent(name: "dustLevel", value: resp.data.list[0].pm10Value as Integer, unit: "㎍/γŽ₯", displayed: true)
                } else {
                	sendEvent(name: "pm10_value", value: "--", unit: "㎍/γŽ₯")
                    sendEvent(name: "dustLevel", value: "--", unit: "㎍/γŽ₯")
                }
                
                if( resp.data.list[0].pm25Value != "-" ) { 
                    log.debug "PM25 value: ${resp.data.list[0].pm25Value}"
                    sendEvent(name: "pm25_value", value: resp.data.list[0].pm25Value as Integer, unit: "㎍/γŽ₯")
                    sendEvent(name: "fineDustLevel", value: resp.data.list[0].pm25Value as Integer, unit: "㎍/γŽ₯", displayed: true)
                } else {
                	sendEvent(name: "pm25_value", value: "--", unit: "㎍/γŽ₯")
                    sendEvent(name: "fineDustLevel", value: "--", unit: "㎍/γŽ₯")
                }
                
                def display_value
                if( resp.data.list[0].o3Value != "-" ) {
                	log.debug "Ozone: ${resp.data.list[0].o3Value}"
                    display_value = "\n" + resp.data.list[0].o3Value + "\n"
                    sendEvent(name: "o3_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "o3_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].no2Value != "-" ) {
                    log.debug "NO2: ${resp.data.list[0].no2Value}"
                    display_value = "\n" + resp.data.list[0].no2Value + "\n"
                    sendEvent(name: "no2_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "no2_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].so2Value != "-" ) {
                    log.debug "SO2: ${resp.data.list[0].so2Value}"
                    display_value = "\n" + resp.data.list[0].so2Value + "\n"
                    sendEvent(name: "so2_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "so2_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].coValue != "-" ) {
                    log.debug "CO: ${resp.data.list[0].coValue}"
                    display_value = "\n" + resp.data.list[0].coValue + "\n"
                    
                    def carbonMonoxide_value = "clear"
                    
                    if ((resp.data.list[0].coValue as Float) >= (coThresholdValue as Float)) {
                    	carbonMonoxide_value = "detected"
                    }
                    
                    sendEvent(name: "carbonMonoxide", value: carbonMonoxide_value, displayed: true)
                    sendEvent(name: "co_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "co_value", value: "--", unit: "ppm")
                
                def khai_text = "μ•Œμˆ˜μ—†μŒ"
                if( resp.data.list[0].khaiValue != "-" ) {
                    def khai = resp.data.list[0].khaiValue as Integer
                    log.debug "Khai value: ${khai}"
                    
                    def station_display_name = resp.data.parm.stationName
                    
                    if (fakeStationName)
                    	station_display_name = fakeStationName
                    
                    sendEvent(name:"data_time", value: " " + station_display_name + " λŒ€κΈ°μ§ˆ 수치: ${khai}\n μΈ‘μ • μ‹œκ°„: " + resp.data.list[0].dataTime + "\nVersion: " + dthVersion)
                    
              		sendEvent(name: "airQuality", value: resp.data.list[0].khaiValue as Integer, displayed: true)

                    if (khai > 250) khai_text="λ§€μš°λ‚˜μ¨"
                    else if (khai > 100) khai_text="λ‚˜μ¨"
                    else if (khai > 50) khai_text="보톡"
                    else if (khai >= 0) khai_text="μ’‹μŒ"
                    
                    sendEvent(name: "airQualityStatus", value: khai_text, unit: "")
                    
                } else {
                    def station_display_name = resp.data.parm.stationName
                    
                    if (fakeStationName)
                    	station_display_name = fakeStationName

                
                    sendEvent(name:"data_time", value: " " + station_display_name + " λŒ€κΈ°μ§ˆ 수치: μ •λ³΄μ—†μŒ\n μΈ‘μ • μ‹œκ°„: " + resp.data.list[0].dataTime)                    
                	sendEvent(name: "airQualityStatus", value: khai_text)
                }
      		}
        	else if (resp.status==429) log.debug "You have exceeded the maximum number of refreshes today"	
            else if (resp.status==500) log.debug "Internal server error"
        }
    } catch (e) {
        log.error "error: $e"
    }
}
else log.debug "Missing data from the device settings station name or access key"

}

// WunderGround weather handle commands
def pollWunderground() {
log.debug "pollAirKorea()"

// Current conditions

def obs = get()
if (obs) {
	//def weatherIcon = obs.icon_url.split("/")[-1].split("\\.")[0]

	if(getTemperatureScale() == "C") {
		send(name: "temperature", value: Math.round(obs.temperature), unit: "C", displayed: true)
		send(name: "feelsLike", value: Math.round(obs.temperatureFeelsLike as Double), unit: "C")            
	} else {
		send(name: "temperature", value: Math.round(obs.temperature), unit: "F", displayed: true)
		send(name: "feelsLike", value: Math.round(obs.temperatureFeelsLike as Double), unit: "F") 
	}
	
    send(name: "humidity", value: obs.relativeHumidity as Integer, unit: "%", displayed: true)
    send(name: "weather", value: obs.wxPhraseShort)
    send(name: "weatherIcon", value: obs.iconCode as String, displayed: false)
    send(name: "wind", value: Math.round(obs.windSpeed) as Integer, unit: "MPH")

	//loc
	def loc = getTwcLocation(zipCode).location
    
    //timezone
    def localTimeOffSet = "+" + obs.validTimeLocal.split("\\+")[1]
    
	if (localTimeOffSet != device.currentValue("timeZoneOffset")) {
        send(name: "timeZoneOffset", value: localTimeOffSet)
	}
    
    def cityValue = "${loc.city}, ${loc.adminDistrict}, ${loc.countryCode}"
	if (cityValue != device.currentValue("city")) {
        send(name: "city", value: cityValue)
	}

    send(name: "ultravioletIndex", value: Math.round(obs.uvIndex as Double))

	// Sunrise / Sunset
    def dtf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

    def sunriseDate = dtf.parse(obs.sunriseTimeLocal)
    log.info "'${obs.sunriseTimeLocal}'"

    def sunsetDate = dtf.parse(obs.sunsetTimeLocal)

    def tf = new java.text.SimpleDateFormat("h:mm a")
    tf.setTimeZone(TimeZone.getTimeZone(loc.ianaTimeZone))

    def localSunrise = "${tf.format(sunriseDate)}"
    def localSunset = "${tf.format(sunsetDate)}"
    
    send(name: "localSunrise", value: localSunrise, descriptionText: "Sunrise today is at $localSunrise")
    send(name: "localSunset", value: localSunset, descriptionText: "Sunset today at is $localSunset")

    send(name: "illuminance", value: estimateLux(obs, sunriseDate, sunsetDate), displayed: true)

	// Forecast
    def f = getTwcForecast(zipCode)
     if (f) {
        def icon = f.daypart[0].iconCode[0] ?: f.daypart[0].iconCode[1]
        def value = f.daypart[0].precipChance[0] as Integer ?: f.daypart[0].precipChance[1] as Integer
        def narrative = f.daypart[0].narrative
        send(name: "percentPrecip", value: value, unit: "%")
        send(name: "forecastIcon", value: icon, displayed: false)
    }       
	else {
		log.warn "Forecast not found"
	}
}
else {
	log.warn "No response from Weather Underground API"
}

}

// get weather data api
private get() {
getTwcConditions(zipCode)
}

private localDate(timeZone) {
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
df.setTimeZone(TimeZone.getTimeZone(timeZone))
df.format(new Date())
}

private send(map) {
log.debug "WUSTATION: event: $map"
sendEvent(map)
}

private estimateLux(obs, sunriseDate, sunsetDate) {
def lux = 0
def now = new Date().time

if(obs.dayOrNight != 'N') {
	//day
    switch(obs.iconCode) {
        case '04':
            lux = 200
            break
        case ['05', '06', '07', '08', '09', '10',
              '11', '12', '13','14', '15','17','18','19','20',
              '21','22','23','24','25','26']:
            lux = 1000
            break
        case ['27', '28']:
            lux = 2500
            break
        case ['29', '30']:
            lux = 7500
            break
        default:
            //sunny, clear
            lux = 10000
	}

	//adjust for dusk/dawn
	def afterSunrise = now - sunriseDate.time
	def beforeSunset = sunsetDate.time - now
	def oneHour = 1000 * 60 * 60

	if(afterSunrise < oneHour) {
		//dawn
		lux = (long)(lux * (afterSunrise/oneHour))
	} else if (beforeSunset < oneHour) {
		//dusk
		lux = (long)(lux * (beforeSunset/oneHour))
	}
} else {
	//night - always set to 10 for now
	//could do calculations for dusk/dawn too
	lux = 10
}

lux

}

This will save, but unsure of how functional:

Driver Source Code

/**

SmartWeather Station For Korea
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at:
 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
for the specific language governing permissions and limitations under the License.
Based on original DH codes by SmartThings and SeungCheol Lee(slasher)
/
public static String version() { return "v0.0.16.20200522" }
/
2020/05/22 >>> v0.0.16 - Explicit displayed flag
2019/04/28 >>> v0.0.15 - Updarte reference table
*/
metadata {
definition (name: "SmartWeather Station For Korea", namespace: "WooBooung", author: "Booung", ocfDeviceType: "x.com.st.d.airqualitysensor") {
capability "Air Quality"// Sensor"
capability "Carbon Monoxide Detector" // co : clear, detected
//capability "Dust Sensor" // fineDustLevel : PM 2.5 dustLevel : PM 10
capability "Illuminance Measurement"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Ultraviolet Index"
capability "Polling"
capability "Configuration"
capability "Refresh"
capability "Sensor"

	// Air Korea infos for WebCore
	attribute "airQualityStatus", "string"
	attribute "pm25_value", "number"
    attribute "pm10_value", "number"
    attribute "o3_value", "number"
	attribute "no2_value", "number"
	attribute "so2_value", "number"
    attribute "co_value", "number"
    
    // Weather Station infos
    attribute "localSunrise", "string"
	attribute "localSunset", "string"
    attribute "city", "string"
	attribute "timeZoneOffset", "string"
	attribute "weather", "string"
	attribute "wind", "number"
	attribute "weatherIcon", "string"
	attribute "forecastIcon", "string"
	attribute "feelsLike", "number"
	attribute "percentPrecip", "number"
    
    command "refresh"
    command "pollAirKorea"
    command "pollWunderground"
}
}
preferences {
	input "accessKey", "text", type: "password", title: "AirKorea API Key", description: "www.data.go.krμ—μ„œ apikey λ°œκΈ‰ λ°›μœΌμ„Έμš”", required: true 
	input "stationName", "text", title: "Station name", description: "μΈ‘μ²­μ†Œ 이름", required: true
    input "fakeStationName", "text", title: "Fake Station name(option)", description: "Tile에 λ³΄μ—¬μ§ˆ 이름 μž…λ ₯ν•˜μ„Έμš”", required: false
    input name: "refreshRateMin", title: "Update time in every hour", type: "enum", options:[0 : "0", 15 : "15", 30 : "30"], defaultValue: "15", displayDuringSetup: true
    input "coThresholdValue", "decimal", title: "CO Detect Threshold", defaultValue: 0.0, description: "λͺ‡ μ΄μƒμΌλ•Œ Detected둜 할지 μ μœΌμ„Έμš” default:0.0", required: false
//    input type: "paragraph", element: "paragraph", title: "μΈ‘μ •μ†Œ 쑰회 방법", description: "λΈŒλΌμš°μ € 톡해 μ›ν•˜μ‹œλŠ” 지역을 μž…λ ₯ν•˜μ„Έμš”\n http://www.airkorea.or.kr/web/realSearch", displayDuringSetup: false
//	input type: "paragraph", element: "paragraph", title: "좜처", description: "Airkorea\nλ°μ΄ν„°λŠ” μ‹€μ‹œκ°„ κ΄€μΈ‘λœ 자료이며 μΈ‘μ •μ†Œ ν˜„μ§€ μ‚¬μ •μ΄λ‚˜ λ°μ΄ν„°μ˜ μˆ˜μ‹ μƒνƒœμ— 따라 λ―Έμˆ˜μ‹ λ  수 μžˆμŠ΅λ‹ˆλ‹€.", displayDuringSetup: false
//    input type: "paragraph", element: "paragraph", title: "Version", description: version(), displayDuringSetup: false
}

// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
}

def installed() {
refresh()
}

def uninstalled() {
unschedule()
}

def updated() {
log.debug "updated()"
refresh()
}

def refresh() {
log.debug "refresh()"
unschedule()

def airKoreaHealthCheckInterval = 15

if ($settings != null && $settings.refreshRateMin != null) {
	airKoreaHealthCheckInterval = Integer.parseInt($settings.refreshRateMin)
}

log.debug "airKoreaHealthCheckInterval $airKoreaHealthCheckInterval"

def wunderGroundHealthCheckInterval = airKoreaHealthCheckInterval + 1
schedule("0 $airKoreaHealthCheckInterval * * * ?", pollAirKorea)
log.debug "wunderGroundHealthCheckInterval $wunderGroundHealthCheckInterval"
schedule("0 $wunderGroundHealthCheckInterval * * * ?", pollWunderground)
}

def configure() {
log.debug "Configuare()"
}

// Air Korea handle commands
def pollAirKorea() {
log.debug "pollAirKorea()"
def dthVersion = "0.0.11"
if (stationName && accessKey) {
def params = [
uri: "http://openapi.airkorea.or.kr/openapi/services/rest/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?stationName=${stationName}&dataTerm=DAILY&pageNo=1&numOfRows=1&ServiceKey=${accessKey}&ver=1.3&_returnType=json",
contentType: 'application/json'
]

    try {
    	log.debug "uri: ${params.uri}"
        
        httpGet(params) {resp ->
            resp.headers.each {
                log.debug "${it.name} : ${it.value}"
            }
            // get the contentType of the response
            log.debug "response contentType: ${resp.contentType}"
            // get the status code of the response
            log.debug "response status code: ${resp.status}"
            if (resp.status == 200) {
                // get the data from the response body
                //log.debug "response data: ${resp.data}"
          
                if( resp.data.list[0].pm10Value != "-" ) {
                    log.debug "PM10 value: ${resp.data.list[0].pm10Value}"
                    sendEvent(name: "pm10_value", value: resp.data.list[0].pm10Value as Integer, unit: "㎍/γŽ₯")
                    sendEvent(name: "dustLevel", value: resp.data.list[0].pm10Value as Integer, unit: "㎍/γŽ₯", displayed: true)
                } else {
                	sendEvent(name: "pm10_value", value: "--", unit: "㎍/γŽ₯")
                    sendEvent(name: "dustLevel", value: "--", unit: "㎍/γŽ₯")
                }
                
                if( resp.data.list[0].pm25Value != "-" ) { 
                    log.debug "PM25 value: ${resp.data.list[0].pm25Value}"
                    sendEvent(name: "pm25_value", value: resp.data.list[0].pm25Value as Integer, unit: "㎍/γŽ₯")
                    sendEvent(name: "fineDustLevel", value: resp.data.list[0].pm25Value as Integer, unit: "㎍/γŽ₯", displayed: true)
                } else {
                	sendEvent(name: "pm25_value", value: "--", unit: "㎍/γŽ₯")
                    sendEvent(name: "fineDustLevel", value: "--", unit: "㎍/γŽ₯")
                }
                
                def display_value
                if( resp.data.list[0].o3Value != "-" ) {
                	log.debug "Ozone: ${resp.data.list[0].o3Value}"
                    display_value = "\n" + resp.data.list[0].o3Value + "\n"
                    sendEvent(name: "o3_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "o3_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].no2Value != "-" ) {
                    log.debug "NO2: ${resp.data.list[0].no2Value}"
                    display_value = "\n" + resp.data.list[0].no2Value + "\n"
                    sendEvent(name: "no2_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "no2_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].so2Value != "-" ) {
                    log.debug "SO2: ${resp.data.list[0].so2Value}"
                    display_value = "\n" + resp.data.list[0].so2Value + "\n"
                    sendEvent(name: "so2_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "so2_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].coValue != "-" ) {
                    log.debug "CO: ${resp.data.list[0].coValue}"
                    display_value = "\n" + resp.data.list[0].coValue + "\n"
                    
                    def carbonMonoxide_value = "clear"
                    
                    if ((resp.data.list[0].coValue as Float) >= (coThresholdValue as Float)) {
                    	carbonMonoxide_value = "detected"
                    }
                    
                    sendEvent(name: "carbonMonoxide", value: carbonMonoxide_value, displayed: true)
                    sendEvent(name: "co_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "co_value", value: "--", unit: "ppm")
                
                def khai_text = "μ•Œμˆ˜μ—†μŒ"
                if( resp.data.list[0].khaiValue != "-" ) {
                    def khai = resp.data.list[0].khaiValue as Integer
                    log.debug "Khai value: ${khai}"
                    
                    def station_display_name = resp.data.parm.stationName
                    
                    if (fakeStationName)
                    	station_display_name = fakeStationName
                    
                    sendEvent(name:"data_time", value: " " + station_display_name + " λŒ€κΈ°μ§ˆ 수치: ${khai}\n μΈ‘μ • μ‹œκ°„: " + resp.data.list[0].dataTime + "\nVersion: " + dthVersion)
                    
              		sendEvent(name: "airQuality", value: resp.data.list[0].khaiValue as Integer, displayed: true)

                    if (khai > 250) khai_text="λ§€μš°λ‚˜μ¨"
                    else if (khai > 100) khai_text="λ‚˜μ¨"
                    else if (khai > 50) khai_text="보톡"
                    else if (khai >= 0) khai_text="μ’‹μŒ"
                    
                    sendEvent(name: "airQualityStatus", value: khai_text, unit: "")
                    
                } else {
                    def station_display_name = resp.data.parm.stationName
                    
                    if (fakeStationName)
                    	station_display_name = fakeStationName

                
                    sendEvent(name:"data_time", value: " " + station_display_name + " λŒ€κΈ°μ§ˆ 수치: μ •λ³΄μ—†μŒ\n μΈ‘μ • μ‹œκ°„: " + resp.data.list[0].dataTime)                    
                	sendEvent(name: "airQualityStatus", value: khai_text)
                }
      		}
        	else if (resp.status==429) log.debug "You have exceeded the maximum number of refreshes today"	
            else if (resp.status==500) log.debug "Internal server error"
        }
    } catch (e) {
        log.error "error: $e"
    }
}
else log.debug "Missing data from the device settings station name or access key"
}

// WunderGround weather handle commands
def pollWunderground() {
log.debug "pollAirKorea()"

// Current conditions

def obs = get()
if (obs) {
	//def weatherIcon = obs.icon_url.split("/")[-1].split("\\.")[0]

	if(getTemperatureScale() == "C") {
		send(name: "temperature", value: Math.round(obs.temperature), unit: "C", displayed: true)
		send(name: "feelsLike", value: Math.round(obs.temperatureFeelsLike as Double), unit: "C")            
	} else {
		send(name: "temperature", value: Math.round(obs.temperature), unit: "F", displayed: true)
		send(name: "feelsLike", value: Math.round(obs.temperatureFeelsLike as Double), unit: "F") 
	}
	
    send(name: "humidity", value: obs.relativeHumidity as Integer, unit: "%", displayed: true)
    send(name: "weather", value: obs.wxPhraseShort)
    send(name: "weatherIcon", value: obs.iconCode as String, displayed: false)
    send(name: "wind", value: Math.round(obs.windSpeed) as Integer, unit: "MPH")

	//loc
	def loc = getTwcLocation(zipCode).location
    
    //timezone
    def localTimeOffSet = "+" + obs.validTimeLocal.split("\\+")[1]
    
	if (localTimeOffSet != device.currentValue("timeZoneOffset")) {
        send(name: "timeZoneOffset", value: localTimeOffSet)
	}
    
    def cityValue = "${loc.city}, ${loc.adminDistrict}, ${loc.countryCode}"
	if (cityValue != device.currentValue("city")) {
        send(name: "city", value: cityValue)
	}

    send(name: "ultravioletIndex", value: Math.round(obs.uvIndex as Double))

	// Sunrise / Sunset
    def dtf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

    def sunriseDate = dtf.parse(obs.sunriseTimeLocal)
    log.info "'${obs.sunriseTimeLocal}'"

    def sunsetDate = dtf.parse(obs.sunsetTimeLocal)

    def tf = new java.text.SimpleDateFormat("h:mm a")
    tf.setTimeZone(TimeZone.getTimeZone(loc.ianaTimeZone))

    def localSunrise = "${tf.format(sunriseDate)}"
    def localSunset = "${tf.format(sunsetDate)}"
    
    send(name: "localSunrise", value: localSunrise, descriptionText: "Sunrise today is at $localSunrise")
    send(name: "localSunset", value: localSunset, descriptionText: "Sunset today at is $localSunset")

    send(name: "illuminance", value: estimateLux(obs, sunriseDate, sunsetDate), displayed: true)

	// Forecast
    def f = getTwcForecast(zipCode)
     if (f) {
        def icon = f.daypart[0].iconCode[0] ?: f.daypart[0].iconCode[1]
        def value = f.daypart[0].precipChance[0] as Integer ?: f.daypart[0].precipChance[1] as Integer
        def narrative = f.daypart[0].narrative
        send(name: "percentPrecip", value: value, unit: "%")
        send(name: "forecastIcon", value: icon, displayed: false)
    }       
	else {
		log.warn "Forecast not found"
	}
}
else {
	log.warn "No response from Weather Underground API"
}
}

// get weather data api
private get() {
getTwcConditions(zipCode)
}

private localDate(timeZone) {
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
df.setTimeZone(TimeZone.getTimeZone(timeZone))
df.format(new Date())
}

private send(map) {
log.debug "WUSTATION: event: $map"
sendEvent(map)
}

private estimateLux(obs, sunriseDate, sunsetDate) {
def lux = 0
def now = new Date().time

if(obs.dayOrNight != 'N') {
	//day
    switch(obs.iconCode) {
        case '04':
            lux = 200
            break
        case ['05', '06', '07', '08', '09', '10',
              '11', '12', '13','14', '15','17','18','19','20',
              '21','22','23','24','25','26']:
            lux = 1000
            break
        case ['27', '28']:
            lux = 2500
            break
        case ['29', '30']:
            lux = 7500
            break
        default:
            //sunny, clear
            lux = 10000
	}

	//adjust for dusk/dawn
	def afterSunrise = now - sunriseDate.time
	def beforeSunset = sunsetDate.time - now
	def oneHour = 1000 * 60 * 60

	if(afterSunrise < oneHour) {
		//dawn
		lux = (long)(lux * (afterSunrise/oneHour))
	} else if (beforeSunset < oneHour) {
		//dusk
		lux = (long)(lux * (beforeSunset/oneHour))
	}
} else {
	//night - always set to 10 for now
	//could do calculations for dusk/dawn too
	lux = 10
}

lux
}
3 Likes

Thank you for your helping :slight_smile:
but this doesn't work properly. :frowning:

2023-01-22 10:12:47.134 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_WooBooung_SmartWeather_Station_For_Korea_708.getTwcConditions() is applicable for argument types: (null) values: [null] on line 309 (method pollWunderground)

023-01-22 10:12:45.870 AMerrororg.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack: No signature of method: user_driver_WooBooung_SmartWeather_Station_For_Korea_708.poll() is applicable for argument types: () values: []

2023-01-22 09:53:45.651 AMerrorerror: org.apache.http.conn.ConnectTimeoutException: Connect to openapi.airkorea.or.kr:80 [openapi.airkorea.or.kr/210.99.81.18] failed: connect timed out

Looks like it’s missing an input for zipcode. I added it, but still not sure that it will work.

Code v2

/**

SmartWeather Station For Korea
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at:
 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
for the specific language governing permissions and limitations under the License.
Based on original DH codes by SmartThings and SeungCheol Lee(slasher)
/
public static String version() { return "v0.0.16.20200522" }
/
2020/05/22 >>> v0.0.16 - Explicit displayed flag
2019/04/28 >>> v0.0.15 - Updarte reference table
*/
metadata {
definition (name: "SmartWeather Station For Korea", namespace: "WooBooung", author: "Booung", ocfDeviceType: "x.com.st.d.airqualitysensor") {
capability "Air Quality"// Sensor"
capability "Carbon Monoxide Detector" // co : clear, detected
//capability "Dust Sensor" // fineDustLevel : PM 2.5 dustLevel : PM 10
capability "Illuminance Measurement"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Ultraviolet Index"
capability "Polling"
capability "Configuration"
capability "Refresh"
capability "Sensor"

	// Air Korea infos for WebCore
	attribute "airQualityStatus", "string"
	attribute "pm25_value", "number"
    attribute "pm10_value", "number"
    attribute "o3_value", "number"
	attribute "no2_value", "number"
	attribute "so2_value", "number"
    attribute "co_value", "number"
    
    // Weather Station infos
    attribute "localSunrise", "string"
	attribute "localSunset", "string"
    attribute "city", "string"
	attribute "timeZoneOffset", "string"
	attribute "weather", "string"
	attribute "wind", "number"
	attribute "weatherIcon", "string"
	attribute "forecastIcon", "string"
	attribute "feelsLike", "number"
	attribute "percentPrecip", "number"
    
    command "refresh"
    command "pollAirKorea"
    command "pollWunderground"
}
}
preferences {
	input "accessKey", "text", type: "password", title: "AirKorea API Key", description: "www.data.go.krμ—μ„œ apikey λ°œκΈ‰ λ°›μœΌμ„Έμš”", required: true 
	input "stationName", "text", title: "Station name", description: "μΈ‘μ²­μ†Œ 이름", required: true
    input "fakeStationName", "text", title: "Fake Station name(option)", description: "Tile에 λ³΄μ—¬μ§ˆ 이름 μž…λ ₯ν•˜μ„Έμš”", required: false
    input name: "refreshRateMin", title: "Update time in every hour", type: "enum", options:[0 : "0", 15 : "15", 30 : "30"], defaultValue: "15", displayDuringSetup: true
    input "coThresholdValue", "decimal", title: "CO Detect Threshold", defaultValue: 0.0, description: "λͺ‡ μ΄μƒμΌλ•Œ Detected둜 할지 μ μœΌμ„Έμš” default:0.0", required: false
 //   input type: "text", element: "paragraph", title: "μΈ‘μ •μ†Œ 쑰회 방법", description: "λΈŒλΌμš°μ € 톡해 μ›ν•˜μ‹œλŠ” 지역을 μž…λ ₯ν•˜μ„Έμš”\n http://www.airkorea.or.kr/web/realSearch", displayDuringSetup: false
//	input type: "text", element: "paragraph", title: "좜처", description: "Airkorea\nλ°μ΄ν„°λŠ” μ‹€μ‹œκ°„ κ΄€μΈ‘λœ 자료이며 μΈ‘μ •μ†Œ ν˜„μ§€ μ‚¬μ •μ΄λ‚˜ λ°μ΄ν„°μ˜ μˆ˜μ‹ μƒνƒœμ— 따라 λ―Έμˆ˜μ‹ λ  수 μžˆμŠ΅λ‹ˆλ‹€.", displayDuringSetup: false
//    input type: "text", element: "paragraph", title: "Version", description: version(), displayDuringSetup: false
    input "zipcode", "text", title:"Zip code"
}

// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
}

def installed() {
refresh()
}

def uninstalled() {
unschedule()
}

def updated() {
log.debug "updated()"
refresh()
}

def refresh() {
log.debug "refresh()"
unschedule()

def airKoreaHealthCheckInterval = 15

if ($settings != null && $settings.refreshRateMin != null) {
	airKoreaHealthCheckInterval = Integer.parseInt($settings.refreshRateMin)
}

log.debug "airKoreaHealthCheckInterval $airKoreaHealthCheckInterval"

def wunderGroundHealthCheckInterval = airKoreaHealthCheckInterval + 1
schedule("0 $airKoreaHealthCheckInterval * * * ?", pollAirKorea)
log.debug "wunderGroundHealthCheckInterval $wunderGroundHealthCheckInterval"
schedule("0 $wunderGroundHealthCheckInterval * * * ?", pollWunderground)
}

def configure() {
log.debug "Configuare()"
}

// Air Korea handle commands
def pollAirKorea() {
log.debug "pollAirKorea()"
def dthVersion = "0.0.11"
if (stationName && accessKey) {
def params = [
uri: "http://openapi.airkorea.or.kr/openapi/services/rest/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?stationName=${stationName}&dataTerm=DAILY&pageNo=1&numOfRows=1&ServiceKey=${accessKey}&ver=1.3&_returnType=json",
contentType: 'application/json'
]

    try {
    	log.debug "uri: ${params.uri}"
        
        httpGet(params) {resp ->
            resp.headers.each {
                log.debug "${it.name} : ${it.value}"
            }
            // get the contentType of the response
            log.debug "response contentType: ${resp.contentType}"
            // get the status code of the response
            log.debug "response status code: ${resp.status}"
            if (resp.status == 200) {
                // get the data from the response body
                //log.debug "response data: ${resp.data}"
          
                if( resp.data.list[0].pm10Value != "-" ) {
                    log.debug "PM10 value: ${resp.data.list[0].pm10Value}"
                    sendEvent(name: "pm10_value", value: resp.data.list[0].pm10Value as Integer, unit: "㎍/γŽ₯")
                    sendEvent(name: "dustLevel", value: resp.data.list[0].pm10Value as Integer, unit: "㎍/γŽ₯", displayed: true)
                } else {
                	sendEvent(name: "pm10_value", value: "--", unit: "㎍/γŽ₯")
                    sendEvent(name: "dustLevel", value: "--", unit: "㎍/γŽ₯")
                }
                
                if( resp.data.list[0].pm25Value != "-" ) { 
                    log.debug "PM25 value: ${resp.data.list[0].pm25Value}"
                    sendEvent(name: "pm25_value", value: resp.data.list[0].pm25Value as Integer, unit: "㎍/γŽ₯")
                    sendEvent(name: "fineDustLevel", value: resp.data.list[0].pm25Value as Integer, unit: "㎍/γŽ₯", displayed: true)
                } else {
                	sendEvent(name: "pm25_value", value: "--", unit: "㎍/γŽ₯")
                    sendEvent(name: "fineDustLevel", value: "--", unit: "㎍/γŽ₯")
                }
                
                def display_value
                if( resp.data.list[0].o3Value != "-" ) {
                	log.debug "Ozone: ${resp.data.list[0].o3Value}"
                    display_value = "\n" + resp.data.list[0].o3Value + "\n"
                    sendEvent(name: "o3_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "o3_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].no2Value != "-" ) {
                    log.debug "NO2: ${resp.data.list[0].no2Value}"
                    display_value = "\n" + resp.data.list[0].no2Value + "\n"
                    sendEvent(name: "no2_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "no2_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].so2Value != "-" ) {
                    log.debug "SO2: ${resp.data.list[0].so2Value}"
                    display_value = "\n" + resp.data.list[0].so2Value + "\n"
                    sendEvent(name: "so2_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "so2_value", value: "--", unit: "ppm")
                
                if( resp.data.list[0].coValue != "-" ) {
                    log.debug "CO: ${resp.data.list[0].coValue}"
                    display_value = "\n" + resp.data.list[0].coValue + "\n"
                    
                    def carbonMonoxide_value = "clear"
                    
                    if ((resp.data.list[0].coValue as Float) >= (coThresholdValue as Float)) {
                    	carbonMonoxide_value = "detected"
                    }
                    
                    sendEvent(name: "carbonMonoxide", value: carbonMonoxide_value, displayed: true)
                    sendEvent(name: "co_value", value: display_value as String, unit: "ppm")
                } else
                	sendEvent(name: "co_value", value: "--", unit: "ppm")
                
                def khai_text = "μ•Œμˆ˜μ—†μŒ"
                if( resp.data.list[0].khaiValue != "-" ) {
                    def khai = resp.data.list[0].khaiValue as Integer
                    log.debug "Khai value: ${khai}"
                    
                    def station_display_name = resp.data.parm.stationName
                    
                    if (fakeStationName)
                    	station_display_name = fakeStationName
                    
                    sendEvent(name:"data_time", value: " " + station_display_name + " λŒ€κΈ°μ§ˆ 수치: ${khai}\n μΈ‘μ • μ‹œκ°„: " + resp.data.list[0].dataTime + "\nVersion: " + dthVersion)
                    
              		sendEvent(name: "airQuality", value: resp.data.list[0].khaiValue as Integer, displayed: true)

                    if (khai > 250) khai_text="λ§€μš°λ‚˜μ¨"
                    else if (khai > 100) khai_text="λ‚˜μ¨"
                    else if (khai > 50) khai_text="보톡"
                    else if (khai >= 0) khai_text="μ’‹μŒ"
                    
                    sendEvent(name: "airQualityStatus", value: khai_text, unit: "")
                    
                } else {
                    def station_display_name = resp.data.parm.stationName
                    
                    if (fakeStationName)
                    	station_display_name = fakeStationName

                
                    sendEvent(name:"data_time", value: " " + station_display_name + " λŒ€κΈ°μ§ˆ 수치: μ •λ³΄μ—†μŒ\n μΈ‘μ • μ‹œκ°„: " + resp.data.list[0].dataTime)                    
                	sendEvent(name: "airQualityStatus", value: khai_text)
                }
      		}
        	else if (resp.status==429) log.debug "You have exceeded the maximum number of refreshes today"	
            else if (resp.status==500) log.debug "Internal server error"
        }
    } catch (e) {
        log.error "error: $e"
    }
}
else log.debug "Missing data from the device settings station name or access key"
}

// WunderGround weather handle commands
def pollWunderground() {
log.debug "pollAirKorea()"

// Current conditions

def obs = get()
if (obs) {
	//def weatherIcon = obs.icon_url.split("/")[-1].split("\\.")[0]

	if(getTemperatureScale() == "C") {
		send(name: "temperature", value: Math.round(obs.temperature), unit: "C", displayed: true)
		send(name: "feelsLike", value: Math.round(obs.temperatureFeelsLike as Double), unit: "C")            
	} else {
		send(name: "temperature", value: Math.round(obs.temperature), unit: "F", displayed: true)
		send(name: "feelsLike", value: Math.round(obs.temperatureFeelsLike as Double), unit: "F") 
	}
	
    send(name: "humidity", value: obs.relativeHumidity as Integer, unit: "%", displayed: true)
    send(name: "weather", value: obs.wxPhraseShort)
    send(name: "weatherIcon", value: obs.iconCode as String, displayed: false)
    send(name: "wind", value: Math.round(obs.windSpeed) as Integer, unit: "MPH")

	//loc
	def loc = getTwcLocation(zipCode).location
    
    //timezone
    def localTimeOffSet = "+" + obs.validTimeLocal.split("\\+")[1]
    
	if (localTimeOffSet != device.currentValue("timeZoneOffset")) {
        send(name: "timeZoneOffset", value: localTimeOffSet)
	}
    
    def cityValue = "${loc.city}, ${loc.adminDistrict}, ${loc.countryCode}"
	if (cityValue != device.currentValue("city")) {
        send(name: "city", value: cityValue)
	}

    send(name: "ultravioletIndex", value: Math.round(obs.uvIndex as Double))

	// Sunrise / Sunset
    def dtf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

    def sunriseDate = dtf.parse(obs.sunriseTimeLocal)
    log.info "'${obs.sunriseTimeLocal}'"

    def sunsetDate = dtf.parse(obs.sunsetTimeLocal)

    def tf = new java.text.SimpleDateFormat("h:mm a")
    tf.setTimeZone(TimeZone.getTimeZone(loc.ianaTimeZone))

    def localSunrise = "${tf.format(sunriseDate)}"
    def localSunset = "${tf.format(sunsetDate)}"
    
    send(name: "localSunrise", value: localSunrise, descriptionText: "Sunrise today is at $localSunrise")
    send(name: "localSunset", value: localSunset, descriptionText: "Sunset today at is $localSunset")

    send(name: "illuminance", value: estimateLux(obs, sunriseDate, sunsetDate), displayed: true)

	// Forecast
    def f = getTwcForecast(zipCode)
     if (f) {
        def icon = f.daypart[0].iconCode[0] ?: f.daypart[0].iconCode[1]
        def value = f.daypart[0].precipChance[0] as Integer ?: f.daypart[0].precipChance[1] as Integer
        def narrative = f.daypart[0].narrative
        send(name: "percentPrecip", value: value, unit: "%")
        send(name: "forecastIcon", value: icon, displayed: false)
    }       
	else {
		log.warn "Forecast not found"
	}
}
else {
	log.warn "No response from Weather Underground API"
}
}

// get weather data api
private get() {
getTwcConditions(zipCode)
}

private localDate(timeZone) {
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
df.setTimeZone(TimeZone.getTimeZone(timeZone))
df.format(new Date())
}

private send(map) {
log.debug "WUSTATION: event: $map"
sendEvent(map)
}

private estimateLux(obs, sunriseDate, sunsetDate) {
def lux = 0
def now = new Date().time

if(obs.dayOrNight != 'N') {
	//day
    switch(obs.iconCode) {
        case '04':
            lux = 200
            break
        case ['05', '06', '07', '08', '09', '10',
              '11', '12', '13','14', '15','17','18','19','20',
              '21','22','23','24','25','26']:
            lux = 1000
            break
        case ['27', '28']:
            lux = 2500
            break
        case ['29', '30']:
            lux = 7500
            break
        default:
            //sunny, clear
            lux = 10000
	}

	//adjust for dusk/dawn
	def afterSunrise = now - sunriseDate.time
	def beforeSunset = sunsetDate.time - now
	def oneHour = 1000 * 60 * 60

	if(afterSunrise < oneHour) {
		//dawn
		lux = (long)(lux * (afterSunrise/oneHour))
	} else if (beforeSunset < oneHour) {
		//dusk
		lux = (long)(lux * (beforeSunset/oneHour))
	}
} else {
	//night - always set to 10 for now
	//could do calculations for dusk/dawn too
	lux = 10
}

lux
}
1 Like

I got the same error.

2023-01-22 03:25:28.179 PMerrorerror: org.apache.http.conn.ConnectTimeoutException: Connect to openapi.airkorea.or.kr:80 [openapi.airkorea.or.kr/210.99.81.18] failed: connect timed out

2023-01-22 03:25:27.971 PMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_WooBooung_SmartWeather_Station_For_Korea_708.getTwcConditions() is applicable for argument types: (null) values: [null] on line 310 (method pollWunderground)

2023-01-22 03:25:27.357 PMerrororg.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack: No signature of method: user_driver_WooBooung_SmartWeather_Station_For_Korea_708.poll() is applicable for argument types: () values: []

Actually it eliminated one of the errors, but there are still problems. What are you trying to integrate, and have you searched the forum to see if there is already a supporting driver/integration?

1 Like

@user3644 do you have a valid AirKorea API Key and Station name ?

The harcoded http://openapi.airkorea.or.kr/ does not return any data here, but it may be region restricted ( accessible only from Korea) ?

Do you know when last the same DTH worked in SmartThings?

There may be some changes in the open API recently, found this discussion :

3 Likes

@user3644 can you modify this part of the code yourself?

The correct URI may be :


    if (stationName && accessKey) {
        def params = [
    	    uri: "http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty?stationName=${stationName}&dataTerm=DAILY&pageNo=1&numOfRows=1&ServiceKey=${accessKey}&ver=1.3&returnType=json",
        	contentType: 'application/json'
    	]

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.