günsan elektrik – tuya destekli akıllı priz
uzaktan kontrol ve zamanlama için bir adet akıllı priz ihtiyacı oldu. bir alış veriş sitesinden kampanyadan faydalanarak günsan elektrik e ait olan prizden sipariş verdim. ürünün resmi sayfası
https://www.gunsanelectric.com/urunler/anahtar-priz/smart/wi-fi-kontrollu-akilli-priz-16a
şeklinde..
yerli ürün alalım diye düşürken içten içe üzerine günsan yazılmış fason olarak üretilmiş bir ürün alaağımı biliyordum. günsan smart life uygulamasını kendisine göre uyarlamış ama eksiklerde kalmış.. bazı yerlerde hala smartlife yazıyor 🙂
günsanın gg smart isimli uygulamasını kullanarak kısa sürede ürünü uygulamaya sorunsuz olarak ekleyebiliyorsunuz. tuya veya smart life uygulamasını kullanmak istersenizde sorun yaşamıyorsunuz.
buraya kadar sorun yok ancak sizde benim gibi uygulamalarda istediğinizi yapamıyorsanız, verilerinizi dış dünya ile paylaşmak istemiyor ve olası güvenlik açıklarının önüne geçmek istiyebilirsiniz.
bunun için kendi uygulamamızı yazabiliriz veya evinizde çalıştırdığınız bir akıllı ev asistanına ekleyebilirsiniz. ancak bunu yapabilmeniz için cihazın iletşim prokolu vb bilgileri gerekli. bu bilgiler ne yazıkkı cihaz ile doğrudan size gelmiyor.
adım adım bu bilgileri nasıl elde edeceğimize ve nasıl basit bir program yazacağımıza bakalım.
öncelikle akıllı prizinize smart life uygulamasına doğru şekilde ekleyin.
akıllı priz wifi desteğine sahip olduğu için ipsini öğrenmeliyiz. bunu ağınızda ip taraması veya modem arayüzünden rahatlıkla bulabilirsiniz. sonrasına bir port scanner ile açık olan portları bulmalıyız… bu ürün 6666, 6667, 6668 portlarını kullanıyor.
ihtiyacımız olan diğer bilgilere ulaşmanın bir kaç yöntemi var.
en temel haliyle yakadığımız pakeleri analiz ederek bu verilere ulaşabiliriz.. mitm, proy , wireshak vb ile uğraşmayı seviyersanız konuya zaten hekimsinizdir burada anlatmayacağım, siz hallederseniz. biz daha basit olan yöntemlere bakalım.
tuya geliştirici ortamı üzerinden nasıl öğreneceğimizi inceleyelim. öncelikle
adresinden kendimize bir hesap oluşturalım. hesap oluşumunda sonra
https://platform.tuya.com/cloud
adresinden bir proje oluşturalım.. proje oluştururken aşagıdaki parametreleri kullanabilirsiniz. dikkat etmeniz gereken nokta data center seçimi olacak. ileleyen aşamalarda yapacağımız bazı işlemlerde seçtiğiniz data center önem kazanıyor.. US veya Central Europe seçmenizi öneririm..

sonrasında kullanacağınız api servislerini belirlemeniz gerekiyor. Smart Home Content Manage, [Deprecate]Device Log Query , Smart Home Basic Service, [Deprecate]Smart Home Scene Linkage servislerinin seçili olması olmasına dikkat edelim.

projeyi oluşturduktan sonra aşagıdaki gibi ekran bizi karşılayacaktır.

Device altına giriyoruz

sonrasında Link App Account a seçip Add App Account butonuna tıklıyoruz. açılan penceredki qr kodu

smart life uygulamasında tanımlama kısmında taratacağız. bu işlem başarılı olarak tamamlandıktan sonra smart life uygulamasına eklediğiniz tuya destekli cihazları platformda görmeye başlayacaksınız

Device Permission ayarlarını Controllable olarak ayarlayın.. buraya kadar yaptıklarımız sayesinde tuya developer üzerinden akıllı prizlerimizi kontrol edebilir ve izleyebilir duruma getirmiş olduk.
şimdi api explorer i açalım.
https://platform.tuya.com/cloud/explorer
platformu açtıktan sonra “query device details” sorgusu altında device list altında gördüğümüz device id bilgisini girerek

sorguyu çalıştırdığımızda aşagıdaki gibi bir sonuç elde ediyoruz. product id ve model bilgisinden ürünün bir fason olduğunu görüyoruz.
{
"result": {
"active_time": 1738437687,
"bind_space_id": "223209011",
"category": "cz",
"create_time": 1738180007,
"custom_name": "tv",
"icon": "smart/icon/ay1544008322541hl9iQ/9aebc86a6392f0b9495d826cba17ba54.jpg",
"id": "xxxxxxxxxxxxxxxxxxxx",
"ip": "11.11.11.11",
"is_online": true,
"lat": "38.74",
"local_key": "xxxxxxxxxxxxxxxxxxxx",
"lon": "35.44",
"model": "EU02A-中性欧规EU02A-16A计量CB2S-BK7231N 过充保护",
"name": "Smart plug 2",
"product_id": "newhgkiotowsaryi",
"product_name": "Smart Plug",
"sub": false,
"time_zone": "+03:00",
"update_time": 1738437697,
"uuid": "xxxxxxxxxxxxxxxxxxxx"
},
"success": true,
"t": 1738528737555,
"tid": "xxxxxxxxxxxxxxxxxxxx"
}
elde ettiğimiz bu bilgilerle lokalden akıllı prize bağlanıp veri çekebiliriz.
Alacağımız veriler bize doğrudan bir şey ifade etmeyebilir. anlamlı hale getirmek için Query Things Data Model altından data modelini elde edelim.

elde edilen çıktı aşagıdaki şekildedir…
{
"result": {
"model": "{\"modelId\":\"000004yex6\",\"services\":[{\"actions\":[],\"code\":\"\",\"description\":\"\",\"events\":[],\"name\":\"默认服务\",\"properties\":[{\"abilityId\":1,\"accessMode\":\"rw\",\"code\":\"switch_1\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-dp_power2\",\"attribute\":\"641\"},\"name\":\"开关1\",\"typeSpec\":{\"type\":\"bool\"}},{\"abilityId\":9,\"accessMode\":\"rw\",\"code\":\"countdown_1\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-dp_time2\",\"attribute\":\"736\"},\"name\":\"开关1倒计时\",\"typeSpec\":{\"type\":\"value\",\"max\":86400,\"min\":0,\"scale\":0,\"step\":1,\"unit\":\"s\"}},{\"abilityId\":17,\"accessMode\":\"ro\",\"code\":\"add_ele\",\"description\":\"\",\"extensions\":{\"attribute\":\"704\"},\"name\":\"增加电量\",\"typeSpec\":{\"type\":\"value\",\"max\":50000,\"min\":0,\"scale\":3,\"step\":100}},{\"abilityId\":18,\"accessMode\":\"ro\",\"code\":\"cur_current\",\"description\":\"\",\"extensions\":{\"attribute\":\"704\"},\"name\":\"当前电流\",\"typeSpec\":{\"type\":\"value\",\"max\":30000,\"min\":0,\"scale\":0,\"step\":1,\"unit\":\"mA\"}},{\"abilityId\":19,\"accessMode\":\"ro\",\"code\":\"cur_power\",\"description\":\"\",\"extensions\":{\"attribute\":\"704\"},\"name\":\"当前功率\",\"typeSpec\":{\"type\":\"value\",\"max\":80000,\"min\":0,\"scale\":1,\"step\":1,\"unit\":\"W\"}},{\"abilityId\":20,\"accessMode\":\"ro\",\"code\":\"cur_voltage\",\"description\":\"\",\"extensions\":{\"attribute\":\"704\"},\"name\":\"当前电压\",\"typeSpec\":{\"type\":\"value\",\"max\":5000,\"min\":0,\"scale\":1,\"step\":1,\"unit\":\"V\"}},{\"abilityId\":21,\"accessMode\":\"ro\",\"code\":\"test_bit\",\"description\":\"\",\"extensions\":{\"attribute\":\"736\"},\"name\":\"产测结果位\",\"typeSpec\":{\"type\":\"value\",\"max\":5,\"min\":0,\"scale\":0,\"step\":1}},{\"abilityId\":22,\"accessMode\":\"ro\",\"code\":\"voltage_coe\",\"description\":\"\",\"extensions\":{\"attribute\":\"736\"},\"name\":\"电压校准系数\",\"typeSpec\":{\"type\":\"value\",\"max\":1000000,\"min\":0,\"scale\":0,\"step\":1}},{\"abilityId\":23,\"accessMode\":\"ro\",\"code\":\"electric_coe\",\"description\":\"\",\"extensions\":{\"attribute\":\"736\"},\"name\":\"电流校准系数\",\"typeSpec\":{\"type\":\"value\",\"max\":1000000,\"min\":0,\"scale\":0,\"step\":1}},{\"abilityId\":24,\"accessMode\":\"ro\",\"code\":\"power_coe\",\"description\":\"\",\"extensions\":{\"attribute\":\"736\"},\"name\":\"功率校准系数\",\"typeSpec\":{\"type\":\"value\",\"max\":1000000,\"min\":0,\"scale\":0,\"step\":1}},{\"abilityId\":25,\"accessMode\":\"ro\",\"code\":\"electricity_coe\",\"description\":\"\",\"extensions\":{\"attribute\":\"736\"},\"name\":\"电量校准系数\",\"typeSpec\":{\"type\":\"value\",\"max\":1000000,\"min\":0,\"scale\":0,\"step\":1}},{\"abilityId\":26,\"accessMode\":\"ro\",\"code\":\"fault\",\"description\":\"\",\"extensions\":{\"attribute\":\"640\"},\"name\":\"故障告警\",\"typeSpec\":{\"type\":\"bitmap\",\"label\":[\"ov_cr\",\"ov_vol\",\"ov_pwr\",\"ls_cr\",\"ls_vol\",\"ls_pow\"],\"maxlen\":6}},{\"abilityId\":38,\"accessMode\":\"rw\",\"code\":\"relay_status\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-zhuangtai\",\"attribute\":\"736\"},\"name\":\"上电状态设置\",\"typeSpec\":{\"type\":\"enum\",\"range\":[\"off\",\"on\",\"memory\"]}},{\"abilityId\":39,\"accessMode\":\"rw\",\"code\":\"overcharge_switch\",\"description\":\"\",\"extensions\":{\"attribute\":\"128\"},\"name\":\"过充保护\",\"typeSpec\":{\"type\":\"bool\"}},{\"abilityId\":40,\"accessMode\":\"rw\",\"code\":\"light_mode\",\"description\":\"\",\"extensions\":{\"iconName\":\"tcl_function_light\",\"attribute\":\"224\"},\"name\":\"指示灯状态设置\",\"typeSpec\":{\"type\":\"enum\",\"range\":[\"relay\",\"pos\",\"none\",\"on\"]}},{\"abilityId\":41,\"accessMode\":\"rw\",\"code\":\"child_lock\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-dp_power2\",\"attribute\":\"128\"},\"name\":\"童锁开关\",\"typeSpec\":{\"type\":\"bool\"}},{\"abilityId\":42,\"accessMode\":\"rw\",\"code\":\"cycle_time\",\"description\":\"涂鸦协议\",\"extensions\":{\"iconName\":\"icon-dp_time3\",\"attribute\":\"224\"},\"name\":\"循环定时\",\"typeSpec\":{\"type\":\"string\",\"maxlen\":255}},{\"abilityId\":43,\"accessMode\":\"rw\",\"code\":\"random_time\",\"description\":\"涂鸦协议\",\"extensions\":{\"iconName\":\"icon-dp_time2\",\"attribute\":\"224\"},\"name\":\"随机定时\",\"typeSpec\":{\"type\":\"string\",\"maxlen\":255}},{\"abilityId\":44,\"accessMode\":\"rw\",\"code\":\"switch_inching\",\"description\":\"涂鸦协议\",\"extensions\":{\"attribute\":\"224\"},\"name\":\"点动开关\",\"typeSpec\":{\"type\":\"string\",\"maxlen\":255}}]}]}"
},
"success": true,
"t": 1738532745188,
"tid": "0e8bf1f9e1af11ef9e609e9247cde4aa"
}
görüleceği üzere açıklamalar vb çince.. bunu python altında kullanmak için anlamlı hale getirelim.
# DPS tanımları
DPS_MAPPING = {
'1': {
'id': 1,
'name': 'Güç Anahtarı',
'code': 'switch_1',
'type': bool,
'access': 'rw',
'icon': 'icon-dp_power2',
'format': lambda x: 'Açık' if x else 'Kapalı'
},
'9': {
'id': 9,
'name': 'Geri Sayım',
'code': 'countdown_1',
'type': int,
'access': 'rw',
'icon': 'icon-dp_time2',
'max': 86400,
'min': 0,
'scale': 0,
'step': 1,
'unit': 'saniye',
'format': lambda x: f'{x} saniye'
},
'17': {
'id': 17,
'name': 'Toplam Enerji',
'code': 'add_ele',
'type': int,
'access': 'ro',
'max': 50000,
'min': 0,
'scale': 3,
'step': 100,
'format': lambda x: f'{x/1000:.3f} kWh'
},
'18': {
'id': 18,
'name': 'Anlık Akım',
'code': 'cur_current',
'type': int,
'access': 'ro',
'max': 30000,
'min': 0,
'unit': 'mA',
'format': lambda x: f'{x/1000:.2f} A'
},
'19': {
'id': 19,
'name': 'Anlık Güç',
'code': 'cur_power',
'type': int,
'access': 'ro',
'max': 80000,
'min': 0,
'scale': 1,
'unit': 'W',
'format': lambda x: f'{x/10:.1f} W'
},
'20': {
'id': 20,
'name': 'Anlık Voltaj',
'code': 'cur_voltage',
'type': int,
'access': 'ro',
'max': 5000,
'min': 0,
'scale': 1,
'unit': 'V',
'format': lambda x: f'{x/10:.1f} V'
},
'21': {
'id': 21,
'name': 'Test Sonucu',
'code': 'test_bit',
'type': int,
'access': 'ro',
'max': 5,
'min': 0,
'format': lambda x: f'Test Sonucu: {x}'
},
'26': {
'id': 26,
'name': 'Hata Durumu',
'code': 'fault',
'type': int,
'access': 'ro',
'labels': ['Aşırı Akım', 'Aşırı Voltaj', 'Aşırı Güç', 'Düşük Akım', 'Düşük Voltaj', 'Düşük Güç'],
'format': lambda x: decode_fault(x)
},
'38': {
'id': 38,
'name': 'Röle Durumu',
'code': 'relay_status',
'type': str,
'access': 'rw',
'icon': 'icon-zhuangtai',
'range': ['off', 'on', 'memory'],
'format': lambda x: {'off': 'Kapalı', 'on': 'Açık', 'memory': 'Hafıza'}.get(x, x)
},
'39': {
'id': 39,
'name': 'Aşırı Şarj Koruması',
'code': 'overcharge_switch',
'type': bool,
'access': 'rw',
'format': lambda x: 'Aktif' if x else 'Pasif'
},
'40': {
'id': 40,
'name': 'LED Gösterge Modu',
'code': 'light_mode',
'type': str,
'access': 'rw',
'icon': 'tcl_function_light',
'range': ['relay', 'pos', 'none', 'on'],
'format': lambda x: {'relay': 'Röle', 'pos': 'Pozisyon', 'none': 'Kapalı', 'on': 'Açık'}.get(x, x)
},
'41': {
'id': 41,
'name': 'Çocuk Kilidi',
'code': 'child_lock',
'type': bool,
'access': 'rw',
'icon': 'icon-dp_power2',
'format': lambda x: 'Kilitli' if x else 'Kilitsiz'
},
'42': {
'id': 42,
'name': 'Döngü Zamanlayıcı',
'code': 'cycle_time',
'type': str,
'access': 'rw',
'icon': 'icon-dp_time3',
'maxlen': 255,
'format': lambda x: f'Döngü: {x}'
},
'43': {
'id': 43,
'name': 'Rastgele Zamanlayıcı',
'code': 'random_time',
'type': str,
'access': 'rw',
'icon': 'icon-dp_time2',
'maxlen': 255,
'format': lambda x: f'Rastgele: {x}'
},
'44': {
'id': 44,
'name': 'Anlık Anahtar',
'code': 'switch_inching',
'type': str,
'access': 'rw',
'maxlen': 255,
'format': lambda x: f'Anlık: {x}'
}
}
def decode_fault(fault_code):
faults = []
fault_map = {
0: "Aşırı Akım",
1: "Aşırı Voltaj",
2: "Aşırı Güç",
3: "Düşük Akım",
4: "Düşük Voltaj",
5: "Düşük Güç"
}
for bit, fault in fault_map.items():
if fault_code & (1 << bit):
faults.append(fault)
return ', '.join(faults) if faults else 'Normal'
şimdi tinyyuya modulunu kullanarak akıllı prizimiden veri çekecek python kodumuzu yazalım…
import tinytuya
import time
# Cihaz bilgileri
DEVICE_ID = "xx"
IP_ADDRESS = "192.168.1.143"
LOCAL_KEY = "xx"
VERSION = 3.4
# DPS tanımları
DPS_MAPPING = {
'1': {
'id': 1,
'name': 'Güç Anahtarı',
'code': 'switch_1',
'type': bool,
'access': 'rw',
'icon': 'icon-dp_power2',
'format': lambda x: 'Açık' if x else 'Kapalı'
},
'9': {
'id': 9,
'name': 'Geri Sayım',
'code': 'countdown_1',
'type': int,
'access': 'rw',
'icon': 'icon-dp_time2',
'max': 86400,
'min': 0,
'scale': 0,
'step': 1,
'unit': 'saniye',
'format': lambda x: f'{x} saniye'
},
'17': {
'id': 17,
'name': 'Toplam Enerji',
'code': 'add_ele',
'type': int,
'access': 'ro',
'max': 50000,
'min': 0,
'scale': 3,
'step': 100,
'format': lambda x: f'{x/1000:.3f} kWh'
},
'18': {
'id': 18,
'name': 'Anlık Akım',
'code': 'cur_current',
'type': int,
'access': 'ro',
'max': 30000,
'min': 0,
'unit': 'mA',
'format': lambda x: f'{x/1000:.2f} A'
},
'19': {
'id': 19,
'name': 'Anlık Güç',
'code': 'cur_power',
'type': int,
'access': 'ro',
'max': 80000,
'min': 0,
'scale': 1,
'unit': 'W',
'format': lambda x: f'{x/10:.1f} W'
},
'20': {
'id': 20,
'name': 'Anlık Voltaj',
'code': 'cur_voltage',
'type': int,
'access': 'ro',
'max': 5000,
'min': 0,
'scale': 1,
'unit': 'V',
'format': lambda x: f'{x/10:.1f} V'
},
'21': {
'id': 21,
'name': 'Test Sonucu',
'code': 'test_bit',
'type': int,
'access': 'ro',
'max': 5,
'min': 0,
'format': lambda x: f'Test Sonucu: {x}'
},
'26': {
'id': 26,
'name': 'Hata Durumu',
'code': 'fault',
'type': int,
'access': 'ro',
'labels': ['Aşırı Akım', 'Aşırı Voltaj', 'Aşırı Güç', 'Düşük Akım', 'Düşük Voltaj', 'Düşük Güç'],
'format': lambda x: decode_fault(x)
},
'38': {
'id': 38,
'name': 'Röle Durumu',
'code': 'relay_status',
'type': str,
'access': 'rw',
'icon': 'icon-zhuangtai',
'range': ['off', 'on', 'memory'],
'format': lambda x: {'off': 'Kapalı', 'on': 'Açık', 'memory': 'Hafıza'}.get(x, x)
},
'39': {
'id': 39,
'name': 'Aşırı Şarj Koruması',
'code': 'overcharge_switch',
'type': bool,
'access': 'rw',
'format': lambda x: 'Aktif' if x else 'Pasif'
},
'40': {
'id': 40,
'name': 'LED Gösterge Modu',
'code': 'light_mode',
'type': str,
'access': 'rw',
'icon': 'tcl_function_light',
'range': ['relay', 'pos', 'none', 'on'],
'format': lambda x: {'relay': 'Röle', 'pos': 'Pozisyon', 'none': 'Kapalı', 'on': 'Açık'}.get(x, x)
},
'41': {
'id': 41,
'name': 'Çocuk Kilidi',
'code': 'child_lock',
'type': bool,
'access': 'rw',
'icon': 'icon-dp_power2',
'format': lambda x: 'Kilitli' if x else 'Kilitsiz'
},
'42': {
'id': 42,
'name': 'Döngü Zamanlayıcı',
'code': 'cycle_time',
'type': str,
'access': 'rw',
'icon': 'icon-dp_time3',
'maxlen': 255,
'format': lambda x: f'Döngü: {x}'
},
'43': {
'id': 43,
'name': 'Rastgele Zamanlayıcı',
'code': 'random_time',
'type': str,
'access': 'rw',
'icon': 'icon-dp_time2',
'maxlen': 255,
'format': lambda x: f'Rastgele: {x}'
},
'44': {
'id': 44,
'name': 'Anlık Anahtar',
'code': 'switch_inching',
'type': str,
'access': 'rw',
'maxlen': 255,
'format': lambda x: f'Anlık: {x}'
}
}
def decode_fault(fault_code):
faults = []
fault_map = {
0: "Aşırı Akım",
1: "Aşırı Voltaj",
2: "Aşırı Güç",
3: "Düşük Akım",
4: "Düşük Voltaj",
5: "Düşük Güç"
}
for bit, fault in fault_map.items():
if fault_code & (1 << bit):
faults.append(fault)
return ', '.join(faults) if faults else 'Normal'
def check_device_status(device):
try:
status = device.status()
if status and 'dps' in status:
dps = status['dps']
print("\n=== Cihaz Durumu ===")
for key, value in dps.items():
if key in DPS_MAPPING:
mapping = DPS_MAPPING[key]
try:
if isinstance(value, mapping['type']):
formatted_value = mapping['format'](value)
print(f"{mapping['name']}: {formatted_value}")
else:
print(f"{mapping['name']}: Tip hatası")
except Exception as e:
print(f"{mapping['name']}: Formatlama hatası - {str(e)}")
print("\n=== Ham Veri ===")
print(status)
else:
print("Cihaz durumu alınamadı!")
except Exception as e:
print(f"Durum kontrolünde hata: {e}")
return None
def connect_device():
try:
device = tinytuya.OutletDevice(
dev_id=DEVICE_ID,
address=IP_ADDRESS,
local_key=LOCAL_KEY
)
device.set_version(VERSION)
return device
except Exception as e:
print(f"Cihaz bağlantısı hatası: {e}")
return None
def main():
device = connect_device()
if not device:
print("Program sonlandırılıyor...")
return
print("Cihaza bağlanıldı...")
check_device_status(device)
if __name__ == "__main__":
main()
kodu çalıştırınca aşagıdaki çıktı ile karşılacağız…
Cihaza bağlanıldı...
=== Cihaz Durumu ===
Güç Anahtarı: Açık
Geri Sayım: 0 saniye
Toplam Enerji: 0.100 kWh
Anlık Akım: 1.24 A
Anlık Güç: 279.3 W
Anlık Voltaj: 233.1 V
Test Sonucu: Test Sonucu: 1
Hata Durumu: Normal
Röle Durumu: Hafıza
Aşırı Şarj Koruması: Pasif
LED Gösterge Modu: Röle
Çocuk Kilidi: Kilitsiz
Döngü Zamanlayıcı: Döngü:
Rastgele Zamanlayıcı: Rastgele:
Anlık Anahtar: Anlık:
=== Ham Veri ===
{'dps': {'1': True, '9': 0, '17': 100, '18': 1242, '19': 2793, '20': 2331, '21': 1, '22': 567, '23': 27594, '24': 14940, '25': 2780, '26': 0, '38': 'memory', '39': False, '40': 'relay', '41': False, '42': '', '43': '', '44': ''}}
şimdilik bu kadar…. ikinci bir yazı olursa veriyi loglamayı, analizi ve açıp kapama örneklerini anlatacağım…