├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── cmd └── serialrecordingtohex │ └── serialrecordingtohex.go ├── commands ├── MultiChannelEncap.go ├── SwitchBinary.go ├── SwitchMultilevel.go ├── constants.go ├── constants_test.go ├── duration.go ├── duration_test.go ├── multiChannelEncap_test.go ├── raw.go ├── raw_test.go ├── reports │ ├── alarm.go │ ├── alarm_test.go │ ├── manufacturer_specific.go │ ├── manufacturer_specific_test.go │ ├── multi_instance_endpoint.go │ ├── multi_instance_endpoint_test.go │ ├── report.go │ ├── report_test.go │ ├── sensor_multi_level.go │ ├── sensor_multi_level_test.go │ ├── switch_binary.go │ ├── switch_binary_test.go │ ├── switch_multilevel.go │ ├── switch_multilevel_test.go │ ├── wakeup.go │ └── wakeup_test.go ├── switchBinary_test.go └── switchMultilevel_test.go ├── connection.go ├── connection_test.go ├── controllable.go ├── controller.go ├── database ├── CREDITS ├── commandclasses.go ├── devices.go ├── json │ ├── _sensoair_0_0.json │ ├── _td1311_0_0.json │ ├── _vitrumiiieudimmer_0_0.json │ ├── act_45602_0_0.json │ ├── act_zdm230_0_0.json │ ├── act_zdp200_0_0.json │ ├── act_zdw103_0_0.json │ ├── act_zdw120_0_0.json │ ├── act_zir010_0_0.json │ ├── act_zrm230_0_0.json │ ├── act_zrp200_0_0.json │ ├── act_zrw230_0_0.json │ ├── act_ztm230_0_0.json │ ├── aeon_aeonzw120_0_0.json │ ├── aeon_dsa03202_0_0.json │ ├── aeon_dsa38_0_0.json │ ├── aeon_dsb05_0_0.json │ ├── aeon_dsb09_0_0.json │ ├── aeon_dsb28_0_0.json │ ├── aeon_dsb29_0_0.json │ ├── aeon_dsb45_0_0.json │ ├── aeon_dsb54_0_0.json │ ├── aeon_dsc06_0_0.json │ ├── aeon_dsc08_0_0.json │ ├── aeon_dsc10_0_0.json │ ├── aeon_dsc11_0_0.json │ ├── aeon_dsc12_0_0.json │ ├── aeon_dsc13_0_0.json │ ├── aeon_dsc14_0_0.json │ ├── aeon_dsc17_0_0.json │ ├── aeon_dsc18_0_0.json │ ├── aeon_dsc19_0_0.json │ ├── aeon_dsc24_0_0.json │ ├── aeon_dsc25_0_0.json │ ├── aeon_dsc26_0_0.json │ ├── aeon_dsc27_0_0.json │ ├── aeon_dsc35_0_0.json │ ├── aeon_dsd31_0_0.json │ ├── aeon_dsd37_0_0.json │ ├── aeon_zw056_0_0.json │ ├── aeon_zw062_0_0.json │ ├── aeon_zw074_0_0.json │ ├── aeon_zw075_0_0.json │ ├── aeon_zw078_0_0.json │ ├── aeon_zw080_0_0.json │ ├── aeon_zw088_1_0.json │ ├── aeon_zw089_0_0.json │ ├── aeon_zw095_0_0.json │ ├── aeon_zw096_0_0.json │ ├── aeon_zw097_0_0.json │ ├── aeon_zw098_0_0.json │ ├── aeon_zw099_0_0.json │ ├── aeon_zw100_0_0.json │ ├── aeon_zw100_1_7.json │ ├── aeon_zw100_1_8.json │ ├── aeon_zw111_0_0.json │ ├── aeon_zw112_0_0.json │ ├── aeon_zw117_0_0.json │ ├── aeon_zw130_0_0.json │ ├── amc_zdsud10_0_0.json │ ├── benext_benextalarmsound_0_0.json │ ├── benext_builtindimmer_0_0.json │ ├── benext_doorsensor_0_0.json │ ├── benext_energyswitch_0_0.json │ ├── benext_heatingcontrol_0_0.json │ ├── benext_molite_0_0.json │ ├── benext_p1dongle_0_0.json │ ├── benext_plugindimmer_0_0.json │ ├── benext_powerswitch_0_0.json │ ├── benext_tagreader_0_0.json │ ├── bonig_pope005206_0_0.json │ ├── brk_zcombo_0_0.json │ ├── brk_zsmoke_0_0.json │ ├── cbcc_swzcs1_0_0.json │ ├── chromagic_hsm02_0_0.json │ ├── chromagic_sm103_0_0.json │ ├── cloud_a803n_0_0.json │ ├── connecthome_ch201_0_0.json │ ├── cooper_rf9500_0_0.json │ ├── cooper_rf9501_0_0.json │ ├── cooper_rf9517_0_0.json │ ├── cooper_rf9534ws_0_0.json │ ├── cooper_rf9540n_0_0.json │ ├── cooper_rf9542_0_0.json │ ├── cooper_rftr9505_0_0.json │ ├── cooper_rfwc5_0_0.json │ ├── coqon_psmz0001_0_0.json │ ├── danfoss_010101_0_0.json │ ├── danfoss_014g0160_0_0.json │ ├── danfoss_dthermz5_0_0.json │ ├── danfoss_dthermz6_0_0.json │ ├── danfoss_lc13_0_0.json │ ├── danfoss_lcz251_0_0.json │ ├── danfoss_mt02650_0_0.json │ ├── devolo_mt02646_0_0.json │ ├── devolo_mt02647_0_0.json │ ├── devolo_mt02648_0_0.json │ ├── devolo_mt2652_0_0.json │ ├── devolo_mt2653_0_0.json │ ├── devolo_mt2755_0_0.json │ ├── devolo_mt2756_0_0.json │ ├── devolo_pan11_0_0.json │ ├── devolo_phpse02_0_0.json │ ├── devolo_pst021b_0_0.json │ ├── dlink_dchz110_0_0.json │ ├── dlink_dchz120_0_0.json │ ├── dlink_dchz510_0_0.json │ ├── domitech_zb22_0_0.json │ ├── domitech_ze27_0_0.json │ ├── dragontech_pa100_0_0.json │ ├── dragontech_wd100_0_0.json │ ├── dragontech_ws100_0_0.json │ ├── eco_dwzwave2_0_0.json │ ├── eco_ecolinkpirv25_0_0.json │ ├── eco_pir1_0_0.json │ ├── eco_tiltzwave25eco_0_0.json │ ├── eco_tiltzwave2_0_0.json │ ├── econet_ebv105_0_0.json │ ├── econet_ev100_0_0.json │ ├── econet_gr105n_0_0.json │ ├── elexa_dms01_0_0.json │ ├── elexa_dms01domewirelesssiren_0_0.json │ ├── enerwave_enerwavezw15s_0_0.json │ ├── enerwave_zw15r_0_0.json │ ├── enerwave_zw20r_0_0.json │ ├── enerwave_zw20rm_0_0.json │ ├── enerwave_zw500d_0_0.json │ ├── enerwave_zw500dm_0_0.json │ ├── enerwave_zwmrsm1_0_0.json │ ├── enerwave_zwn333_0_0.json │ ├── enerwave_zwnbpc_0_0.json │ ├── enerwave_zwnrsm1plus_0_0.json │ ├── enerwave_zwnrsm2_0_0.json │ ├── enerwave_zwnsc7_0_0.json │ ├── eurotronic_cometz_0_0.json │ ├── eurotronic_stellaz_0_0.json │ ├── everspring135_st812_0_0.json │ ├── everspring_ad130_0_0.json │ ├── everspring_ad131_0_0.json │ ├── everspring_ad146_0_0.json │ ├── everspring_ad147_0_0.json │ ├── everspring_an157_0_0.json │ ├── everspring_an158_0_0.json │ ├── everspring_an163_0_0.json │ ├── everspring_an179_0_0.json │ ├── everspring_an1802_0_0.json │ ├── everspring_an180_0_0.json │ ├── everspring_an181_0_0.json │ ├── everspring_hac01_0_0.json │ ├── everspring_hsm02_0_0.json │ ├── everspring_hsp02_0_0.json │ ├── everspring_se812_0_0.json │ ├── everspring_sf812_0_0.json │ ├── everspring_sm103_0_0.json │ ├── everspring_sp103_0_0.json │ ├── everspring_sp814_0_0.json │ ├── everspring_st812_0_0.json │ ├── everspring_st814_0_0.json │ ├── everspring_st815_0_0.json │ ├── evolve_ldm15w_0_0.json │ ├── evolve_lfm20_0_0.json │ ├── evolve_lpm15_0_0.json │ ├── evolve_lrmas_0_0.json │ ├── evolve_lsm15_0_0.json │ ├── evolve_t100_0_0.json │ ├── fakro_arz_0_0.json │ ├── fakro_zws12_0_0.json │ ├── fibaro_fgbs001_0_0.json │ ├── fibaro_fgcc001_0_0.json │ ├── fibaro_fgd211_0_0.json │ ├── fibaro_fgd211_1_9.json │ ├── fibaro_fgd211_2_1.json │ ├── fibaro_fgd212_0_0.json │ ├── fibaro_fgfs101_0_0.json │ ├── fibaro_fgfs101_23_23.json │ ├── fibaro_fgfs101_25_25.json │ ├── fibaro_fgfs101_3_2.json │ ├── fibaro_fgk101_0_0.json │ ├── fibaro_fgk101_3_2.json │ ├── fibaro_fgkf601_0_0.json │ ├── fibaro_fgms001_0_0.json │ ├── fibaro_fgms001_3_2.json │ ├── fibaro_fgpb101_0_0.json │ ├── fibaro_fgr221_0_0.json │ ├── fibaro_fgrgbw_0_0.json │ ├── fibaro_fgrm222_0_0.json │ ├── fibaro_fgrm222_24_24.json │ ├── fibaro_fgrm222_25_25.json │ ├── fibaro_fgs211_0_0.json │ ├── fibaro_fgs212_0_0.json │ ├── fibaro_fgs213_0_0.json │ ├── fibaro_fgs221_1_4.json │ ├── fibaro_fgs221_1_9.json │ ├── fibaro_fgs221_2_1.json │ ├── fibaro_fgs222_0_0.json │ ├── fibaro_fgs223_0_0.json │ ├── fibaro_fgsd002_0_0.json │ ├── fibaro_fgss001_0_0.json │ ├── fibaro_fgss101_0_0.json │ ├── fibaro_fgwp101_0_0.json │ ├── fibaro_fgwp102_0_0.json │ ├── fibaro_fgwp102_2_1.json │ ├── fibaro_fgwp102_3_0.json │ ├── fibaro_fgwp102_3_2.json │ ├── fibaro_fgwpe_0_0.json │ ├── fibaro_fibarodoubleswitch2_0_0.json │ ├── forest_forestshuttlesl_0_0.json │ ├── fortrezz_mimolite_0_0.json │ ├── fortrezz_ssa1_0_0.json │ ├── fortrezz_wv01_0_0.json │ ├── fortrezz_wwa02_0_0.json │ ├── ge_12718_0_0.json │ ├── ge_12727_0_0.json │ ├── ge_12729_0_0.json │ ├── ge_35931_0_0.json │ ├── ge_45603_0_0.json │ ├── ge_45604_0_0.json │ ├── ge_45605_0_0.json │ ├── ge_45606_0_0.json │ ├── ge_45607_0_0.json │ ├── ge_45609_0_0.json │ ├── ge_enbrighten60wbulb_0_0.json │ ├── ge_ge12725_0_0.json │ ├── ge_ge14291_0_0.json │ ├── ge_ge14291zw4005_0_0.json │ ├── ge_ge14294_0_0.json │ ├── ge_gejasco14321_0_0.json │ ├── ge_gepluginsmartswitch_0_0.json │ ├── ge_jascosmartdoorsensor_0_0.json │ ├── ge_smartdoorsensor_0_0.json │ ├── ge_zw1001_0_0.json │ ├── ge_zw3003_0_0.json │ ├── ge_zw3101_0_0.json │ ├── ge_zw4002_0_0.json │ ├── ge_zw4005_0_0.json │ ├── ge_zw4101_0_0.json │ ├── ge_zw6302_0_0.json │ ├── goodway_td1311_0_0.json │ ├── graber_csz1_0_0.json │ ├── graber_rsz1_0_0.json │ ├── greenwave_gwpn1_3_0.json │ ├── greenwave_gwpn5_0_0.json │ ├── greenwave_gwpn6_0_0.json │ ├── hank_hkzws001_0_0.json │ ├── hank_scn01_0_0.json │ ├── hank_so05_0_0.json │ ├── heiman_hs1ds_0_0.json │ ├── heiman_hs1sa_0_0.json │ ├── heiman_hs2skz_0_0.json │ ├── hogar_htp4s1fb_0_0.json │ ├── hogar_htp4s1fb_2_2.json │ ├── homeseer_ezmotionexpress_0_0.json │ ├── homeseer_ezmultipli_0_0.json │ ├── homeseer_hswd100_0_0.json │ ├── homeseer_hsws100_0_0.json │ ├── honeywell_th8320zw_0_0.json │ ├── horstmann_asrzw_0_0.json │ ├── horstmann_hrt4zw_0_0.json │ ├── horstmann_scsc17_0_0.json │ ├── horstmann_ses302_0_0.json │ ├── horstmann_sir321_0_0.json │ ├── horstmann_srt323_0_0.json │ ├── horstmann_ssr302_0_0.json │ ├── horstmann_swm301_0_0.json │ ├── idlock_idl101_0_0.json │ ├── idlock_idl101_1_0.json │ ├── ingersollecolink_dwzwave1_0_0.json │ ├── innovus_00110001_0_0.json │ ├── innovus_00110001_1_0.json │ ├── innovus_smartdimmer_0_0.json │ ├── innovus_smartpower_0_0.json │ ├── intermatic_ca3500_0_0.json │ ├── intermatic_ca3750_0_0.json │ ├── intermatic_ca8900_0_0.json │ ├── intermatic_ha01c_0_0.json │ ├── intermatic_ha02_0_0.json │ ├── intermatic_ha03_0_0.json │ ├── intermatic_ha04_0_0.json │ ├── intermatic_ha04c_0_0.json │ ├── intermatic_ha05_0_0.json │ ├── intermatic_ha20_0_0.json │ ├── intermatic_intermaticca3000_0_0.json │ ├── intermatic_intermaticca3500_0_0.json │ ├── kaipule_es61_0_0.json │ ├── kichler_15dc200_0_0.json │ ├── kwikset_914trl_0_0.json │ ├── leviton_dz15s_0_0.json │ ├── leviton_dz6hd_0_0.json │ ├── leviton_dzmx11lz_0_0.json │ ├── leviton_dzpa1_0_0.json │ ├── leviton_dzpd32_0_0.json │ ├── leviton_dzpd3_0_0.json │ ├── leviton_dzr15_0_0.json │ ├── leviton_dzs15_0_0.json │ ├── leviton_rzp03_0_0.json │ ├── leviton_vrcs2mrx_0_0.json │ ├── leviton_vre06_0_0.json │ ├── leviton_vrf011lz_0_0.json │ ├── leviton_vrf01_0_0.json │ ├── leviton_vri06_0_0.json │ ├── leviton_vrmx11lz_0_0.json │ ├── leviton_vrp03_0_0.json │ ├── leviton_vrpa1_0_0.json │ ├── leviton_vrpd3_0_0.json │ ├── leviton_vrs05_0_0.json │ ├── leviton_vrs15_0_0.json │ ├── linear_fs20z_0_0.json │ ├── linear_gb00z_0_0.json │ ├── linear_lb60z1_0_0.json │ ├── linear_ngd00z4_0_0.json │ ├── linear_pd300z2_0_0.json │ ├── linear_ps15z_0_0.json │ ├── linear_tbz48_0_0.json │ ├── linear_wa105dbz1_0_0.json │ ├── linear_wa105dbz_0_0.json │ ├── linear_wadwaz1_0_0.json │ ├── linear_wapirz_0_0.json │ ├── linear_wd500z_0_0.json │ ├── linear_wo15z_0_0.json │ ├── linear_ws15z_0_0.json │ ├── linear_wt00z1_0_0.json │ ├── logic_zhc5010_0_0.json │ ├── logic_zhc5010_1_4.json │ ├── mcohome__0_0.json │ ├── mcohome_co2monitor_0_0.json │ ├── mcohome_mcotps412_0_0.json │ ├── mcohome_mh10pm25wa_0_0.json │ ├── mcohome_mh7_0_0.json │ ├── mcohome_mh8fc_1_2.json │ ├── mcohome_mh9co2wd_0_0.json │ ├── mcohome_mhs312_0_0.json │ ├── mcohome_mhs314_0_0.json │ ├── mcohome_mhs411_0_0.json │ ├── mcohome_mhs412_0_0.json │ ├── mcohome_tps411_0_0.json │ ├── mcohome_tps412_0_0.json │ ├── merten_505160_0_0.json │ ├── merten_506119_0_0.json │ ├── merten_507001_0_0.json │ ├── merten_507501_0_0.json │ ├── merten_507601_0_0.json │ ├── merten_507900_0_0.json │ ├── merten_5082xx_0_0.json │ ├── merten_509519_0_0.json │ ├── merten_50x5xx_0_0.json │ ├── merten_mertenradioargus220connect_0_0.json │ ├── nexia_db100z_0_0.json │ ├── nodon_asp31_0_0.json │ ├── nodon_crc3100_0_0.json │ ├── nodon_cws3101_0_0.json │ ├── nodon_msp31x1_0_0.json │ ├── nodon_softremote_0_0.json │ ├── northq_nq-9121_0_0.json │ ├── northq_nq9021_0_0.json │ ├── northq_nq9022_0_0.json │ ├── northq_nq9121_3_0.json │ ├── philio_pab01_0_0.json │ ├── philio_pad02_0_0.json │ ├── philio_pan03_0_0.json │ ├── philio_pan04_0_0.json │ ├── philio_pan06_0_0.json │ ├── philio_pan08_0_0.json │ ├── philio_pan11_0_0.json │ ├── philio_pat02_0_0.json │ ├── philio_pat02a_0_0.json │ ├── philio_pat02b_0_0.json │ ├── philio_pat02c_0_0.json │ ├── philio_pat02c_1_11.json │ ├── philio_pse02_0_0.json │ ├── philio_psm02_0_0.json │ ├── philio_psr04_0_0.json │ ├── philio_pst02a_0_0.json │ ├── philio_pst02b_0_0.json │ ├── philio_pst02c_0_0.json │ ├── polycontrol_btzu125_0_0.json │ ├── popp_004001_0_0.json │ ├── popp_004407_1_0.json │ ├── popp_005107_0_0.json │ ├── popp_005107_1_1.json │ ├── popp_005107_1_3.json │ ├── popp_005206_0_0.json │ ├── popp_009204_0_0.json │ ├── popp_009303_0_0.json │ ├── popp_009402_0_0.json │ ├── popp_009402_1_14.json │ ├── popp_012501_0_0.json │ ├── popp_05438_0_0.json │ ├── popp_123580_0_0.json │ ├── popp_123610_0_0.json │ ├── popp_123665_0_0.json │ ├── popp_pope005206_0_0.json │ ├── popp_pope700168_0_0.json │ ├── prodrive_ed20_0_0.json │ ├── qees_p313a_0_0.json │ ├── qees_qeeswall_0_0.json │ ├── qubino_zmnhaa_0_0.json │ ├── qubino_zmnhad_0_0.json │ ├── qubino_zmnhba_0_0.json │ ├── qubino_zmnhbd_0_0.json │ ├── qubino_zmnhcd_0_0.json │ ├── qubino_zmnhda_0_0.json │ ├── qubino_zmnhdd_0_0.json │ ├── qubino_zmnhia_0_0.json │ ├── qubino_zmnhid_0_0.json │ ├── qubino_zmnhja2_0_0.json │ ├── qubino_zmnhja_0_0.json │ ├── qubino_zmnhjd_0_0.json │ ├── qubino_zmnhla_0_0.json │ ├── qubino_zmnhld_0_0.json │ ├── qubino_zmnhnd_0_0.json │ ├── qubino_zmnhod_0_0.json │ ├── qubino_zmnhsd_0_0.json │ ├── qubino_zmnhvd_0_0.json │ ├── qubino_zmnhwd_0_0.json │ ├── qubino_zmnhwdx_0_0.json │ ├── qubino_zmnhzd_0_0.json │ ├── rcs_tbz48_0_0.json │ ├── rcs_tz43_0_0.json │ ├── reitz_05431_0_0.json │ ├── reitz_05433_0_0.json │ ├── reitz_054375_0_0.json │ ├── reitz_05443_0_0.json │ ├── reitz_06436_0_0.json │ ├── reitz_064394_0_0.json │ ├── reitz_duwrpt_0_0.json │ ├── reitz_swiidplug_0_0.json │ ├── reitz_zme05431_0_0.json │ ├── reitz_zme05433ex_0_0.json │ ├── reitz_zwesj300_0_0.json │ ├── remotec_zfm80_0_0.json │ ├── remotec_zrc100_0_0.json │ ├── remotec_zrc90_0_0.json │ ├── remotec_zts110_0_0.json │ ├── remotec_zxt-120au_0_0.json │ ├── remotec_zxt120_0_0.json │ ├── rimport_razberry2_1_1.json │ ├── rimport_zmeeraz2_0_0.json │ ├── rtc_ct100_0_0.json │ ├── rtc_ct101_0_0.json │ ├── rtc_ct110thermostat_0_0.json │ ├── rtc_ct30_0_0.json │ ├── rtc_ct32_0_0.json │ ├── schlage_be369_0_0.json │ ├── schlage_be468_0_0.json │ ├── schlage_be469_0_0.json │ ├── schlage_fe599nx_0_0.json │ ├── seco_clamp_0_0.json │ ├── sensative_1101011_0_0.json │ ├── shenzhen_doorsensorin_0_0.json │ ├── shenzhen_doorwindowsensor_0_0.json │ ├── shenzhen_floodsensor_0_0.json │ ├── shenzhen_motionsensor_0_0.json │ ├── shenzhen_motionsensorin_0_0.json │ ├── shenzhen_nasds01z_0_0.json │ ├── shenzhen_powerplug_0_0.json │ ├── shenzhen_sirenalarm_0_0.json │ ├── siegeniaaubi_sensoair_0_0.json │ ├── sigma_zwck8_0_0.json │ ├── somfy_zrtsi_0_0.json │ ├── somfy_zrtsivnode_0_0.json │ ├── stelpro_stzw402_0_0.json │ ├── swiid_swzcs1_0_0.json │ ├── systemair_29990_0_0.json │ ├── telldus_tzdw100_0_0.json │ ├── telldus_tzwp100_0_0.json │ ├── thermofloor_tf016_0_0.json │ ├── thermofloor_tf016_1_8.json │ ├── tkb_tsm02_0_0.json │ ├── tkb_tz08_0_0.json │ ├── tkb_tz37_0_0.json │ ├── tkb_tz55d_0_0.json │ ├── tkb_tz65d_0_0.json │ ├── tkb_tz66d_0_0.json │ ├── tkb_tz67_0_0.json │ ├── tkb_tz68_0_0.json │ ├── tkb_tz69_0_0.json │ ├── tkb_tz88_0_0.json │ ├── trane_azemt500bb32ma_0_0.json │ ├── trane_tzemt400bb32maa_0_0.json │ ├── trane_xl624_0_0.json │ ├── trane_xr524_0_0.json │ ├── ufairy_gr105_0_0.json │ ├── ufairy_grm202n2_0_0.json │ ├── ufairy_ufairy2gangwallswitch_0_0.json │ ├── ufairy_zse02_0_0.json │ ├── vda_dimmer3_0_0.json │ ├── vision_pid11995_0_0.json │ ├── vision_pid15903_0_0.json │ ├── vision_zd2102_0_0.json │ ├── vision_zd2105_0_0.json │ ├── vision_zd2201_0_0.json │ ├── vision_zg8101_0_0.json │ ├── vision_zl7101_0_0.json │ ├── vision_zl7431_0_0.json │ ├── vision_zl7432_0_0.json │ ├── vision_zm1601_0_0.json │ ├── vision_zm1602_0_0.json │ ├── vision_zm1702_0_0.json │ ├── vision_zp3102_0_0.json │ ├── vision_zp3102_8_0.json │ ├── vision_zp3103_0_0.json │ ├── vision_zs5101_0_0.json │ ├── vision_zs6101_0_0.json │ ├── vision_zs6301_0_0.json │ ├── vision_zse40_0_0.json │ ├── vision_zw4101_0_0.json │ ├── widom_ubs_1_0.json │ ├── widom_wds_0_0.json │ ├── widom_wps104_0_0.json │ ├── willis_zen20_0_0.json │ ├── willis_zen21_0_0.json │ ├── willis_zen23_0_0.json │ ├── willis_zen24_0_0.json │ ├── willis_zw37_0_0.json │ ├── wintop_1122r_1_4.json │ ├── wintop_dhsitswpdhs_0_0.json │ ├── wintop_dhszwdmiw01_0_0.json │ ├── wintop_easyplug_0_0.json │ ├── wintop_ishutter_0_0.json │ ├── wintop_itemp_0_0.json │ ├── wintop_le120_0_0.json │ ├── wintop_multisensor_0_0.json │ ├── wintop_wtrfid_0_0.json │ ├── yale_ykfcon_0_0.json │ ├── yale_yrd110_0_0.json │ ├── yale_yrd120_0_0.json │ ├── yale_yrd210_0_0.json │ ├── yale_yrd220_0_0.json │ ├── yale_yrl220_0_0.json │ ├── zipato_rgbwe27zw_0_0.json │ ├── zooz_zen06_0_0.json │ ├── zooz_zen07_0_0.json │ ├── zooz_zoozzse08_0_0.json │ ├── zooz_zse09_0_0.json │ ├── zooz_zse30_0_0.json │ ├── zooz_zse40_0_0.json │ ├── zwaveme_06436_0_0.json │ ├── zwaveme_kfob_0_0.json │ ├── zwaveme_kfobc_0_0.json │ ├── zwaveme_wallcs_0_0.json │ ├── zwaveme_wcd2_0_0.json │ ├── zwaveme_zme05431_0_0.json │ ├── zwaveme_zme05433_0_0.json │ ├── zwaveme_zme05459_0_0.json │ ├── zwaveme_zme05461_0_0.json │ ├── zwaveme_zme06437_0_0.json │ ├── zwaveme_zme06443_0_0.json │ ├── zwaveme_zmeft_0_0.json │ ├── zwaveme_zmekfobs_0_0.json │ ├── zwaveme_zmerc2_0_0.json │ ├── zwaveme_zuno_1_0.json │ ├── zwaveme_zweather_0_0.json │ └── zyxel_st812_0_0.json ├── mandatory.go ├── mandatory.txt └── types.go ├── events.go ├── events ├── node_awake.go ├── node_discoverd.go ├── node_identification.go ├── node_updated.go └── node_values.go ├── generators ├── commandclasses │ └── commandclasses.go └── database │ ├── database.go │ └── database_test.go ├── gozwave.go ├── gozwave_test.go ├── interfaces └── encodable.go ├── mock_test.go ├── nodes ├── commands.go ├── endpoints.go ├── list.go ├── manufacurer_specific.go ├── node.go ├── protocol_info.go ├── request_endpoints.go └── request_states.go ├── protocol └── constants.go ├── serialapi ├── application_command_handler.go ├── application_command_handler_test.go ├── discovery_nodes.go ├── discovery_nodes_test.go ├── get_node_protocolinfo.go ├── get_node_protocolinfo_test.go ├── message.go ├── message_test.go ├── raw.go ├── raw_test.go ├── types.go └── types_test.go ├── serialrecorder ├── direction.go ├── direction_test.go ├── serialrecorder.go └── serialrecorder_test.go └── zwave-example └── example.go /.gitignore: -------------------------------------------------------------------------------- 1 | zwave-example/zwave-example 2 | coverage.txt 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | before_install: 7 | - go get -t -v ./... 8 | 9 | script: 10 | - make cover 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test cover cover-html cover-test 2 | 3 | test: 4 | go test ./... 5 | 6 | cover: 7 | @echo Running coverage 8 | go get github.com/wadey/gocovmerge 9 | $(eval PKGS := $(shell go list ./... | grep -v /vendor/ | grep -v database )) 10 | $(eval PKGS_DELIM := $(shell echo $(PKGS) | sed -e 's/ /,/g')) 11 | go list -f '{{if or (len .TestGoFiles) (len .XTestGoFiles)}}go test -test.v -test.timeout=120s -covermode=atomic -coverprofile={{.Name}}_{{len .Imports}}_{{len .Deps}}.coverprofile -coverpkg $(PKGS_DELIM) {{.ImportPath}}{{end}}' $(PKGS) | xargs -I {} bash -c {} 12 | gocovmerge `ls *.coverprofile` > coverage.txt 13 | rm *.coverprofile 14 | 15 | cover-html: cover 16 | go tool cover -html cover.out 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gozwave [](https://codecov.io/gh/stampzilla/gozwave) [](https://travis-ci.org/stampzilla/gozwave) 2 | An opensource golang lib for zwave-controller communication thru serial-api. Tested and developed with a usb connected AEOTEC Z-Stick Gen5 3 | 4 | Sigmadesigns have released some of the zwave documentation and it can be found here http://z-wave.sigmadesigns.com/design-z-wave/z-wave-public-specification/ 5 | 6 | ## Can I use this library in production? 7 | This library is in the early development state and is not ready for production in any way. The api is subject to changes and we will probably redesign it a couple of times before its ready. 8 | 9 | ## Contribute 10 | We love any help we can get! If you are interested just fork the lib, make changes and send ous a pull request. 11 | We have a slack chat room on https://gophers.slack.com/messages/gozwave/ you can get invite on https://invite.slack.golangbridge.org/ 12 | 13 | ## TODO 14 | - [x] Basic communication between controller and lib (serial-api) 15 | - [x] Get a list of nodes 16 | - [x] Identify each node and look them up in a device database 17 | - [x] Save/Load the identified nodes in a file [#4](https://github.com/stampzilla/gozwave/issues/4) 18 | - [ ] List, get and set parameters in a node 19 | - [x] Implement commands to send to the nodes (Some implemented) 20 | - [x] Receive and decode events from sensors 21 | -------------------------------------------------------------------------------- /cmd/serialrecordingtohex/serialrecordingtohex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/gob" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "sort" 11 | "time" 12 | 13 | "github.com/stampzilla/gozwave/serialrecorder" 14 | ) 15 | 16 | var file string 17 | var short bool 18 | var read bool 19 | var write bool 20 | 21 | func main() { 22 | flag.StringVar(&file, "file", "", "File to decode") 23 | flag.BoolVar(&short, "short", false, "Exclude timestamp, direction and writes") 24 | flag.BoolVar(&read, "read", true, "Include reads") 25 | flag.BoolVar(&write, "write", true, "Include writes") 26 | flag.Parse() 27 | 28 | if file == "" { 29 | log.Fatalln("No file specified. Use -file=") 30 | return 31 | } 32 | 33 | // Open the logfile 34 | file, err := os.Open(file) 35 | if err != nil { 36 | log.Fatalln(err) 37 | } 38 | 39 | // Read and decode all lines in the file 40 | dec := gob.NewDecoder(file) 41 | i := 0 42 | var rows []serialrecorder.Row 43 | for { 44 | // Save the line number so we can print a better error message 45 | i++ 46 | 47 | // Decode the row 48 | var r serialrecorder.Row 49 | err = dec.Decode(&r) 50 | if err != nil { 51 | if err == io.EOF { 52 | break 53 | } 54 | log.Fatalf("decode row %d: %s\n", i, err.Error()) 55 | } 56 | 57 | rows = append(rows, r) 58 | } 59 | 60 | // Create a list of combined rows 61 | reads := logRowCombiner{} 62 | writes := logRowCombiner{} 63 | var combinedRows rowsByTimestamp 64 | for _, v := range rows { 65 | if v.Direction == serialrecorder.DirectionRead { 66 | c := reads.combine(v) 67 | if c != nil { 68 | combinedRows = append(combinedRows, *c) 69 | } 70 | continue 71 | } 72 | 73 | c := writes.combine(v) 74 | if c != nil { 75 | combinedRows = append(combinedRows, *c) 76 | } 77 | } 78 | 79 | // Sort the rows and print them out 80 | sort.Sort(combinedRows) 81 | for _, v := range combinedRows { 82 | if v.Direction.IsRead() && !read { 83 | continue 84 | } 85 | if v.Direction.IsWrite() && !write { 86 | continue 87 | } 88 | 89 | if short { 90 | fmt.Printf("%x\n", v.Data) 91 | continue 92 | } 93 | fmt.Printf("%s: %s - %x\n", v.Timestamp, v.Direction, v.Data) 94 | } 95 | } 96 | 97 | type row serialrecorder.Row 98 | type rowsByTimestamp []row 99 | 100 | func (c rowsByTimestamp) Len() int { 101 | return len(c) 102 | } 103 | func (c rowsByTimestamp) Swap(i, j int) { 104 | c[i], c[j] = c[j], c[i] 105 | } 106 | func (c rowsByTimestamp) Less(i, j int) bool { 107 | return c[i].Timestamp.Before(c[j].Timestamp) 108 | } 109 | 110 | type logRowCombiner struct { 111 | prev serialrecorder.Row 112 | buf []byte 113 | } 114 | 115 | func (l *logRowCombiner) combine(r serialrecorder.Row) *row { 116 | // If its the first row, set timestamp to same as first row 117 | if (l.prev.Timestamp == time.Time{}) { 118 | l.prev = r 119 | } 120 | 121 | // Calculate the duration from the last row 122 | duration := r.Timestamp.Sub(l.prev.Timestamp) 123 | 124 | // If duration is short, just add it to the buffer 125 | if duration < time.Millisecond { 126 | l.buf = append(l.buf, r.Data...) 127 | l.prev = r 128 | return nil 129 | } 130 | 131 | combinedRow := &row{ 132 | Timestamp: l.prev.Timestamp, 133 | Direction: l.prev.Direction, 134 | Data: l.buf, 135 | } 136 | 137 | l.buf = r.Data 138 | l.prev = r 139 | 140 | return combinedRow 141 | } 142 | -------------------------------------------------------------------------------- /commands/MultiChannelEncap.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | type MultiChannelEncap struct { 4 | msg []byte 5 | endpoint int 6 | } 7 | 8 | func NewMultiChannelEncap(msg []byte, endpoint int) *MultiChannelEncap { 9 | return &MultiChannelEncap{ 10 | msg: msg, 11 | endpoint: endpoint, 12 | } 13 | } 14 | 15 | func (m *MultiChannelEncap) Encode() []byte { 16 | msg := m.msg 17 | 18 | ret := []byte{ 19 | msg[0], // serialapi function 20 | msg[1], // node id 21 | msg[2] + 4, // length 22 | MultiInstance, 23 | 0x0d, // MultiChannelCmd_Encap 24 | 0x01, // Length 25 | byte(m.endpoint), // Endpoint 26 | } 27 | 28 | ret = append(ret, msg[3:]...) 29 | 30 | return ret 31 | } 32 | -------------------------------------------------------------------------------- /commands/SwitchBinary.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | type CmdSwitchBinary struct { 4 | node byte 5 | value bool 6 | } 7 | 8 | func NewSwitchBinary() *CmdSwitchBinary { 9 | return &CmdSwitchBinary{} 10 | 11 | } 12 | 13 | func (c *CmdSwitchBinary) SetValue(v bool) *CmdSwitchBinary { 14 | c.value = v 15 | return c 16 | } 17 | func (c *CmdSwitchBinary) SetNode(n int) *CmdSwitchBinary { 18 | c.node = byte(n) 19 | return c 20 | } 21 | 22 | func (c *CmdSwitchBinary) Encode() []byte { 23 | v := byte(0x00) 24 | if c.value { 25 | v = 0xff 26 | } 27 | return []byte{ 28 | 0x13, // SEND ZW 29 | c.node, // NOD ID 30 | 0x03, // length 31 | SwitchBinary, // command class id 32 | 0x01, // set 33 | v, // value 34 | 0x25, // transmit options 35 | //0x08, // transmit options 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /commands/SwitchMultilevel.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | type CmdSwitchMultilevel struct { 4 | node byte 5 | level byte 6 | } 7 | 8 | func NewSwitchMultilevel() *CmdSwitchMultilevel { 9 | 10 | return &CmdSwitchMultilevel{} 11 | } 12 | 13 | func (c *CmdSwitchMultilevel) SetValue(v float64) *CmdSwitchMultilevel { 14 | if v < 1 { 15 | v = 0 16 | } 17 | if v > 99 { 18 | v = 99 19 | } 20 | // TODO: Round to integer first to fix precision error 21 | c.level = byte(v) 22 | return c 23 | } 24 | func (c *CmdSwitchMultilevel) SetNode(n int) *CmdSwitchMultilevel { 25 | c.node = byte(n) 26 | return c 27 | } 28 | 29 | func (c *CmdSwitchMultilevel) Encode() []byte { 30 | return []byte{ 31 | 0x13, // SEND ZW 32 | c.node, // NOD ID 33 | 0x03, // length 34 | SwitchMultilevel, // command class id 35 | 0x01, // set 36 | c.level, // value 37 | 0x25, // transmit options 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /commands/constants_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestZwaveCommand(t *testing.T) { 10 | m := map[byte]string{ 11 | 0x20: "Basic", 12 | 0x21: "ControllerReplication", 13 | 0x22: "ApplicationStatus", 14 | 0x25: "SwitchBinary", 15 | 0x26: "SwitchMultilevel", 16 | 0x27: "SwitchAll", 17 | 0x28: "SwitchToggleBinary", 18 | 0x29: "SwitchToggleMultilevel", 19 | 0x2B: "SceneActivation", 20 | 0x2C: "SceneActuatorConf", 21 | 0x30: "SensorBinary", 22 | 0x31: "SensorMultiLevel", 23 | 0x32: "Meter", 24 | 0x35: "MeterPulse", 25 | 0x40: "ThermostatMode", 26 | 0x42: "ThermostatOperatingState", 27 | 0x43: "ThermostatSetpoint", 28 | 0x44: "ThermostatFanMode", 29 | 0x45: "ThermostatFanState", 30 | 0x46: "ClimateControlSchedule", 31 | 0x50: "BasicWindowCovering", 32 | 0x60: "MultiInstance", 33 | 0x70: "Configuration", 34 | 0x71: "Alarm", 35 | 0x72: "ManufacturerSpecific", 36 | 0x73: "PowerLevel", 37 | 0x75: "Protection", 38 | 0x76: "Lock", 39 | 0x77: "NodeNaming", 40 | 0x80: "Battery", 41 | 0x81: "Clock", 42 | 0x82: "Hail", 43 | 0x84: "WakeUp", 44 | 0x85: "Association", 45 | 0x86: "Version", 46 | 0x87: "Indicator", 47 | 0x88: "Proprietary", 48 | 0x89: "Language", 49 | 0x8e: "MultiInstanceAssociation", 50 | 0x8F: "MultiCmd", 51 | 0x90: "EnergyProduction", 52 | 0x91: "ManufacturerProprietary", 53 | 0x9b: "AssociationCommandConfiguration", 54 | 0xEF: "Mark", 55 | } 56 | 57 | for k, v := range m { 58 | assert.Equal(t, v, ZWaveCommand(k).String()) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /commands/duration.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Duration used to describe time between 0-127 seconds and 1-127 minutes in a byte 9 | type Duration byte 10 | 11 | // Duration converts zwave duration type into time.Duration 12 | func (d Duration) Duration() (time.Duration, error) { 13 | switch { 14 | case byte(d) == 0x00: 15 | return time.Duration(0), nil 16 | case byte(d) <= 0x7F: 17 | return time.Duration(d) * time.Second, nil 18 | case d <= 0xFD: 19 | return time.Duration(d-0x7F) * time.Minute, nil 20 | } 21 | return time.Duration(0), fmt.Errorf("Factory default duration") 22 | } 23 | 24 | func (d Duration) String() string { 25 | t, err := d.Duration() 26 | 27 | if err != nil { 28 | return err.Error() 29 | } 30 | 31 | return t.String() 32 | } 33 | -------------------------------------------------------------------------------- /commands/duration_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDuration0(t *testing.T) { 11 | a := Duration(0) 12 | 13 | d, err := a.Duration() 14 | 15 | assert.NoError(t, err) 16 | assert.Equal(t, time.Duration(0), d) 17 | assert.Equal(t, time.Duration(0).String(), a.String()) 18 | } 19 | 20 | func TestDuration1s(t *testing.T) { 21 | a := Duration(0x01) 22 | 23 | d, err := a.Duration() 24 | 25 | assert.NoError(t, err) 26 | assert.Equal(t, 1*time.Second, d) 27 | assert.Equal(t, time.Duration(1*time.Second).String(), a.String()) 28 | } 29 | func TestDuration127s(t *testing.T) { 30 | a := Duration(0x7F) 31 | 32 | d, err := a.Duration() 33 | 34 | assert.NoError(t, err) 35 | assert.Equal(t, 127*time.Second, d) 36 | assert.Equal(t, time.Duration(127*time.Second).String(), a.String()) 37 | } 38 | 39 | func TestDuration1m(t *testing.T) { 40 | a := Duration(0x80) 41 | 42 | d, err := a.Duration() 43 | 44 | assert.NoError(t, err) 45 | assert.Equal(t, 1*time.Minute, d) 46 | assert.Equal(t, time.Duration(1*time.Minute).String(), a.String()) 47 | } 48 | 49 | func TestDuration126m(t *testing.T) { 50 | a := Duration(0xFD) 51 | 52 | d, err := a.Duration() 53 | 54 | assert.NoError(t, err) 55 | assert.Equal(t, 126*time.Minute, d) 56 | assert.Equal(t, time.Duration(126*time.Minute).String(), a.String()) 57 | } 58 | 59 | func TestDurationUnknown(t *testing.T) { 60 | a := Duration(0xFE) 61 | 62 | _, err := a.Duration() 63 | 64 | assert.Error(t, err) 65 | assert.Equal(t, "Factory default duration", a.String()) 66 | } 67 | -------------------------------------------------------------------------------- /commands/multiChannelEncap_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMultiChannelEncap(t *testing.T) { 10 | r := NewMultiChannelEncap([]byte{0x01, 0x02, 0x03,0x04}, 99) 11 | 12 | e := r.Encode() 13 | 14 | assert.Equal(t, []byte{0x01, 0x02, 0x07, 0x60, 0x0d, 0x01, 0x63, 0x04}, e) 15 | } 16 | -------------------------------------------------------------------------------- /commands/raw.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | // Raw binary data type 4 | type Raw struct { 5 | data []byte 6 | node int 7 | } 8 | 9 | // NewRaw creates a new Raw 10 | func NewRaw(b []byte) *Raw { 11 | return &Raw{data: b} 12 | } 13 | 14 | // Encode make sure we implement the Encodable interface 15 | func (r Raw) Encode() []byte { 16 | 17 | return append([]byte{ 18 | 0x13, // SendData // TODO: use functions.SendData somehow without import cycle 19 | byte(r.node), // Node id 20 | byte(len(r.data) + 1), // Length 21 | }, r.data...) 22 | } 23 | 24 | func (r *Raw) SetNode(n int) { 25 | r.node = n 26 | } 27 | -------------------------------------------------------------------------------- /commands/raw_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRaw(t *testing.T) { 10 | 11 | r := NewRaw([]byte{0x01, 0x02, 0x03,0x04}) 12 | r.SetNode(0x05) 13 | 14 | e := r.Encode() 15 | 16 | assert.Equal(t, []byte{0x13, 0x05, 0x05, 0x01, 0x02, 0x03,0x04}, e) 17 | } 18 | -------------------------------------------------------------------------------- /commands/reports/alarm.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import "fmt" 4 | 5 | //Alarm reports are sent from a node as an event. Is typicaly used for motion sensors and such to notify when motion is detected. 6 | type Alarm struct { 7 | *report 8 | Type byte `json:"type_"` 9 | Level byte `json:"level"` 10 | SensorSourceID byte `json:"sensor_source_id"` 11 | SensorType byte `json:"sensor_type"` 12 | Event byte `json:"event"` 13 | Status byte `json:"status"` 14 | 15 | data []byte 16 | } 17 | 18 | // NewAlarm creates a new alarm report. 19 | func NewAlarm(data []byte) (*Alarm, error) { 20 | a := &Alarm{data: data} 21 | 22 | if len(data) < 2 { 23 | return nil, fmt.Errorf("To short, expected at least 2 byte") 24 | } 25 | 26 | a.Type = data[0] 27 | a.Level = data[1] 28 | 29 | if len(data) >= 6 { 30 | a.SensorSourceID = data[2] 31 | a.Status = data[3] 32 | a.SensorType = data[4] 33 | a.Event = data[5] 34 | } 35 | 36 | // Version 2 37 | if len(data) >= 9 { 38 | 39 | } 40 | return a, nil 41 | } 42 | 43 | func (a *Alarm) String() string { 44 | if len(a.data) >= 6 { 45 | return fmt.Sprintf("alarm type:%x level:%x sensorSrcID:%x status:%x sensorType:%x event:%x", 46 | a.Type, a.Level, a.SensorSourceID, a.Status, a.SensorType, a.Event) 47 | } 48 | return fmt.Sprintf("alarm type:%x level:%x", a.Type, a.Level) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /commands/reports/alarm_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAlarmNoData(t *testing.T) { 10 | w, err := NewAlarm([]byte{}) 11 | 12 | assert.Error(t, err) 13 | assert.IsType(t, w, &Alarm{}) 14 | } 15 | 16 | func TestAlarmShort(t *testing.T) { 17 | w, err := NewAlarm([]byte{0x01, 0x02}) 18 | 19 | assert.NoError(t, err) 20 | assert.IsType(t, w, &Alarm{}) 21 | assert.Equal(t, w.Type, byte(1), w.Type) 22 | assert.Equal(t, w.Level, byte(2), w.Level) 23 | assert.Equal(t, w.String(), "alarm type:1 level:2") 24 | } 25 | 26 | func TestAlarmLong(t *testing.T) { 27 | w, err := NewAlarm([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) 28 | 29 | assert.NoError(t, err) 30 | assert.IsType(t, w, &Alarm{}) 31 | assert.Equal(t, w.Type, byte(1)) 32 | assert.Equal(t, w.Level, byte(2)) 33 | assert.Equal(t, w.String(), "alarm type:1 level:2 sensorSrcID:3 status:4 sensorType:5 event:6") 34 | } 35 | -------------------------------------------------------------------------------- /commands/reports/manufacturer_specific.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | // ManufacturerSpecific reports are used to identify the manufacturer, model and version of a product. 10 | type ManufacturerSpecific struct { 11 | *report 12 | Manufacturer string `json:"manufacturer"` 13 | Type string `json:"type"` 14 | ID string `json:"id"` 15 | } 16 | 17 | // NewManufacturerSpecific decodes and returns a manufacureSpecific report. 18 | func NewManufacturerSpecific(data []byte) (*ManufacturerSpecific, error) { 19 | if len(data) != 6 { 20 | return nil, fmt.Errorf("Failed to decode ManufacturerSpecific: Wrong length") 21 | } 22 | 23 | type aliasCmdManufacturerSpecific struct { 24 | Manufacturer uint16 25 | Type uint16 26 | ID uint16 27 | } 28 | 29 | ms := &aliasCmdManufacturerSpecific{} 30 | 31 | buf := bytes.NewReader(data) 32 | err := binary.Read(buf, binary.BigEndian, ms) 33 | 34 | ret := &ManufacturerSpecific{} 35 | 36 | ret.Manufacturer = fmt.Sprintf("%04x", ms.Manufacturer) 37 | ret.Type = fmt.Sprintf("%04x", ms.Type) 38 | ret.ID = fmt.Sprintf("%04x", ms.ID) 39 | 40 | return ret, err 41 | } 42 | 43 | func (ms *ManufacturerSpecific) String() string { 44 | return fmt.Sprintf("manufacturerSpecific manufacturer:%s type:%s id:%s (%s:%s:%s)", ms.Manufacturer, ms.Type, ms.ID, ms.Manufacturer, ms.Type, ms.ID) 45 | } 46 | -------------------------------------------------------------------------------- /commands/reports/manufacturer_specific_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestManufacturerSpecificNoData(t *testing.T) { 10 | w, err := NewManufacturerSpecific([]byte{}) 11 | 12 | assert.Error(t, err) 13 | assert.IsType(t, w, &ManufacturerSpecific{}) 14 | } 15 | 16 | func TestManufacturerSpecific(t *testing.T) { 17 | w, err := NewManufacturerSpecific([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) 18 | 19 | assert.NoError(t, err) 20 | assert.IsType(t, w, &ManufacturerSpecific{}) 21 | assert.Equal(t, w.Manufacturer, "0102", w.Manufacturer) 22 | assert.Equal(t, w.Type, "0304", w.Type) 23 | assert.Equal(t, w.ID, "0506", w.ID) 24 | assert.Equal(t, "manufacturerSpecific manufacturer:0102 type:0304 id:0506 (0102:0304:0506)", w.String()) 25 | } 26 | -------------------------------------------------------------------------------- /commands/reports/multi_instance_endpoint.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | type MultiChannelEndpoints struct { 10 | *report 11 | Dynamic bool `json:"dynamic"` // When true are the number of endpoints dynamic else its always the same 12 | Identical bool `json:"identical"` // When true are all endpoints identical (same command classes) 13 | Endpoints int `json:"endpoints"` // Number of available endpoints 14 | } 15 | 16 | func NewMultiChannelEndpoints(data []byte) (*MultiChannelEndpoints, error) { 17 | if len(data) < 2 { 18 | return nil, fmt.Errorf("wrong length, got %d expected 2 (data: %x)", len(data), data) 19 | } 20 | 21 | type aliasMultiChannelEndpoints struct { 22 | //Dynamic bool 23 | //Identical bool 24 | //Spare3 bool 25 | //Spare4 bool 26 | //Spare5 bool 27 | //Spare6 bool 28 | //Spare7 bool 29 | //Spare8 bool 30 | Bits byte 31 | Endpoints byte 32 | } 33 | 34 | ms := &aliasMultiChannelEndpoints{} 35 | 36 | buf := bytes.NewReader(data) 37 | err := binary.Read(buf, binary.BigEndian, ms) 38 | 39 | ret := &MultiChannelEndpoints{} 40 | ret.Dynamic = (ms.Bits & 0x80) != 0 41 | ret.Identical = (ms.Bits & 0x40) != 0 42 | ret.Endpoints = int(ms.Endpoints) 43 | 44 | return ret, err 45 | } 46 | 47 | func (ms *MultiChannelEndpoints) String() string { 48 | return fmt.Sprintf("multiChannelEndpoints dynamic:%t identical:%t endpoints:%d", ms.Dynamic, ms.Identical, ms.Endpoints) 49 | } 50 | -------------------------------------------------------------------------------- /commands/reports/multi_instance_endpoint_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestMultiChannelEndpointsNoData(t *testing.T) { 10 | w, err := NewMultiChannelEndpoints([]byte{}) 11 | 12 | assert.Error(t, err) 13 | assert.Nil(t, w) 14 | } 15 | 16 | func TestMultiChannelEndpoints(t *testing.T) { 17 | w, err := NewMultiChannelEndpoints([]byte{0x40, 0x03}) 18 | 19 | assert.NoError(t, err) 20 | assert.IsType(t, w, &MultiChannelEndpoints{}) 21 | assert.Equal(t, w.Dynamic, false) 22 | assert.Equal(t, w.Identical, true) 23 | assert.Equal(t, w.Endpoints, 3) 24 | assert.Equal(t, w.String(), "multiChannelEndpoints dynamic:false identical:true endpoints:3") 25 | } 26 | -------------------------------------------------------------------------------- /commands/reports/report.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/stampzilla/gozwave/commands" 7 | ) 8 | 9 | type Report interface { 10 | SetNode(byte) 11 | String() string 12 | } 13 | 14 | type report struct { 15 | node byte 16 | } 17 | 18 | func (r *report) SetNode(n byte) { 19 | if r == nil { 20 | r = &report{} 21 | //r = nr 22 | } 23 | 24 | r.node = n 25 | } 26 | 27 | // New creates a new report based on command, command class and data 28 | func New(c commands.ZWaveCommand, class byte, data []byte) (Report, error) { 29 | switch c { 30 | case commands.Alarm: 31 | switch class { 32 | case 0x05: // Report 33 | return NewAlarm(data) 34 | } 35 | case commands.ManufacturerSpecific: 36 | switch class { 37 | case 0x05: // Report 38 | return NewManufacturerSpecific(data) 39 | } 40 | case commands.MultiInstance: 41 | switch class { 42 | case 0x08: // MultiChannelCmd_EndPointReport 43 | return NewMultiChannelEndpoints(data) 44 | } 45 | case commands.SensorMultiLevel: 46 | switch class { 47 | case 0x05: // Report 48 | return NewSensorMultiLevel(data) 49 | } 50 | case commands.SwitchBinary: 51 | switch class { 52 | case 0x03: // Report 53 | switch len(data) { 54 | case 1: 55 | return NewSwitchBinaryV1(data) 56 | case 3: 57 | return NewSwitchBinaryV2(data) 58 | } 59 | } 60 | case commands.SwitchMultilevel: 61 | switch class { 62 | case 0x03: // Report 63 | switch len(data) { 64 | case 1: 65 | return NewSwitchMultilevelV1(data) 66 | // V2 reports is same as V1 67 | // V3 reports is depreciated and is not recomended to implement by sigma designs 68 | case 3: 69 | return NewSwitchMultilevelV4(data) 70 | } 71 | } 72 | case commands.WakeUp: 73 | return NewWakeUp() 74 | } 75 | 76 | return nil, fmt.Errorf("Unknown command (%x - %s) / command class (%x)", byte(c), c, class) 77 | } 78 | -------------------------------------------------------------------------------- /commands/reports/report_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stampzilla/gozwave/commands" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNew(t *testing.T) { 11 | type row struct { 12 | data []byte 13 | expected interface{} 14 | } 15 | 16 | m := map[commands.ZWaveCommand]map[byte][]row{ 17 | commands.Alarm: map[byte][]row{ 18 | 0x05: []row{ 19 | row{[]byte{0x00, 0x00}, &Alarm{}}, 20 | }, 21 | }, 22 | commands.ManufacturerSpecific: map[byte][]row{ 23 | 0x05: []row{ 24 | row{[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, &ManufacturerSpecific{}}, 25 | }, 26 | }, 27 | commands.MultiInstance: map[byte][]row{ 28 | 0x08: []row{ 29 | row{[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, &MultiChannelEndpoints{}}, 30 | }, 31 | }, 32 | commands.SensorMultiLevel: map[byte][]row{ 33 | 0x05: []row{ 34 | row{[]byte{0x00, 0x00}, &SensorMultiLevel{}}, 35 | }, 36 | }, 37 | commands.SwitchBinary: map[byte][]row{ 38 | 0x03: []row{ 39 | row{[]byte{0x00}, &SwitchBinaryV1{}}, 40 | row{[]byte{0x00, 0x00, 0x00}, &SwitchBinaryV2{}}, 41 | }, 42 | }, 43 | commands.SwitchMultilevel: map[byte][]row{ 44 | 0x03: []row{ 45 | row{[]byte{0x00}, &SwitchMultilevelV1{}}, 46 | row{[]byte{0x00, 0x00, 0x00}, &SwitchMultilevelV4{}}, 47 | }, 48 | }, 49 | commands.WakeUp: map[byte][]row{ 50 | 0x00: []row{ 51 | row{[]byte{}, &WakeUp{}}, 52 | }, 53 | }, 54 | } 55 | for command, v := range m { 56 | for class, row := range v { 57 | for _, e := range row { 58 | r, err := New(command, class, []byte(e.data)) 59 | 60 | assert.NoError(t, err) 61 | assert.IsType(t, e.expected, r) 62 | } 63 | } 64 | } 65 | } 66 | 67 | func TestNewNoData(t *testing.T) { 68 | r, err := New(commands.ZWaveCommand(0), byte(0), []byte{}) 69 | 70 | assert.Error(t, err) 71 | assert.Nil(t, r) 72 | } 73 | 74 | func TestReport(t *testing.T) { 75 | r, err := New(commands.WakeUp, byte(0), []byte{}) 76 | 77 | r.SetNode(5) 78 | 79 | assert.NoError(t, err) 80 | assert.NotNil(t, r) 81 | } 82 | -------------------------------------------------------------------------------- /commands/reports/sensor_multi_level_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSensorMultiLevelNoData(t *testing.T) { 10 | w, err := NewSensorMultiLevel([]byte{}) 11 | 12 | assert.Error(t, err) 13 | assert.Nil(t, w) 14 | } 15 | 16 | func TestSensorMultiLevelShort(t *testing.T) { 17 | w, err := NewSensorMultiLevel([]byte{0x01, 0x01}) 18 | 19 | assert.Error(t, err) 20 | assert.Nil(t, w) 21 | } 22 | 23 | func TestSensorMultiLevel1(t *testing.T) { 24 | w, err := NewSensorMultiLevel([]byte{0x01, 0x21, 0x01}) 25 | 26 | assert.NoError(t, err) 27 | assert.IsType(t, w, &SensorMultiLevel{}) 28 | } 29 | func TestSensorMultiLevel2(t *testing.T) { 30 | w, err := NewSensorMultiLevel([]byte{0x01, 0x22, 0x00, 0x01}) 31 | 32 | assert.NoError(t, err) 33 | assert.IsType(t, w, &SensorMultiLevel{}) 34 | } 35 | func TestSensorMultiLevel4(t *testing.T) { 36 | w, err := NewSensorMultiLevel([]byte{0x01, 0x24, 0x00, 0x00, 0x00, 0x01}) 37 | 38 | assert.NoError(t, err) 39 | assert.IsType(t, w, &SensorMultiLevel{}) 40 | } 41 | 42 | func TestSensorMultiLevelTypes(t *testing.T) { 43 | d := map[byte]map[byte]string{ 44 | 0x01: map[byte]string{ 45 | 0x00: "Temperature C", 46 | 0x01: "Temperature F", 47 | }, 48 | 0x02: map[byte]string{ 49 | 0x00: "General %", 50 | }, 51 | 0x03: map[byte]string{ 52 | 0x00: "Luminance %", 53 | 0x01: "Luminance lux", 54 | }, 55 | 0x04: map[byte]string{ 56 | 0x00: "Power W", 57 | 0x01: "Power BTU/h", 58 | }, 59 | 0x05: map[byte]string{ 60 | 0x00: "RelativeHumidity %", 61 | }, 62 | 0x06: map[byte]string{ 63 | 0x00: "Velocity m/s", 64 | 0x01: "Velocity mph", 65 | }, 66 | 0x07: map[byte]string{ 67 | 0x00: "Direction ", 68 | }, 69 | 0x08: map[byte]string{ 70 | 0x00: "AtmosphericPressure kPa", 71 | 0x01: "AtmosphericPressure inHg", 72 | }, 73 | 0x09: map[byte]string{ 74 | 0x00: "BarometricPressure kPa", 75 | 0x01: "BarometricPressure inHg", 76 | }, 77 | 0x0A: map[byte]string{ 78 | 0x00: "SolarRadiation W/m2", 79 | }, 80 | 0x0B: map[byte]string{ 81 | 0x00: "DewPoint C", 82 | 0x01: "DewPoint F", 83 | }, 84 | 0x0C: map[byte]string{ 85 | 0x00: "RainRate mm/h", 86 | 0x01: "RainRate in/h", 87 | }, 88 | 0x0D: map[byte]string{ 89 | 0x00: "TideLevel m", 90 | 0x01: "TideLevel ft", 91 | }, 92 | 0x0E: map[byte]string{ 93 | 0x00: "Weight kg", 94 | 0x01: "Weight lb", 95 | }, 96 | 0x0F: map[byte]string{ 97 | 0x00: "Voltage V", 98 | 0x01: "Voltage mV", 99 | }, 100 | 0x10: map[byte]string{ 101 | 0x00: "Current A", 102 | 0x01: "Current mA", 103 | }, 104 | 0x11: map[byte]string{ 105 | 0x00: "CO2 ppm", 106 | }, 107 | 0x12: map[byte]string{ 108 | 0x00: "AirFlow m3/h", 109 | 0x01: "AirFlow cfm", 110 | }, 111 | 0x13: map[byte]string{ 112 | 0x00: "TankCapacity l", 113 | 0x01: "TankCapacity cbm", 114 | 0x02: "TankCapacity gal", 115 | }, 116 | 0x14: map[byte]string{ 117 | 0x00: "Distance m", 118 | 0x01: "Distance cm", 119 | 0x02: "Distance ft", 120 | }, 121 | 0x15: map[byte]string{ 122 | 0x00: "AnglePosition %", 123 | 0x01: "AnglePosition deg N", 124 | 0x02: "AnglePosition deg S", 125 | }, 126 | 0x16: map[byte]string{ 127 | 0x00: "Rotation rpm", 128 | 0x01: "Rotation hz", 129 | }, 130 | 0x17: map[byte]string{ 131 | 0x00: "WaterTemperature C", 132 | 0x01: "WaterTemperature F", 133 | }, 134 | 0x18: map[byte]string{ 135 | 0x00: "SoilTemperature C", 136 | 0x01: "SoilTemperature F", 137 | }, 138 | 0x19: map[byte]string{ 139 | 0x00: "SeismicIntensity mercalli", 140 | 0x01: "SeismicIntensity EU macroseismic", 141 | 0x02: "SeismicIntensity liedu", 142 | 0x03: "SeismicIntensity shindo", 143 | }, 144 | 0x1A: map[byte]string{ 145 | 0x00: "SeismicMagnitude local", 146 | 0x01: "SeismicMagnitude moment", 147 | 0x02: "SeismicMagnitude surface wave", 148 | 0x03: "SeismicMagnitude body wave", 149 | }, 150 | 0x1B: map[byte]string{ 151 | 0x00: "Ultraviolet ", 152 | }, 153 | 0x1C: map[byte]string{ 154 | 0x00: "ElectricalResistivity ohm", 155 | }, 156 | 0x1D: map[byte]string{ 157 | 0x00: "ElectricalConductivity siemens/m", 158 | }, 159 | 0x1E: map[byte]string{ 160 | 0x00: "Loudness db", 161 | 0x01: "Loudness dBA", 162 | }, 163 | 0x1F: map[byte]string{ 164 | 0x00: "Moisture %", 165 | 0x01: "Moisture content", 166 | 0x02: "Moisture k ohm", 167 | 0x03: "Moisture water activity", 168 | }, 169 | 0x20: map[byte]string{ 170 | 0x00: "Unknown (32) ", 171 | }, 172 | } 173 | 174 | for ty, scales := range d { 175 | for scale, expectedTypeString := range scales { 176 | w, err := NewSensorMultiLevel([]byte{ty, 0x04 + (scale << 0x03), 0x00, 0x00, 0x00, 0x01}) 177 | 178 | assert.NoError(t, err) 179 | assert.IsType(t, &SensorMultiLevel{}, w) 180 | assert.Equal(t, "1.000000 "+expectedTypeString, w.String()) 181 | } 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /commands/reports/switch_binary.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/stampzilla/gozwave/commands" 7 | ) 8 | 9 | // SwitchBinaryV1 is sent from a zwave node to advertise the status of a device with On/Off or 10 | // Enable/Disable capability. A known issue is that nodes with multiple endpoints will send 11 | // a SwichBinary report on all endpoint binary state changes but without encapsulating it in a 12 | // MultiChannel message. 13 | type SwitchBinaryV1 struct { 14 | *report 15 | CurrentValue bool `json:"current_value"` // The current state of the node 16 | Valid bool `json:"valid"` // If the state in known or not. Some devices report "unknown" state 17 | 18 | data []byte 19 | } 20 | 21 | // NewSwitchBinaryV1 creates a new SwitchBinary report that contains the switch status from a 22 | // zwave node 23 | func NewSwitchBinaryV1(data []byte) (*SwitchBinaryV1, error) { 24 | if len(data) != 1 { 25 | return nil, fmt.Errorf("Wrong length, expected 1 byte got %d", len(data)) 26 | } 27 | 28 | return &SwitchBinaryV1{ 29 | CurrentValue: data[0] == 0xFF && data[0] != 0xFE, 30 | Valid: data[0] != 0xFE, 31 | data: data, 32 | }, nil 33 | } 34 | 35 | func (sb *SwitchBinaryV1) String() string { 36 | return fmt.Sprintf("current:%t", sb.CurrentValue) 37 | } 38 | 39 | // SwitchBinaryV2 is sent from a zwave node to advertise the status of a device with On/Off or 40 | // Enable/Disable capability. A known issue is that nodes with multiple endpoints will send 41 | // a SwichBinary report on all endpoint state changes but without encapsulating it in a 42 | // MultiChannel message. 43 | type SwitchBinaryV2 struct { 44 | *report 45 | CurrentValue bool `json:"current_value"` // The current state of the node 46 | TargetValue bool `json:"target_value"` // The target state of the node 47 | Duration commands.Duration `json:"duration"` // The transition time between current and target values 48 | 49 | data []byte 50 | } 51 | 52 | // NewSwitchBinaryV2 creates a new switchBinary report that contains the switch status from a 53 | // zwave node 54 | func NewSwitchBinaryV2(data []byte) (*SwitchBinaryV2, error) { 55 | if len(data) != 3 { 56 | return nil, fmt.Errorf("Wrong length, expected 3 byte got %d", len(data)) 57 | } 58 | 59 | return &SwitchBinaryV2{ 60 | CurrentValue: data[0] != 0x00, 61 | TargetValue: data[1] != 0x00, 62 | Duration: commands.Duration(data[2]), 63 | data: data, 64 | }, nil 65 | } 66 | 67 | func (sb *SwitchBinaryV2) String() string { 68 | return fmt.Sprintf("current:%t target:%t duration:%s", sb.CurrentValue, sb.TargetValue, sb.Duration) 69 | } 70 | -------------------------------------------------------------------------------- /commands/reports/switch_binary_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSwitchBinaryV1NoData(t *testing.T) { 11 | w, err := NewSwitchBinaryV1([]byte{}) 12 | 13 | assert.Error(t, err) 14 | assert.Nil(t, w) 15 | } 16 | 17 | func TestSwitchBinaryV1False(t *testing.T) { 18 | w, err := NewSwitchBinaryV1([]byte{0x00}) 19 | 20 | assert.NoError(t, err) 21 | assert.IsType(t, w, &SwitchBinaryV1{}) 22 | assert.Equal(t, w.CurrentValue, false) 23 | assert.Equal(t, w.String(), "current:false") 24 | } 25 | 26 | func TestSwitchBinaryV1True(t *testing.T) { 27 | w, err := NewSwitchBinaryV1([]byte{0xFF}) 28 | 29 | assert.NoError(t, err) 30 | assert.IsType(t, w, &SwitchBinaryV1{}) 31 | assert.Equal(t, w.CurrentValue, true) 32 | assert.Equal(t, w.String(), "current:true") 33 | } 34 | 35 | func TestSwitchBinaryV2NoData(t *testing.T) { 36 | w, err := NewSwitchBinaryV2([]byte{}) 37 | 38 | assert.Error(t, err) 39 | assert.Nil(t, w) 40 | } 41 | 42 | func TestSwitchBinaryV2False(t *testing.T) { 43 | w, err := NewSwitchBinaryV2([]byte{0x00, 0xFF, 0x00}) 44 | 45 | assert.NoError(t, err) 46 | assert.IsType(t, w, &SwitchBinaryV2{}) 47 | assert.Equal(t, w.CurrentValue, false) 48 | assert.Equal(t, w.TargetValue, true) 49 | assert.Equal(t, w.String(), "current:false target:true duration:"+time.Duration(0).String()) 50 | 51 | d, err := w.Duration.Duration() 52 | assert.NoError(t, err) 53 | assert.Equal(t, d, time.Duration(0)) 54 | } 55 | 56 | func TestSwitchBinaryV2True(t *testing.T) { 57 | w, err := NewSwitchBinaryV2([]byte{0xFF, 0x00, 0x01}) 58 | 59 | assert.NoError(t, err) 60 | assert.IsType(t, w, &SwitchBinaryV2{}) 61 | assert.Equal(t, w.CurrentValue, true) 62 | assert.Equal(t, w.TargetValue, false) 63 | assert.Equal(t, w.String(), "current:true target:false duration:1s") 64 | 65 | d, err := w.Duration.Duration() 66 | assert.NoError(t, err) 67 | assert.Equal(t, d, time.Second) 68 | } 69 | -------------------------------------------------------------------------------- /commands/reports/switch_multilevel.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/stampzilla/gozwave/commands" 8 | ) 9 | 10 | // SwitchMultilevelV1 is sent by a zwave node to advertise the status of a multilevel device. 11 | // A known issue is that nodes with multiple endpoints will send a SwichMultilevel report on 12 | // all endpoint multilevel state changes but without encapsulating it in a MultiChannel message. 13 | type SwitchMultilevelV1 struct { 14 | *report 15 | CurrentValue float64 `json:"current_value"` // The current value state 16 | Valid bool `json:"valid"` // If the state in known or not. Some devices report "unknown" state 17 | data []byte 18 | } 19 | 20 | // NewSwitchMultilevelV1 creates a new SwitchMultilevelV1 report from binary data sent from a 21 | // zwave node 22 | func NewSwitchMultilevelV1(data []byte) (*SwitchMultilevelV1, error) { 23 | if len(data) != 1 { 24 | return nil, fmt.Errorf("Wrong length, expected 1 byte got %d", len(data)) 25 | } 26 | 27 | return &SwitchMultilevelV1{ 28 | CurrentValue: math.Min(float64(data[0]), 100), 29 | Valid: data[0] != 0xFE, 30 | data: data, 31 | }, nil 32 | } 33 | 34 | func (sm SwitchMultilevelV1) String() string { 35 | if !sm.Valid { 36 | return fmt.Sprintf("current:unknown") 37 | } 38 | return fmt.Sprintf("current:%f", sm.CurrentValue) 39 | } 40 | 41 | // SwitchMultilevelV4 is sent by a zwave node to advertise the status of a multilevel device. 42 | // A known issue is that nodes with multiple endpoints will send a SwichMultilevel report on 43 | // all endpoint multilevel state changes but without encapsulating it in a MultiChannel message. 44 | type SwitchMultilevelV4 struct { 45 | *report 46 | CurrentValue float64 `json:"current_value"` // The current state of the node 47 | TargetValue float64 `json:"target_value"` // The target state of the node 48 | Duration commands.Duration `json:"duration"` // The transition time between current and target values 49 | Valid bool `json:"valid"` // If the current state in known or not. Some devices report "unknown" state 50 | TargetValid bool `json:"target_valid"` // If the target state in known or not. Some devices report "unknown" state 51 | data []byte 52 | } 53 | 54 | // NewSwitchMultilevelV4 creates a new SwitchMultilevelV1 report from binary data sent from a 55 | // zwave node 56 | func NewSwitchMultilevelV4(data []byte) (*SwitchMultilevelV4, error) { 57 | if len(data) != 3 { 58 | return nil, fmt.Errorf("Wrong length, expected 3 byte got %d", len(data)) 59 | } 60 | 61 | return &SwitchMultilevelV4{ 62 | CurrentValue: math.Min(float64(data[0]), 100), 63 | TargetValue: math.Min(float64(data[1]), 100), 64 | Duration: commands.Duration(data[2]), 65 | Valid: data[0] != 0xFE, 66 | TargetValid: data[1] != 0xFE, 67 | data: data, 68 | }, nil 69 | } 70 | 71 | func (sm SwitchMultilevelV4) String() string { 72 | ret := fmt.Sprintf("current:%f ", sm.CurrentValue) 73 | if !sm.Valid { 74 | ret = fmt.Sprintf("current:unknown ") 75 | } 76 | 77 | if !sm.TargetValid { 78 | return ret + fmt.Sprintf("target:unknown duration:%s", sm.Duration) 79 | } 80 | return ret + fmt.Sprintf("target:%f duration:%s", sm.TargetValue, sm.Duration) 81 | } 82 | -------------------------------------------------------------------------------- /commands/reports/switch_multilevel_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSwitchMultilevelNoData(t *testing.T) { 11 | w, err := NewSwitchMultilevelV1([]byte{}) 12 | 13 | assert.Error(t, err) 14 | assert.Nil(t, w) 15 | } 16 | 17 | func TestSwitchMultilevel0(t *testing.T) { 18 | w, err := NewSwitchMultilevelV1([]byte{0x00}) 19 | 20 | assert.NoError(t, err) 21 | assert.IsType(t, w, &SwitchMultilevelV1{}) 22 | assert.Equal(t, w.CurrentValue, 0.0) 23 | assert.Equal(t, w.Valid, true) 24 | assert.Equal(t, w.String(), "current:0.000000") 25 | } 26 | 27 | func TestSwitchMultilevel100(t *testing.T) { 28 | w, err := NewSwitchMultilevelV1([]byte{100}) 29 | 30 | assert.NoError(t, err) 31 | assert.IsType(t, w, &SwitchMultilevelV1{}) 32 | assert.Equal(t, w.CurrentValue, 100.0) 33 | assert.Equal(t, w.Valid, true) 34 | assert.Equal(t, w.String(), "current:100.000000") 35 | } 36 | 37 | func TestSwitchMultilevelUnknown(t *testing.T) { 38 | w, err := NewSwitchMultilevelV1([]byte{0xFE}) 39 | 40 | assert.NoError(t, err) 41 | assert.IsType(t, w, &SwitchMultilevelV1{}) 42 | assert.Equal(t, w.CurrentValue, 100.0) 43 | assert.Equal(t, w.Valid, false) 44 | assert.Equal(t, w.String(), "current:unknown") 45 | } 46 | 47 | func TestSwitchMultilevelV4NoData(t *testing.T) { 48 | w, err := NewSwitchMultilevelV4([]byte{}) 49 | 50 | assert.Error(t, err) 51 | assert.Nil(t, w) 52 | } 53 | 54 | func TestSwitchMultilevelV40(t *testing.T) { 55 | w, err := NewSwitchMultilevelV4([]byte{0x00, 0xFF, 0x00}) 56 | 57 | assert.NoError(t, err) 58 | assert.IsType(t, w, &SwitchMultilevelV4{}) 59 | assert.Equal(t, w.CurrentValue, 0.0) 60 | assert.Equal(t, w.Valid, true) 61 | assert.Equal(t, w.String(), "current:0.000000 target:100.000000 duration:"+time.Duration(0).String()) 62 | } 63 | 64 | func TestSwitchMultilevelV4100(t *testing.T) { 65 | w, err := NewSwitchMultilevelV4([]byte{0xFF, 0x00, 0x01}) 66 | 67 | assert.NoError(t, err) 68 | assert.IsType(t, w, &SwitchMultilevelV4{}) 69 | assert.Equal(t, w.CurrentValue, 100.0) 70 | assert.Equal(t, w.TargetValue, 0.0) 71 | assert.Equal(t, w.Valid, true) 72 | assert.Equal(t, w.TargetValid, true) 73 | assert.Equal(t, w.String(), "current:100.000000 target:0.000000 duration:1s") 74 | } 75 | 76 | func TestSwitchMultilevelV4Unknown(t *testing.T) { 77 | w, err := NewSwitchMultilevelV4([]byte{0xFE, 0x00, 0x01}) 78 | 79 | assert.NoError(t, err) 80 | assert.IsType(t, w, &SwitchMultilevelV4{}) 81 | assert.Equal(t, w.CurrentValue, 100.0) 82 | assert.Equal(t, w.TargetValue, 0.0) 83 | assert.Equal(t, w.Valid, false) 84 | assert.Equal(t, w.TargetValid, true) 85 | assert.Equal(t, w.String(), "current:unknown target:0.000000 duration:1s") 86 | } 87 | 88 | func TestSwitchMultilevelV4UnknownTarget(t *testing.T) { 89 | w, err := NewSwitchMultilevelV4([]byte{0x00, 0xFE, 0x01}) 90 | 91 | assert.NoError(t, err) 92 | assert.IsType(t, w, &SwitchMultilevelV4{}) 93 | assert.Equal(t, w.CurrentValue, 0.0) 94 | assert.Equal(t, w.TargetValue, 100.0) 95 | assert.Equal(t, w.Valid, true) 96 | assert.Equal(t, w.TargetValid, false) 97 | assert.Equal(t, w.String(), "current:0.000000 target:unknown duration:1s") 98 | } 99 | -------------------------------------------------------------------------------- /commands/reports/wakeup.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import "fmt" 4 | 5 | // WakeUp report. Zwave nodes that are battery powerd sends a wakeUp report when they wake up and listens for messages from the controller. This usualy happens something like once every hour and the node is typicaly awake for 1 second. 6 | type WakeUp struct { 7 | *report 8 | } 9 | 10 | // NewWakeUp creates a new wakeUp report. 11 | func NewWakeUp() (*WakeUp, error) { 12 | return &WakeUp{}, nil 13 | } 14 | 15 | func (wr *WakeUp) String() string { 16 | return fmt.Sprintf("wakeup") 17 | } 18 | -------------------------------------------------------------------------------- /commands/reports/wakeup_test.go: -------------------------------------------------------------------------------- 1 | package reports 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestWakeUp(t *testing.T) { 11 | w, err := NewWakeUp() 12 | 13 | assert.NoError(t, err) 14 | assert.IsType(t, &WakeUp{}, w) 15 | assert.Implements(t, (*fmt.Stringer)(nil), w) 16 | assert.Equal(t, w.String(), "wakeup") 17 | } 18 | -------------------------------------------------------------------------------- /commands/switchBinary_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSwitchBinaryTrue(t *testing.T) { 10 | r := NewSwitchBinary() 11 | r.SetValue(true) 12 | r.SetNode(99) 13 | 14 | e := r.Encode() 15 | 16 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x25, 0x01, 0xff, 0x25}, e) 17 | } 18 | 19 | func TestSwitchBinaryFalse(t *testing.T) { 20 | r := NewSwitchBinary() 21 | r.SetValue(false) 22 | r.SetNode(99) 23 | 24 | e := r.Encode() 25 | 26 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x25, 0x01, 0x00, 0x25}, e) 27 | } 28 | -------------------------------------------------------------------------------- /commands/switchMultilevel_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSwitchMultilevel0(t *testing.T) { 10 | r := NewSwitchMultilevel() 11 | r.SetValue(0) 12 | r.SetNode(99) 13 | 14 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x26, 0x01, 0x00, 0x25}, r.Encode()) 15 | } 16 | 17 | func TestSwitchMultilevel1(t *testing.T) { 18 | r := NewSwitchMultilevel() 19 | r.SetValue(1) 20 | r.SetNode(99) 21 | 22 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x26, 0x01, 0x01, 0x25}, r.Encode()) 23 | } 24 | 25 | func TestSwitchMultilevel99(t *testing.T) { 26 | r := NewSwitchMultilevel() 27 | r.SetValue(99) 28 | r.SetNode(99) 29 | 30 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x26, 0x01, 0x63, 0x25}, r.Encode()) 31 | } 32 | 33 | func TestSwitchMultilevel100(t *testing.T) { 34 | r := NewSwitchMultilevel() 35 | r.SetValue(100) 36 | r.SetNode(99) 37 | 38 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x26, 0x01, 0x63, 0x25}, r.Encode()) 39 | } 40 | 41 | func TestSwitchMultilevel255(t *testing.T) { 42 | r := NewSwitchMultilevel() 43 | r.SetValue(255) 44 | r.SetNode(99) 45 | 46 | assert.Equal(t, []byte{0x13, 0x63, 0x03, 0x26, 0x01, 0x63, 0x25}, r.Encode()) 47 | } 48 | -------------------------------------------------------------------------------- /connection_test.go: -------------------------------------------------------------------------------- 1 | package gozwave 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stampzilla/gozwave/serialapi" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSendPackageMatch(t *testing.T) { 11 | s := newSendPackage([]byte{serialapi.SendData, 0x02, 0x03, 0x04, 0x05}) 12 | 13 | assert.Equal(t, byte(serialapi.SendData), s.function, "Function") 14 | assert.Equal(t, byte(0x02), s.node, "Node") 15 | assert.Equal(t, byte(0x04), s.commandclass, "CommandClass") 16 | assert.Equal(t, byte(0x00), s.expectedReport, "ExpectedReport") 17 | 18 | assert.Equal(t, false, s.Match([]byte{0x00, 0x00, 0x00, 0xff, 0x00, 0x02, 0x04, 0x05, 0x06, 0x00}), "Function") 19 | assert.Equal(t, false, s.Match([]byte{0x00, 0x00, 0x00, byte(serialapi.ApplicationCommandHandler), 0x00, 0xff, 0x00, 0x04, 0x06, 0x00}), "Node") 20 | assert.Equal(t, false, s.Match([]byte{0x00, 0x00, 0x00, byte(serialapi.ApplicationCommandHandler), 0x00, 0x02, 0x00, 0xff, 0x06, 0x00}), "Command") 21 | assert.Equal(t, true, s.Match([]byte{0x00, 0x00, 0x00, byte(serialapi.ApplicationCommandHandler), 0x00, 0x02, 0x00, 0x04, 0x06})) 22 | 23 | s.expectedReport = byte(0x06) 24 | assert.Equal(t, true, s.Match([]byte{0x00, 0x00, 0x00, byte(serialapi.ApplicationCommandHandler), 0x00, 0x02, 0x00, 0x04, 0x06})) 25 | assert.Equal(t, false, s.Match([]byte{0x00, 0x00, 0x00, byte(serialapi.ApplicationCommandHandler), 0x00, 0x02, 0x00, 0x04, 0xff}), "Expected report") 26 | } 27 | -------------------------------------------------------------------------------- /controllable.go: -------------------------------------------------------------------------------- 1 | package gozwave 2 | 3 | // Controllable interface used by nodes and endpoints to controll them 4 | type Controllable interface { 5 | On() 6 | Off() 7 | Level(float64) 8 | } 9 | -------------------------------------------------------------------------------- /controller.go: -------------------------------------------------------------------------------- 1 | package gozwave 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "reflect" 9 | "sync" 10 | "time" 11 | 12 | "github.com/sirupsen/logrus" 13 | "github.com/stampzilla/gozwave/commands/reports" 14 | "github.com/stampzilla/gozwave/events" 15 | "github.com/stampzilla/gozwave/nodes" 16 | "github.com/stampzilla/gozwave/serialapi" 17 | ) 18 | 19 | type Controller struct { 20 | Nodes *nodes.List 21 | Connection *Connection `json:"-"` 22 | eventQue chan interface{} 23 | 24 | filename string 25 | triggerFileSave chan struct{} 26 | 27 | sync.RWMutex 28 | } 29 | 30 | func NewController() *Controller { 31 | c := &Controller{ 32 | Nodes: nodes.NewList(), 33 | Connection: NewConnection(), 34 | eventQue: make(chan interface{}, 10), 35 | triggerFileSave: make(chan struct{}), 36 | } 37 | return c 38 | } 39 | 40 | func (c *Controller) getNodes() (*nodes.List, error) { 41 | t, err := c.Connection.WriteWithTimeout(serialapi.NewRaw([]byte{0x02}), time.Second*5) 42 | 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | resp := <-t 48 | 49 | if resp == nil { 50 | return nil, fmt.Errorf("Timeout or invalid response") 51 | } 52 | 53 | switch r := resp.Data.(type) { 54 | case *serialapi.DiscoverdNodes: 55 | for index, active := range r.ActiveNodes { 56 | if !active { 57 | continue 58 | } 59 | 60 | //skip first node (controller) 61 | if index == 0 { 62 | continue 63 | } 64 | 65 | node := c.Nodes.Get(index + 1) 66 | node = c.initNode(index+1, node) 67 | go node.Identify() 68 | } 69 | default: 70 | logrus.Errorf("Got wrong response type: %t", r) 71 | } 72 | 73 | return c.Nodes, nil 74 | } 75 | 76 | func (c *Controller) initNode(id int, node *nodes.Node) *nodes.Node { 77 | if node != nil { 78 | return node 79 | } 80 | 81 | node = nodes.New(id) 82 | c.Nodes.Add(node) 83 | c.pushEvent(events.NodeDiscoverd{ 84 | Address: node.Id, 85 | }) 86 | 87 | node.Setup(c.Connection, c.pushEvent) 88 | return node 89 | } 90 | 91 | func (c *Controller) pushEvent(event interface{}) { 92 | select { 93 | case c.eventQue <- event: 94 | default: 95 | } 96 | 97 | go func() { 98 | switch event.(type) { 99 | case events.NodeUpdated: 100 | c.triggerFileSave <- struct{}{} 101 | } 102 | }() 103 | } 104 | 105 | func (c *Controller) saveDebouncer() { 106 | <-c.triggerFileSave 107 | for { 108 | select { 109 | case <-c.triggerFileSave: 110 | case <-time.After(time.Second * 10): 111 | if c.filename != "" { 112 | c.SaveConfigurationToFile() 113 | } 114 | <-c.triggerFileSave 115 | } 116 | } 117 | } 118 | 119 | func (c *Controller) GetNextEvent() chan interface{} { 120 | c.RLock() 121 | defer c.RUnlock() 122 | 123 | return c.eventQue 124 | } 125 | 126 | func (c *Controller) DeliverReportToNode(node byte, report reports.Report) { 127 | n := c.Nodes.Get(int(node)) 128 | if n == nil { 129 | return 130 | } 131 | n.ProcessEvent(report) 132 | } 133 | 134 | func (c *Controller) SaveConfigurationToFile() error { 135 | controller, err := c.loadConfigurationFromFile() 136 | if err == nil { 137 | if reflect.DeepEqual(c, controller) { 138 | return nil 139 | } 140 | } 141 | 142 | configFile, err := os.Create(c.filename) 143 | if err != nil { 144 | logrus.Error("creating config file", err.Error()) 145 | } 146 | defer configFile.Close() 147 | 148 | logrus.Info("Save config: ", c.filename) 149 | var out bytes.Buffer 150 | b, err := json.MarshalIndent(c, "", "\t") 151 | if err != nil { 152 | logrus.Error("error marshal json", err) 153 | } 154 | json.Indent(&out, b, "", "\t") 155 | out.WriteTo(configFile) 156 | 157 | return nil 158 | } 159 | func (c *Controller) LoadConfigurationFromFile() error { 160 | controller, err := c.loadConfigurationFromFile() 161 | if err != nil { 162 | return err 163 | } 164 | 165 | for _, v := range controller.Nodes.All() { 166 | v.Setup(c.Connection, c.pushEvent) 167 | c.Nodes.Add(v) 168 | c.pushEvent(events.NodeDiscoverd{ 169 | Address: v.Id, 170 | }) 171 | } 172 | 173 | return nil 174 | } 175 | func (c *Controller) loadConfigurationFromFile() (*Controller, error) { 176 | configFile, err := os.Open(c.filename) 177 | if err != nil { 178 | return nil, err 179 | } 180 | defer configFile.Close() 181 | 182 | controller := &Controller{} 183 | jsonParser := json.NewDecoder(configFile) 184 | if err = jsonParser.Decode(&controller); err != nil { 185 | return nil, err 186 | } 187 | 188 | return controller, nil 189 | } 190 | -------------------------------------------------------------------------------- /database/CREDITS: -------------------------------------------------------------------------------- 1 | Credits to http://www.cd-jackson.com/index.php/zwave/zwave-device-database 2 | -------------------------------------------------------------------------------- /database/commandclasses.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | // GetMandatoryCommandClasses generates a list of mandatory commandclasses 4 | func GetMandatoryCommandClasses(generic, specific byte) []*CommandClass { 5 | for k, v := range definitions { 6 | if k.Generic == generic && k.Specific == specific { 7 | return v 8 | } 9 | } 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /database/json/act_zdp200_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 385, 3 | "label": "ZDP200", 4 | "manufacturer_name": "ACT - Advanced Control Technologies", 5 | "manufacturer_id": "0001", 6 | "device_ref": [ 7 | "4450:3030" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "HomePro ZDP200 Wall Dimmer", 12 | "overview": "", 13 | "inclusion": "
Use the button on the device.<\/p>", 14 | "exclusion": "
Use Habmin or another zwave tool to exclude the device from the zwave mesh.<\/p>", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 17, 46 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "26", 77 | "name": "SWITCH_MULTILEVEL", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_dimmer", 87 | "label": "Dimmer Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "75", 105 | "name": "PROTECTION", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | } 114 | ] 115 | } 116 | ], 117 | "parameters": [], 118 | "associations": [ 119 | { 120 | "id": 1, 121 | "label": "Dimmer Switch", 122 | "max_nodes": 16, 123 | "controller": true, 124 | "description": "", 125 | "overview": "" 126 | } 127 | ] 128 | } -------------------------------------------------------------------------------- /database/json/act_zrp200_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 360, 3 | "label": "ZRP200", 4 | "manufacturer_name": "ACT - Advanced Control Technologies", 5 | "manufacturer_id": "0001", 6 | "device_ref": [ 7 | "7FFF:7FFF" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "HomePro Applicance Module ZRP200", 12 | "overview": "
This is a very old appliance module, which doesn't report the manufacturer information. You'll have to edit the node.xml file(s) yourself. Change the manufacturer from 0x7fffffff to 0x1, to set it to manufacturer ACT. Also, change the deviceType to 7fff and the deviceid to 7fff.<\/p>", 13 | "inclusion": "
Use the button on the device.<\/p>", 14 | "exclusion": "
Use Habmin or another zwave tool to exclude the device from the zwave mesh.<\/p>", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": "SPECIFIC_TYPE_NOT_USED" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "75", 105 | "name": "PROTECTION", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | } 114 | ] 115 | } 116 | ], 117 | "parameters": [], 118 | "associations": [] 119 | } -------------------------------------------------------------------------------- /database/json/aeon_dsd37_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 66, 3 | "label": "DSD37", 4 | "manufacturer_name": "AEON Labs", 5 | "manufacturer_id": "0086", 6 | "device_ref": [ 7 | "0004:0025", 8 | "0019:0004" 9 | ], 10 | "version_min": 0, 11 | "version_max": 255.255, 12 | "decription": "Range Extender", 13 | "overview": "", 14 | "inclusion": "", 15 | "exclusion": "", 16 | "wakeup": "", 17 | "protocol_version": 3.067, 18 | "listening": true, 19 | "frequently_listening": false, 20 | "routing": true, 21 | "beaming": true, 22 | "library_type": { 23 | "id": 7, 24 | "name": "LIB_CONTROLLER_BRIDGE" 25 | }, 26 | "basic_class": { 27 | "id": 4, 28 | "name": "BASIC_TYPE_ROUTING_SLAVE" 29 | }, 30 | "generic_class": { 31 | "id": 4, 32 | "name": "GENERIC_TYPE_REPEATER_SLAVE" 33 | }, 34 | "specific_class": { 35 | "id": 4, 36 | "name": "SPECIFIC_TYPE_REPEATER_SLAVE" 37 | }, 38 | "endpoints": [ 39 | { 40 | "id": 0, 41 | "basic_class": { 42 | "id": 4, 43 | "name": "BASIC_TYPE_ROUTING_SLAVE" 44 | }, 45 | "generic_class": { 46 | "id": 15, 47 | "name": "GENERIC_TYPE_REPEATER_SLAVE" 48 | }, 49 | "specific_class": { 50 | "id": 1, 51 | "name": "SPECIFIC_TYPE_REPEATER_SLAVE" 52 | }, 53 | "commandclasses": [ 54 | { 55 | "id": "00", 56 | "name": "NO_OPERATION", 57 | "version": "1", 58 | "nif": false, 59 | "basic": false, 60 | "secure": false, 61 | "nonsecure": true, 62 | "config": "", 63 | "channels": [] 64 | }, 65 | { 66 | "id": "20", 67 | "name": "BASIC", 68 | "version": "1", 69 | "nif": true, 70 | "basic": false, 71 | "secure": false, 72 | "nonsecure": true, 73 | "config": "", 74 | "channels": [] 75 | }, 76 | { 77 | "id": "70", 78 | "name": "CONFIGURATION", 79 | "version": "1", 80 | "nif": true, 81 | "basic": false, 82 | "secure": false, 83 | "nonsecure": true, 84 | "config": "", 85 | "channels": [] 86 | }, 87 | { 88 | "id": "72", 89 | "name": "MANUFACTURER_SPECIFIC", 90 | "version": "1", 91 | "nif": true, 92 | "basic": false, 93 | "secure": false, 94 | "nonsecure": true, 95 | "config": "", 96 | "channels": [] 97 | }, 98 | { 99 | "id": "73", 100 | "name": "POWERLEVEL", 101 | "version": "1", 102 | "nif": true, 103 | "basic": false, 104 | "secure": false, 105 | "nonsecure": true, 106 | "config": "", 107 | "channels": [] 108 | }, 109 | { 110 | "id": "85", 111 | "name": "ASSOCIATION", 112 | "version": "1", 113 | "nif": true, 114 | "basic": false, 115 | "secure": false, 116 | "nonsecure": true, 117 | "config": "", 118 | "channels": [] 119 | }, 120 | { 121 | "id": "86", 122 | "name": "VERSION", 123 | "version": "1", 124 | "nif": true, 125 | "basic": false, 126 | "secure": false, 127 | "nonsecure": true, 128 | "config": "", 129 | "channels": [] 130 | } 131 | ] 132 | } 133 | ], 134 | "parameters": [], 135 | "associations": [ 136 | { 137 | "id": 1, 138 | "label": "Group 1", 139 | "max_nodes": 5, 140 | "controller": false, 141 | "description": "", 142 | "overview": "" 143 | } 144 | ] 145 | } -------------------------------------------------------------------------------- /database/json/amc_zdsud10_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 184, 3 | "label": "ZDS-UD10", 4 | "manufacturer_name": "Airline Mechanical Co., Ltd.", 5 | "manufacturer_id": "0111", 6 | "device_ref": [ 7 | "8200:0200" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Dimming Switch Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.078, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 4, 23 | "name": "LIB_SLAVE" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": "SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 17, 46 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": true, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": true, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "26", 77 | "name": "SWITCH_MULTILEVEL", 78 | "version": "2", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_dimmer", 87 | "label": "Dimmer", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "73", 116 | "name": "POWERLEVEL", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | }, 125 | { 126 | "id": "86", 127 | "name": "VERSION", 128 | "version": "1", 129 | "nif": true, 130 | "basic": false, 131 | "secure": false, 132 | "nonsecure": true, 133 | "config": "", 134 | "channels": [] 135 | } 136 | ] 137 | } 138 | ], 139 | "parameters": [], 140 | "associations": [] 141 | } -------------------------------------------------------------------------------- /database/json/cooper_rf9500_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 14, 3 | "label": "RF9500", 4 | "manufacturer_name": "Cooper Wiring Devices", 5 | "manufacturer_id": "001A", 6 | "device_ref": [ 7 | "5342:0000" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Battery Switch", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.024, 17 | "listening": false, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 3, 23 | "name": "LIB_SLAVE_ENHANCED" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_REMOTE" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 18, 46 | "name": "GENERIC_TYPE_SWITCH_REMOTE" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [ 74 | { 75 | "type": "switch_dimmer", 76 | "label": "Brightness", 77 | "config": [] 78 | } 79 | ] 80 | }, 81 | { 82 | "id": "72", 83 | "name": "MANUFACTURER_SPECIFIC", 84 | "version": "1", 85 | "nif": false, 86 | "basic": false, 87 | "secure": false, 88 | "nonsecure": true, 89 | "config": "", 90 | "channels": [] 91 | }, 92 | { 93 | "id": "77", 94 | "name": "NODE_NAMING", 95 | "version": "1", 96 | "nif": false, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "85", 105 | "name": "ASSOCIATION", 106 | "version": "1", 107 | "nif": false, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": false, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [ 130 | { 131 | "id": 1, 132 | "label": "Group 1", 133 | "max_nodes": 5, 134 | "controller": true, 135 | "description": "", 136 | "overview": "" 137 | } 138 | ] 139 | } -------------------------------------------------------------------------------- /database/json/econet_ev100_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 564, 3 | "label": "EV100", 4 | "manufacturer_name": "EcoNet Controls", 5 | "manufacturer_id": "0157", 6 | "device_ref": [ 7 | "0100:0100" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Z-Vent ZWave Controlled HVAC Air Register", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 3.067, 17 | "listening": false, 18 | "frequently_listening": true, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 6, 23 | "name": "LIB_SLAVE_ROUTING" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 17, 46 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": true, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "26", 77 | "name": "SWITCH_MULTILEVEL", 78 | "version": "3", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_dimmer", 87 | "label": "Dimmer", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "72", 94 | "name": "MANUFACTURER_SPECIFIC", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "77", 105 | "name": "NODE_NAMING", 106 | "version": "0", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "80", 116 | "name": "BATTERY", 117 | "version": "0", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | }, 125 | { 126 | "id": "86", 127 | "name": "VERSION", 128 | "version": "1", 129 | "nif": true, 130 | "basic": false, 131 | "secure": false, 132 | "nonsecure": true, 133 | "config": "", 134 | "channels": [] 135 | } 136 | ] 137 | } 138 | ], 139 | "parameters": [], 140 | "associations": [] 141 | } -------------------------------------------------------------------------------- /database/json/enerwave_zwn333_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 395, 3 | "label": "ZWN-333", 4 | "manufacturer_name": "Wenzhou MTLC Electric Appliances Co.,Ltd.", 5 | "manufacturer_id": "011A", 6 | "device_ref": [ 7 | "0101:0104" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Plug-in Appliance Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 3.042, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 7, 23 | "name": "LIB_CONTROLLER_BRIDGE" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [] 130 | } -------------------------------------------------------------------------------- /database/json/everspring_an157_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 26, 3 | "label": "AN157", 4 | "manufacturer_name": "Everspring", 5 | "manufacturer_id": "0060", 6 | "device_ref": [ 7 | "0004:0001", 8 | "0104:0001" 9 | ], 10 | "version_min": 0, 11 | "version_max": 255.255, 12 | "decription": "Switch Plugin", 13 | "overview": "", 14 | "inclusion": "", 15 | "exclusion": "", 16 | "wakeup": "", 17 | "protocol_version": 2.064, 18 | "listening": true, 19 | "frequently_listening": false, 20 | "routing": true, 21 | "beaming": true, 22 | "library_type": { 23 | "id": 4, 24 | "name": "LIB_SLAVE" 25 | }, 26 | "basic_class": { 27 | "id": 3, 28 | "name": "BASIC_TYPE_SLAVE" 29 | }, 30 | "generic_class": { 31 | "id": 3, 32 | "name": "GENERIC_TYPE_SWITCH_BINARY" 33 | }, 34 | "specific_class": { 35 | "id": 3, 36 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 37 | }, 38 | "endpoints": [ 39 | { 40 | "id": 0, 41 | "basic_class": { 42 | "id": 3, 43 | "name": "BASIC_TYPE_SLAVE" 44 | }, 45 | "generic_class": { 46 | "id": 16, 47 | "name": "GENERIC_TYPE_SWITCH_BINARY" 48 | }, 49 | "specific_class": { 50 | "id": 1, 51 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 52 | }, 53 | "commandclasses": [ 54 | { 55 | "id": "20", 56 | "name": "BASIC", 57 | "version": "1", 58 | "nif": false, 59 | "basic": false, 60 | "secure": false, 61 | "nonsecure": true, 62 | "config": "", 63 | "channels": [] 64 | }, 65 | { 66 | "id": "25", 67 | "name": "SWITCH_BINARY", 68 | "version": "1", 69 | "nif": true, 70 | "basic": true, 71 | "secure": false, 72 | "nonsecure": true, 73 | "config": "", 74 | "channels": [ 75 | { 76 | "type": "switch_binary", 77 | "label": "Switch", 78 | "config": [] 79 | } 80 | ] 81 | }, 82 | { 83 | "id": "27", 84 | "name": "SWITCH_ALL", 85 | "version": "1", 86 | "nif": true, 87 | "basic": false, 88 | "secure": false, 89 | "nonsecure": true, 90 | "config": "", 91 | "channels": [] 92 | }, 93 | { 94 | "id": "72", 95 | "name": "MANUFACTURER_SPECIFIC", 96 | "version": "1", 97 | "nif": true, 98 | "basic": false, 99 | "secure": false, 100 | "nonsecure": true, 101 | "config": "", 102 | "channels": [] 103 | }, 104 | { 105 | "id": "73", 106 | "name": "POWERLEVEL", 107 | "version": "1", 108 | "nif": true, 109 | "basic": false, 110 | "secure": false, 111 | "nonsecure": true, 112 | "config": "", 113 | "channels": [] 114 | }, 115 | { 116 | "id": "75", 117 | "name": "PROTECTION", 118 | "version": "1", 119 | "nif": true, 120 | "basic": false, 121 | "secure": false, 122 | "nonsecure": true, 123 | "config": "", 124 | "channels": [] 125 | }, 126 | { 127 | "id": "86", 128 | "name": "VERSION", 129 | "version": "1", 130 | "nif": true, 131 | "basic": false, 132 | "secure": false, 133 | "nonsecure": true, 134 | "config": "", 135 | "channels": [] 136 | } 137 | ] 138 | } 139 | ], 140 | "parameters": [], 141 | "associations": [] 142 | } -------------------------------------------------------------------------------- /database/json/horstmann_asrzw_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 310, 3 | "label": "ASR-ZW", 4 | "manufacturer_name": "Horstmann Controls Limited", 5 | "manufacturer_id": "0059", 6 | "device_ref": [ 7 | "0003:0001" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Thermostat Receiver", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.078, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 6, 23 | "name": "LIB_SLAVE_ROUTING" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_THERMOSTAT" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 8, 46 | "name": "GENERIC_TYPE_THERMOSTAT" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": false, 80 | "basic": false, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "40", 94 | "name": "THERMOSTAT_MODE", 95 | "version": "1", 96 | "nif": false, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [ 102 | { 103 | "type": "thermostat_mode", 104 | "label": "Thermostat mode", 105 | "config": [] 106 | } 107 | ] 108 | }, 109 | { 110 | "id": "72", 111 | "name": "MANUFACTURER_SPECIFIC", 112 | "version": "1", 113 | "nif": false, 114 | "basic": false, 115 | "secure": false, 116 | "nonsecure": true, 117 | "config": "", 118 | "channels": [] 119 | }, 120 | { 121 | "id": "86", 122 | "name": "VERSION", 123 | "version": "1", 124 | "nif": false, 125 | "basic": false, 126 | "secure": false, 127 | "nonsecure": true, 128 | "config": "", 129 | "channels": [] 130 | } 131 | ] 132 | } 133 | ], 134 | "parameters": [], 135 | "associations": [] 136 | } -------------------------------------------------------------------------------- /database/json/intermatic_ha02_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 471, 3 | "label": "HA02", 4 | "manufacturer_name": "Intermatic", 5 | "manufacturer_id": "0005", 6 | "device_ref": [ 7 | "0002:0003" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Appliance Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 1.028, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 4, 23 | "name": "LIB_SLAVE" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": true, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [] 130 | } -------------------------------------------------------------------------------- /database/json/intermatic_ha03_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 506, 3 | "label": "HA03", 4 | "manufacturer_name": "Intermatic", 5 | "manufacturer_id": "0005", 6 | "device_ref": [ 7 | "0003:0003" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Lamp Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 1.028, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 4, 23 | "name": "LIB_SLAVE" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 17, 46 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": true, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "26", 77 | "name": "SWITCH_MULTILEVEL", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_dimmer", 87 | "label": "Dimmer", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [] 130 | } -------------------------------------------------------------------------------- /database/json/intermatic_ha04_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 297, 3 | "label": "HA04", 4 | "manufacturer_name": "Intermatic", 5 | "manufacturer_id": "0005", 6 | "device_ref": [ 7 | "0004:0003" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Outdoor Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 1.028, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 4, 23 | "name": "LIB_SLAVE" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": true, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [] 130 | } -------------------------------------------------------------------------------- /database/json/intermatic_ha04c_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 297, 3 | "label": "HA-04C", 4 | "manufacturer_name": "Intermatic", 5 | "manufacturer_id": "0005", 6 | "device_ref": [ 7 | "0004:0003" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Outdoor Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 1.028, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 4, 23 | "name": "LIB_SLAVE" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "BINARY_SWITCH" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": "NOT_USED" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "BINARY_SWITCH" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": "NOT_USED" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": true, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [] 130 | } -------------------------------------------------------------------------------- /database/json/intermatic_ha05_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 441, 3 | "label": "HA05", 4 | "manufacturer_name": "Intermatic", 5 | "manufacturer_id": "0005", 6 | "device_ref": [ 7 | "0005:0003" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Screw in lamp module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 1.028, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 4, 23 | "name": "LIB_SLAVE" 24 | }, 25 | "basic_class": { 26 | "id": 3, 27 | "name": "BASIC_TYPE_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 3, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 3, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 3, 42 | "name": "BASIC_TYPE_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | } 125 | ] 126 | } 127 | ], 128 | "parameters": [], 129 | "associations": [] 130 | } -------------------------------------------------------------------------------- /database/json/leviton_dzmx11lz_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 196, 3 | "label": "DZMX1-1LZ", 4 | "manufacturer_name": "Leviton", 5 | "manufacturer_id": "001D", 6 | "device_ref": [ 7 | "1B03:0334" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Scene Capable Push On\/Off", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 3.052, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 3, 23 | "name": "LIB_SLAVE_ENHANCED" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 17, 46 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 47 | }, 48 | "specific_class": { 49 | "id": 4, 50 | "name": "SPECIFIC_TYPE_SCENE_SWITCH_MULTILEVEL" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "26", 77 | "name": "SWITCH_MULTILEVEL", 78 | "version": "1", 79 | "nif": false, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_dimmer", 87 | "label": "Dimmer", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "0", 96 | "nif": false, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "2B", 105 | "name": "SCENE_ACTIVATION", 106 | "version": "1", 107 | "nif": false, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "72", 116 | "name": "MANUFACTURER_SPECIFIC", 117 | "version": "1", 118 | "nif": false, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | }, 125 | { 126 | "id": "86", 127 | "name": "VERSION", 128 | "version": "1", 129 | "nif": false, 130 | "basic": false, 131 | "secure": false, 132 | "nonsecure": true, 133 | "config": "", 134 | "channels": [] 135 | } 136 | ] 137 | } 138 | ], 139 | "parameters": [], 140 | "associations": [] 141 | } -------------------------------------------------------------------------------- /database/json/mcohome__0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 337, 3 | "label": "MH3700-HP", 4 | "manufacturer_name": "McoHome Technology Co., Ltd", 5 | "manufacturer_id": "015F", 6 | "device_ref": [ 7 | "0000:0000" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Programmable Thermostat", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": false, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 0, 27 | "name": null 28 | }, 29 | "generic_class": { 30 | "id": 0, 31 | "name": null 32 | }, 33 | "specific_class": { 34 | "id": 0, 35 | "name": "SPECIFIC_TYPE_NOT_USED" 36 | }, 37 | "endpoints": [], 38 | "parameters": [], 39 | "associations": [] 40 | } -------------------------------------------------------------------------------- /database/json/merten_505160_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 470, 3 | "label": "5051xx", 4 | "manufacturer_name": "Merten", 5 | "manufacturer_id": "007A", 6 | "device_ref": [ 7 | "0001:0002" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Radio Push-button CONNECT, 1f", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.27, 17 | "listening": false, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 2, 23 | "name": "LIB_CONTROLLER" 24 | }, 25 | "basic_class": { 26 | "id": 1, 27 | "name": "CONTROLLER" 28 | }, 29 | "generic_class": { 30 | "id": 1, 31 | "name": "REMOTE_SWITCH" 32 | }, 33 | "specific_class": { 34 | "id": 1, 35 | "name": "NOT_USED" 36 | }, 37 | "endpoints": [], 38 | "parameters": [ 39 | { 40 | "id": 0, 41 | "label": "Switching\/dimming\/shutters", 42 | "description": "", 43 | "overview": "", 44 | "size": 1, 45 | "bitmask": "00000000", 46 | "default": 0, 47 | "read_only": false, 48 | "write_only": false, 49 | "value_min": 0, 50 | "value_max": 127, 51 | "options": [] 52 | }, 53 | { 54 | "id": 4, 55 | "label": "Switching single-surface", 56 | "description": "", 57 | "overview": "", 58 | "size": 1, 59 | "bitmask": "00000000", 60 | "default": 0, 61 | "read_only": false, 62 | "write_only": false, 63 | "value_min": 0, 64 | "value_max": 127, 65 | "options": [] 66 | }, 67 | { 68 | "id": 44, 69 | "label": "Doorbell function", 70 | "description": "", 71 | "overview": "", 72 | "size": 1, 73 | "bitmask": "00000000", 74 | "default": 0, 75 | "read_only": false, 76 | "write_only": false, 77 | "value_min": 0, 78 | "value_max": 127, 79 | "options": [] 80 | }, 81 | { 82 | "id": 52, 83 | "label": "LOWER shutter as long as button is pressed", 84 | "description": "", 85 | "overview": "", 86 | "size": 1, 87 | "bitmask": "00000000", 88 | "default": 0, 89 | "read_only": false, 90 | "write_only": false, 91 | "value_min": 0, 92 | "value_max": 127, 93 | "options": [] 94 | }, 95 | { 96 | "id": 54, 97 | "label": "Move shutter single-surface", 98 | "description": "Direction of movement changes after each switching process", 99 | "overview": "", 100 | "size": 1, 101 | "bitmask": "00000000", 102 | "default": 0, 103 | "read_only": false, 104 | "write_only": false, 105 | "value_min": 0, 106 | "value_max": 127, 107 | "options": [] 108 | }, 109 | { 110 | "id": 55, 111 | "label": "RAISE shutter as long as button is pressed", 112 | "description": "", 113 | "overview": "", 114 | "size": 1, 115 | "bitmask": "00000000", 116 | "default": 0, 117 | "read_only": false, 118 | "write_only": false, 119 | "value_min": 0, 120 | "value_max": 127, 121 | "options": [] 122 | } 123 | ], 124 | "associations": [ 125 | { 126 | "id": 1, 127 | "label": "Button upper part", 128 | "max_nodes": 12, 129 | "controller": false, 130 | "description": "", 131 | "overview": "" 132 | }, 133 | { 134 | "id": 2, 135 | "label": "Button lower part", 136 | "max_nodes": 12, 137 | "controller": false, 138 | "description": "", 139 | "overview": "" 140 | } 141 | ] 142 | } -------------------------------------------------------------------------------- /database/json/merten_506119_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 60, 3 | "label": "506119", 4 | "manufacturer_name": "Merten", 5 | "manufacturer_id": "007A", 6 | "device_ref": [ 7 | "0001:0002" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Battery Powered Wall Controller", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": false, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 0, 27 | "name": null 28 | }, 29 | "generic_class": { 30 | "id": 0, 31 | "name": null 32 | }, 33 | "specific_class": { 34 | "id": 0, 35 | "name": "SPECIFIC_TYPE_NOT_USED" 36 | }, 37 | "endpoints": [], 38 | "parameters": [ 39 | { 40 | "id": 0, 41 | "label": "Switching\/dimming\/shutters", 42 | "description": "", 43 | "overview": "", 44 | "size": 1, 45 | "bitmask": "00000000", 46 | "default": 0, 47 | "read_only": false, 48 | "write_only": false, 49 | "value_min": 0, 50 | "value_max": 127, 51 | "options": [] 52 | }, 53 | { 54 | "id": 4, 55 | "label": "Switching single-surface", 56 | "description": "", 57 | "overview": "", 58 | "size": 1, 59 | "bitmask": "00000000", 60 | "default": 0, 61 | "read_only": false, 62 | "write_only": false, 63 | "value_min": 0, 64 | "value_max": 127, 65 | "options": [] 66 | }, 67 | { 68 | "id": 44, 69 | "label": "Doorbell function", 70 | "description": "", 71 | "overview": "", 72 | "size": 1, 73 | "bitmask": "00000000", 74 | "default": 0, 75 | "read_only": false, 76 | "write_only": false, 77 | "value_min": 0, 78 | "value_max": 127, 79 | "options": [] 80 | }, 81 | { 82 | "id": 52, 83 | "label": "LOWER shutter as long as button is pressed", 84 | "description": "", 85 | "overview": "", 86 | "size": 1, 87 | "bitmask": "00000000", 88 | "default": 0, 89 | "read_only": false, 90 | "write_only": false, 91 | "value_min": 0, 92 | "value_max": 127, 93 | "options": [] 94 | }, 95 | { 96 | "id": 54, 97 | "label": "Move shutter single-surface", 98 | "description": "Direction of movement changes after each switching process", 99 | "overview": "", 100 | "size": 1, 101 | "bitmask": "00000000", 102 | "default": 0, 103 | "read_only": false, 104 | "write_only": false, 105 | "value_min": 0, 106 | "value_max": 127, 107 | "options": [] 108 | }, 109 | { 110 | "id": 55, 111 | "label": "RAISE shutter as long as button is pressed", 112 | "description": "", 113 | "overview": "", 114 | "size": 1, 115 | "bitmask": "00000000", 116 | "default": 0, 117 | "read_only": false, 118 | "write_only": false, 119 | "value_min": 0, 120 | "value_max": 127, 121 | "options": [] 122 | } 123 | ], 124 | "associations": [ 125 | { 126 | "id": 1, 127 | "label": "Button upper part", 128 | "max_nodes": 12, 129 | "controller": false, 130 | "description": "", 131 | "overview": "" 132 | }, 133 | { 134 | "id": 2, 135 | "label": "Button lower part", 136 | "max_nodes": 12, 137 | "controller": false, 138 | "description": "", 139 | "overview": "" 140 | } 141 | ] 142 | } -------------------------------------------------------------------------------- /database/json/merten_507601_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 399, 3 | "label": "507601", 4 | "manufacturer_name": "Merten", 5 | "manufacturer_id": "007A", 6 | "device_ref": [ 7 | "8001:8003" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Dual Pole Wall Switch", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.027, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 7, 23 | "name": "LIB_CONTROLLER_BRIDGE" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "25", 55 | "name": "SWITCH_BINARY", 56 | "version": "0", 57 | "nif": false, 58 | "basic": true, 59 | "secure": false, 60 | "nonsecure": false, 61 | "config": "", 62 | "channels": [ 63 | { 64 | "type": "switch_binary", 65 | "label": "Switch", 66 | "config": [] 67 | } 68 | ] 69 | } 70 | ] 71 | } 72 | ], 73 | "parameters": [ 74 | { 75 | "id": 176, 76 | "label": "Staircase timer byte 1", 77 | "description": "", 78 | "overview": "", 79 | "size": 1, 80 | "bitmask": "00000000", 81 | "default": 0, 82 | "read_only": false, 83 | "write_only": false, 84 | "value_min": 0, 85 | "value_max": 127, 86 | "options": [] 87 | }, 88 | { 89 | "id": 177, 90 | "label": "Staircase timer byte 2", 91 | "description": "", 92 | "overview": "", 93 | "size": 1, 94 | "bitmask": "00000000", 95 | "default": 0, 96 | "read_only": false, 97 | "write_only": false, 98 | "value_min": 0, 99 | "value_max": 127, 100 | "options": [] 101 | }, 102 | { 103 | "id": 183, 104 | "label": "Additional limit duration (after brief interruption)", 105 | "description": "Unsure what this does", 106 | "overview": "", 107 | "size": 1, 108 | "bitmask": "00000000", 109 | "default": 30, 110 | "read_only": false, 111 | "write_only": false, 112 | "value_min": 0, 113 | "value_max": 127, 114 | "options": [] 115 | } 116 | ], 117 | "associations": [ 118 | { 119 | "id": 1, 120 | "label": "Group 1", 121 | "max_nodes": 5, 122 | "controller": true, 123 | "description": "", 124 | "overview": "" 125 | } 126 | ] 127 | } -------------------------------------------------------------------------------- /database/json/qees_qeeswall_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 531, 3 | "label": "Qees Wall", 4 | "manufacturer_name": "Qees", 5 | "manufacturer_id": "0095", 6 | "device_ref": [ 7 | "0002:0001" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Wall mountable mini 4 button remote", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.051, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 2, 23 | "name": "LIB_CONTROLLER" 24 | }, 25 | "basic_class": { 26 | "id": 2, 27 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 28 | }, 29 | "generic_class": { 30 | "id": 2, 31 | "name": "GENERIC_TYPE_GENERIC_CONTROLLER" 32 | }, 33 | "specific_class": { 34 | "id": 2, 35 | "name": "SPECIFIC_TYPE_PORTABLE_REMOTE_CONTROLLER" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 2, 42 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 43 | }, 44 | "generic_class": { 45 | "id": 1, 46 | "name": "GENERIC_TYPE_GENERIC_CONTROLLER" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_PORTABLE_REMOTE_CONTROLLER" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "70", 77 | "name": "CONFIGURATION", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [] 85 | }, 86 | { 87 | "id": "72", 88 | "name": "MANUFACTURER_SPECIFIC", 89 | "version": "1", 90 | "nif": true, 91 | "basic": false, 92 | "secure": false, 93 | "nonsecure": true, 94 | "config": "", 95 | "channels": [] 96 | }, 97 | { 98 | "id": "80", 99 | "name": "BATTERY", 100 | "version": "1", 101 | "nif": true, 102 | "basic": false, 103 | "secure": false, 104 | "nonsecure": true, 105 | "config": "", 106 | "channels": [] 107 | }, 108 | { 109 | "id": "84", 110 | "name": "WAKE_UP", 111 | "version": "1", 112 | "nif": true, 113 | "basic": false, 114 | "secure": false, 115 | "nonsecure": true, 116 | "config": "", 117 | "channels": [] 118 | }, 119 | { 120 | "id": "85", 121 | "name": "ASSOCIATION", 122 | "version": "1", 123 | "nif": true, 124 | "basic": false, 125 | "secure": false, 126 | "nonsecure": true, 127 | "config": "", 128 | "channels": [] 129 | }, 130 | { 131 | "id": "86", 132 | "name": "VERSION", 133 | "version": "1", 134 | "nif": true, 135 | "basic": false, 136 | "secure": false, 137 | "nonsecure": true, 138 | "config": "", 139 | "channels": [] 140 | } 141 | ] 142 | } 143 | ], 144 | "parameters": [], 145 | "associations": [] 146 | } -------------------------------------------------------------------------------- /database/json/reitz_05443_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 54, 3 | "label": "Duwi ZW WS - 05443", 4 | "manufacturer_name": "Reitz-Group.de", 5 | "manufacturer_id": "0064", 6 | "device_ref": [ 7 | "5002:0000" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Battery powered One paddle wall controller", 12 | "overview": "", 13 | "inclusion": "
Press inclusion button for 2 seconds<\/p>", 14 | "exclusion": "", 15 | "wakeup": "
Button triple press on on\/off<\/p>", 16 | "protocol_version": 0, 17 | "listening": false, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 1, 27 | "name": "BASIC_TYPE_CONTROLLER" 28 | }, 29 | "generic_class": { 30 | "id": 1, 31 | "name": "GENERIC_TYPE_WALL_CONTROLLER" 32 | }, 33 | "specific_class": { 34 | "id": 1, 35 | "name": "SPECIFIC_TYPE_PORTABLE_REMOTE_CONTROLLER" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 1, 42 | "name": "BASIC_TYPE_CONTROLLER" 43 | }, 44 | "generic_class": { 45 | "id": 1, 46 | "name": "GENERIC_TYPE_GENERIC_CONTROLLER" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_PORTABLE_REMOTE_CONTROLLER" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [ 74 | { 75 | "type": "switch_binary", 76 | "label": "Switch", 77 | "config": [] 78 | } 79 | ] 80 | }, 81 | { 82 | "id": "72", 83 | "name": "MANUFACTURER_SPECIFIC", 84 | "version": "1", 85 | "nif": true, 86 | "basic": false, 87 | "secure": false, 88 | "nonsecure": true, 89 | "config": "", 90 | "channels": [] 91 | }, 92 | { 93 | "id": "80", 94 | "name": "BATTERY", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "85", 105 | "name": "ASSOCIATION", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | } 114 | ] 115 | } 116 | ], 117 | "parameters": [], 118 | "associations": [ 119 | { 120 | "id": 1, 121 | "label": "Basic On\/Off Group", 122 | "max_nodes": 5, 123 | "controller": true, 124 | "description": "", 125 | "overview": "" 126 | } 127 | ] 128 | } -------------------------------------------------------------------------------- /database/json/reitz_duwrpt_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 586, 3 | "label": "DUW_RPT", 4 | "manufacturer_name": "Reitz-Group.de", 5 | "manufacturer_id": "0064", 6 | "device_ref": [ 7 | "5003:0000" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "D\u00fcwi Repeater", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.064, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 1, 23 | "name": "LIB_CONTROLLER_STATIC" 24 | }, 25 | "basic_class": { 26 | "id": 2, 27 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 28 | }, 29 | "generic_class": { 30 | "id": 2, 31 | "name": "GENERIC_TYPE_STATIC_CONTROLLER" 32 | }, 33 | "specific_class": { 34 | "id": 2, 35 | "name": "SPECIFIC_TYPE_NOT_USED" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 2, 42 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 43 | }, 44 | "generic_class": { 45 | "id": 2, 46 | "name": "GENERIC_TYPE_STATIC_CONTROLLER" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "71", 77 | "name": "ALARM", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "alarm_general", 87 | "label": "Alarm", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "72", 94 | "name": "MANUFACTURER_SPECIFIC", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "80", 105 | "name": "BATTERY", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "86", 116 | "name": "VERSION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | }, 125 | { 126 | "id": "87", 127 | "name": "INDICATOR", 128 | "version": "1", 129 | "nif": true, 130 | "basic": false, 131 | "secure": false, 132 | "nonsecure": true, 133 | "config": "", 134 | "channels": [] 135 | } 136 | ] 137 | } 138 | ], 139 | "parameters": [], 140 | "associations": [] 141 | } -------------------------------------------------------------------------------- /database/json/rimport_razberry2_1_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 344, 3 | "label": "RaZberry Controller 2016 ZWave+", 4 | "manufacturer_name": "R-import Ltd.", 5 | "manufacturer_id": "0147", 6 | "device_ref": [ 7 | "0400:0001", 8 | "0400:0002" 9 | ], 10 | "version_min": 1.1, 11 | "version_max": 255.255, 12 | "decription": "Z-Wave.Me Razberry daughtercard", 13 | "overview": "", 14 | "inclusion": "", 15 | "exclusion": "", 16 | "wakeup": "", 17 | "protocol_version": 0, 18 | "listening": true, 19 | "frequently_listening": false, 20 | "routing": false, 21 | "beaming": true, 22 | "library_type": { 23 | "id": 0, 24 | "name": "Unknown" 25 | }, 26 | "basic_class": { 27 | "id": 2, 28 | "name": "STATIC_CONTROLLER" 29 | }, 30 | "generic_class": { 31 | "id": 2, 32 | "name": "STATIC_CONTROLLER" 33 | }, 34 | "specific_class": { 35 | "id": 2, 36 | "name": "PC_CONTROLLER" 37 | }, 38 | "endpoints": [ 39 | { 40 | "id": 0, 41 | "basic_class": { 42 | "id": 2, 43 | "name": "STATIC_CONTROLLER" 44 | }, 45 | "generic_class": { 46 | "id": 2, 47 | "name": "STATIC_CONTROLLER" 48 | }, 49 | "specific_class": { 50 | "id": 1, 51 | "name": "PC_CONTROLLER" 52 | }, 53 | "commandclasses": [ 54 | { 55 | "id": "00", 56 | "name": "NO_OPERATION", 57 | "version": "1", 58 | "nif": false, 59 | "basic": false, 60 | "secure": false, 61 | "nonsecure": true, 62 | "config": "", 63 | "channels": [] 64 | }, 65 | { 66 | "id": "20", 67 | "name": "BASIC", 68 | "version": "0", 69 | "nif": false, 70 | "basic": false, 71 | "secure": false, 72 | "nonsecure": true, 73 | "config": "", 74 | "channels": [] 75 | } 76 | ] 77 | } 78 | ], 79 | "parameters": [], 80 | "associations": [] 81 | } -------------------------------------------------------------------------------- /database/json/rimport_zmeeraz2_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 457, 3 | "label": "ZMEERAZ2", 4 | "manufacturer_name": "R-import Ltd.", 5 | "manufacturer_id": "0147", 6 | "device_ref": [ 7 | "0400:0002" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "RaZberry2", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 2, 27 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 28 | }, 29 | "generic_class": { 30 | "id": 2, 31 | "name": "GENERIC_TYPE_STATIC_CONTROLLER" 32 | }, 33 | "specific_class": { 34 | "id": 2, 35 | "name": "SPECIFIC_TYPE_PC_CONTROLLER" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 2, 42 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 43 | }, 44 | "generic_class": { 45 | "id": 2, 46 | "name": "GENERIC_TYPE_STATIC_CONTROLLER" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_PC_CONTROLLER" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "0", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | } 75 | ] 76 | } 77 | ], 78 | "parameters": [], 79 | "associations": [] 80 | } -------------------------------------------------------------------------------- /database/json/rtc_ct101_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 98, 3 | "label": "CT101", 4 | "manufacturer_name": "Radio Thermostat Company of America", 5 | "manufacturer_id": "0098", 6 | "device_ref": [ 7 | "6501:000C" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Z-Wave Thermostat", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "THERMOSTAT" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "THERMOSTAT_GENERAL_V2" 36 | }, 37 | "endpoints": [], 38 | "parameters": [], 39 | "associations": [ 40 | { 41 | "id": 1, 42 | "label": "Group 1", 43 | "max_nodes": 2, 44 | "controller": true, 45 | "description": "", 46 | "overview": "" 47 | } 48 | ] 49 | } -------------------------------------------------------------------------------- /database/json/somfy_zrtsi_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 369, 3 | "label": "ZRTSI", 4 | "manufacturer_name": "Somfy", 5 | "manufacturer_id": "0047", 6 | "device_ref": [ 7 | "5A52:5400" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Z-Wave to RTS Interface Controller", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 3.042, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 7, 23 | "name": "LIB_CONTROLLER_BRIDGE" 24 | }, 25 | "basic_class": { 26 | "id": 2, 27 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 28 | }, 29 | "generic_class": { 30 | "id": 2, 31 | "name": "GENERIC_TYPE_STATIC_CONTROLLER" 32 | }, 33 | "specific_class": { 34 | "id": 2, 35 | "name": null 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 2, 42 | "name": "BASIC_TYPE_STATIC_CONTROLLER" 43 | }, 44 | "generic_class": { 45 | "id": 2, 46 | "name": "GENERIC_TYPE_STATIC_CONTROLLER" 47 | }, 48 | "specific_class": { 49 | "id": 0, 50 | "name": null 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "72", 77 | "name": "MANUFACTURER_SPECIFIC", 78 | "version": "1", 79 | "nif": false, 80 | "basic": false, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [] 85 | }, 86 | { 87 | "id": "86", 88 | "name": "VERSION", 89 | "version": "1", 90 | "nif": false, 91 | "basic": false, 92 | "secure": false, 93 | "nonsecure": true, 94 | "config": "", 95 | "channels": [] 96 | } 97 | ] 98 | } 99 | ], 100 | "parameters": [], 101 | "associations": [] 102 | } -------------------------------------------------------------------------------- /database/json/vision_pid11995_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 454, 3 | "label": "PID11995", 4 | "manufacturer_name": "Vision Security", 5 | "manufacturer_id": "0109", 6 | "device_ref": [ 7 | "2007:0703" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Monoprice ZWave On\/Off Plugin", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 3.052, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 6, 23 | "name": "LIB_SLAVE_ROUTING" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | }, 114 | { 115 | "id": "75", 116 | "name": "PROTECTION", 117 | "version": "1", 118 | "nif": true, 119 | "basic": false, 120 | "secure": false, 121 | "nonsecure": true, 122 | "config": "", 123 | "channels": [] 124 | }, 125 | { 126 | "id": "86", 127 | "name": "VERSION", 128 | "version": "1", 129 | "nif": true, 130 | "basic": false, 131 | "secure": false, 132 | "nonsecure": true, 133 | "config": "", 134 | "channels": [] 135 | } 136 | ] 137 | } 138 | ], 139 | "parameters": [], 140 | "associations": [] 141 | } -------------------------------------------------------------------------------- /database/json/vision_zl7101_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 371, 3 | "label": "ZL7101", 4 | "manufacturer_name": "Vision Security", 5 | "manufacturer_id": "0109", 6 | "device_ref": [ 7 | "2008:0801" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Lamp Dimmer Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 2.064, 17 | "listening": false, 18 | "frequently_listening": false, 19 | "routing": false, 20 | "beaming": false, 21 | "library_type": { 22 | "id": 7, 23 | "name": "LIB_CONTROLLER_BRIDGE" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 17, 46 | "name": "GENERIC_TYPE_SWITCH_MULTILEVEL" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_POWER_SWITCH_MULTILEVEL" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "26", 55 | "name": "SWITCH_MULTILEVEL", 56 | "version": "1", 57 | "nif": true, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [ 63 | { 64 | "type": "switch_dimmer", 65 | "label": "Dimmer", 66 | "config": [] 67 | } 68 | ] 69 | }, 70 | { 71 | "id": "27", 72 | "name": "SWITCH_ALL", 73 | "version": "1", 74 | "nif": true, 75 | "basic": false, 76 | "secure": false, 77 | "nonsecure": true, 78 | "config": "", 79 | "channels": [] 80 | }, 81 | { 82 | "id": "72", 83 | "name": "MANUFACTURER_SPECIFIC", 84 | "version": "1", 85 | "nif": true, 86 | "basic": false, 87 | "secure": false, 88 | "nonsecure": true, 89 | "config": "", 90 | "channels": [] 91 | }, 92 | { 93 | "id": "75", 94 | "name": "PROTECTION", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "86", 105 | "name": "VERSION", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | } 114 | ] 115 | } 116 | ], 117 | "parameters": [], 118 | "associations": [] 119 | } -------------------------------------------------------------------------------- /database/json/wintop_le120_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 389, 3 | "label": "LE120", 4 | "manufacturer_name": "Wintop", 5 | "manufacturer_id": "0097", 6 | "device_ref": [ 7 | "6941:5501" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Appliance Module", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 0, 17 | "listening": true, 18 | "frequently_listening": false, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 0, 23 | "name": "Unknown" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "BASIC_TYPE_ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "GENERIC_TYPE_SWITCH_BINARY" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "BASIC_TYPE_ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 16, 46 | "name": "GENERIC_TYPE_SWITCH_BINARY" 47 | }, 48 | "specific_class": { 49 | "id": 1, 50 | "name": "SPECIFIC_TYPE_POWER_SWITCH_BINARY" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "25", 77 | "name": "SWITCH_BINARY", 78 | "version": "1", 79 | "nif": true, 80 | "basic": true, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [ 85 | { 86 | "type": "switch_binary", 87 | "label": "Switch", 88 | "config": [] 89 | } 90 | ] 91 | }, 92 | { 93 | "id": "27", 94 | "name": "SWITCH_ALL", 95 | "version": "1", 96 | "nif": true, 97 | "basic": false, 98 | "secure": false, 99 | "nonsecure": true, 100 | "config": "", 101 | "channels": [] 102 | }, 103 | { 104 | "id": "72", 105 | "name": "MANUFACTURER_SPECIFIC", 106 | "version": "1", 107 | "nif": true, 108 | "basic": false, 109 | "secure": false, 110 | "nonsecure": true, 111 | "config": "", 112 | "channels": [] 113 | } 114 | ] 115 | } 116 | ], 117 | "parameters": [], 118 | "associations": [] 119 | } -------------------------------------------------------------------------------- /database/json/yale_yrd110_0_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "database_id": 302, 3 | "label": "YRD110", 4 | "manufacturer_name": "Yale Locks", 5 | "manufacturer_id": "0129", 6 | "device_ref": [ 7 | "0004:0800" 8 | ], 9 | "version_min": 0, 10 | "version_max": 255.255, 11 | "decription": "Yale button deadbolt without keyway", 12 | "overview": "", 13 | "inclusion": "", 14 | "exclusion": "", 15 | "wakeup": "", 16 | "protocol_version": 3.034, 17 | "listening": false, 18 | "frequently_listening": true, 19 | "routing": true, 20 | "beaming": true, 21 | "library_type": { 22 | "id": 3, 23 | "name": "LIB_SLAVE_ENHANCED" 24 | }, 25 | "basic_class": { 26 | "id": 4, 27 | "name": "ROUTING_SLAVE" 28 | }, 29 | "generic_class": { 30 | "id": 4, 31 | "name": "ENTRY_CONTROL" 32 | }, 33 | "specific_class": { 34 | "id": 4, 35 | "name": "SECURE_KEYPAD_DOOR_LOCK" 36 | }, 37 | "endpoints": [ 38 | { 39 | "id": 0, 40 | "basic_class": { 41 | "id": 4, 42 | "name": "ROUTING_SLAVE" 43 | }, 44 | "generic_class": { 45 | "id": 64, 46 | "name": "ENTRY_CONTROL" 47 | }, 48 | "specific_class": { 49 | "id": 3, 50 | "name": "SECURE_KEYPAD_DOOR_LOCK" 51 | }, 52 | "commandclasses": [ 53 | { 54 | "id": "00", 55 | "name": "NO_OPERATION", 56 | "version": "1", 57 | "nif": false, 58 | "basic": false, 59 | "secure": false, 60 | "nonsecure": true, 61 | "config": "", 62 | "channels": [] 63 | }, 64 | { 65 | "id": "20", 66 | "name": "BASIC", 67 | "version": "1", 68 | "nif": false, 69 | "basic": false, 70 | "secure": false, 71 | "nonsecure": true, 72 | "config": "", 73 | "channels": [] 74 | }, 75 | { 76 | "id": "72", 77 | "name": "MANUFACTURER_SPECIFIC", 78 | "version": "1", 79 | "nif": false, 80 | "basic": false, 81 | "secure": false, 82 | "nonsecure": true, 83 | "config": "", 84 | "channels": [] 85 | }, 86 | { 87 | "id": "86", 88 | "name": "VERSION", 89 | "version": "1", 90 | "nif": false, 91 | "basic": false, 92 | "secure": false, 93 | "nonsecure": true, 94 | "config": "", 95 | "channels": [] 96 | } 97 | ] 98 | } 99 | ], 100 | "parameters": [], 101 | "associations": [] 102 | } -------------------------------------------------------------------------------- /database/types.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/stampzilla/gozwave/commands" 4 | 5 | type CommandClass struct { 6 | ID commands.ZWaveCommand 7 | //Controlled bool 8 | InNIF bool 9 | Secure bool 10 | NonSecure bool 11 | Version string 12 | } 13 | 14 | type Definition struct { 15 | Generic byte 16 | Specific byte 17 | } 18 | -------------------------------------------------------------------------------- /events.go: -------------------------------------------------------------------------------- 1 | package gozwave 2 | 3 | type Event interface { 4 | //TODO use this instead of empty interface in controller.GetNextEvent?? 5 | } 6 | -------------------------------------------------------------------------------- /events/node_awake.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | type NodeAwake struct { 4 | Awake bool 5 | } 6 | -------------------------------------------------------------------------------- /events/node_discoverd.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | import "github.com/stampzilla/gozwave/serialapi" 4 | 5 | type NodeDiscoverd struct { 6 | Address int 7 | 8 | serialapi.FuncGetNodeProtocolInfo 9 | } 10 | -------------------------------------------------------------------------------- /events/node_identification.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | type NodeIdentification struct { 4 | Manufacturer int32 5 | ProductTypeID int32 6 | ProductID int32 7 | } 8 | -------------------------------------------------------------------------------- /events/node_updated.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | type NodeUpdated struct { 4 | Address int 5 | } 6 | -------------------------------------------------------------------------------- /events/node_values.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | type NodeValues struct { 4 | } 5 | -------------------------------------------------------------------------------- /generators/database/database_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCommandClass (t *testing.T) { 10 | c := commandClass{ 11 | ID: "15", 12 | } 13 | 14 | assert.Equal(t,"0x15", c.IDasHex()); 15 | } 16 | 17 | func TestDeviceDescription (t *testing.T) { 18 | d := deviceDescription{ 19 | BrandName: "a\\b\nc\rd-brand", 20 | ProductName: "a\\b\nc\rd-product", 21 | Description: "a\\b\nc\rd-desc", 22 | } 23 | 24 | assert.Equal(t,"a\\\\b\\nc\\nd-brand", d.EscapedBrandName()); 25 | assert.Equal(t,"a\\\\b\\nc\\nd-product", d.EscapedProductName()); 26 | assert.Equal(t,"a\\\\b\\nc\\nd-desc", d.EscapedDescription()); 27 | } 28 | 29 | func TestRemoveNewLines(t *testing.T) { 30 | s := removeNewLines("a\\b\nc\rd") 31 | 32 | assert.Equal(t, "a\\\\b\\nc\\nd", s); 33 | } 34 | 35 | func TestValue (t *testing.T) { 36 | v := value{ 37 | From:"12121212", 38 | To:"34121212", 39 | size: 1, 40 | Desc: []lang{ 41 | lang{Language: "ru", Body:"a\\b\nc\rd-langru"}, 42 | lang{Language: "en", Body:"a\\b\nc\rd-langen"}, 43 | }, 44 | } 45 | 46 | assert.Equal(t, "18", v.FromDec()); 47 | assert.Equal(t, "52", v.ToDec()); 48 | 49 | v.size = 2 50 | 51 | assert.Equal(t, "4626", v.FromDec()); 52 | assert.Equal(t, "13330", v.ToDec()); 53 | 54 | v.size = 4 55 | 56 | assert.Equal(t, "303174162", v.FromDec()); 57 | assert.Equal(t, "873599506", v.ToDec()); 58 | 59 | assert.Equal(t, "a\\\\b\\nc\\nd-langen", v.EscapedDesc()); 60 | 61 | v.Desc = []lang{} 62 | assert.Equal(t, "", v.EscapedDesc()); 63 | } 64 | 65 | func TestParameter(t *testing.T) { 66 | p := parameter{ 67 | ID: "4", 68 | Name: []lang{ 69 | lang{Language: "ru", Body:"a\\b\nc\rd-nameru"}, 70 | lang{Language: "en", Body:"a\\b\nc\rd-nameen"}, 71 | }, 72 | Desc: []lang{ 73 | lang{Language: "ru", Body:"a\\b\nc\rd-descru"}, 74 | lang{Language: "en", Body:"a\\b\nc\rd-descen"}, 75 | }, 76 | } 77 | 78 | assert.Equal(t, "a\\\\b\\nc\\nd-nameen", p.NameCombined()); 79 | 80 | p.Name = []lang{} 81 | assert.Equal(t, "a\\\\b\\nc\\nd-descen", p.NameCombined()); 82 | 83 | p.Desc = []lang{} 84 | assert.Equal(t, "Parameter 4", p.NameCombined()); 85 | } 86 | -------------------------------------------------------------------------------- /gozwave.go: -------------------------------------------------------------------------------- 1 | //go:generate go run generators/database/database.go -file database/test.go -package database -databasedir ./database 2 | //go:generate go fmt ./database/test.go 3 | //go:generate go run generators/commandclasses/commandclasses.go -file database/mandatory.go -package database -mandatoryfile ./database/mandatory.txt 4 | //go:generate go fmt ./database/mandatory.go 5 | 6 | package gozwave 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | "time" 12 | 13 | "github.com/sirupsen/logrus" 14 | "github.com/tarm/serial" 15 | ) 16 | 17 | func ConnectWithCustomPortOpener(port string, filename string, po PortOpener) (*Controller, error) { 18 | if po == nil { 19 | return nil, fmt.Errorf("portopener is nil") 20 | } 21 | 22 | c := NewController() 23 | c.filename = filename 24 | c.Connection.portOpener = po 25 | 26 | var err error 27 | 28 | c.Connection.reportCallback = c.DeliverReportToNode 29 | connected := make(chan error) 30 | 31 | //spew.Dump(c) 32 | 33 | if filename != "" { 34 | c.LoadConfigurationFromFile() 35 | } 36 | 37 | go func() { 38 | for { 39 | err := c.Connection.Connect(connected) 40 | logrus.Error(err) 41 | <-time.After(time.Second) 42 | } 43 | }() 44 | 45 | err = <-connected 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | go c.getNodes() 51 | go c.saveDebouncer() 52 | 53 | return c, err 54 | } 55 | func Connect(port string, filename string) (*Controller, error) { 56 | return ConnectWithCustomPortOpener(port, filename, newSerialPortOpener(port, 115200)) 57 | } 58 | 59 | type PortOpener interface { 60 | /* TODO: add methods */ 61 | Open() (io.ReadWriteCloser, error) 62 | } 63 | 64 | type serialPortOpener struct { 65 | port string 66 | baud int 67 | } 68 | 69 | func (spo *serialPortOpener) Open() (io.ReadWriteCloser, error) { 70 | return serial.OpenPort(&serial.Config{Name: spo.port, Baud: spo.baud}) 71 | } 72 | 73 | func newSerialPortOpener(port string, baud int) *serialPortOpener { 74 | return &serialPortOpener{ 75 | port: port, 76 | baud: baud, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /gozwave_test.go: -------------------------------------------------------------------------------- 1 | package gozwave 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "testing" 8 | "text/tabwriter" 9 | "time" 10 | 11 | "github.com/sirupsen/logrus" 12 | "github.com/stampzilla/gozwave/serialapi" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestConnect(t *testing.T) { 17 | if testing.Verbose() { 18 | logrus.SetLevel(logrus.DebugLevel) 19 | } 20 | 21 | mockPO := newMockPortOpener() 22 | controller, err := ConnectWithCustomPortOpener("/test", "", mockPO) 23 | 24 | assert.NoError(t, err) 25 | //log.Println("controller: ", controller) 26 | 27 | reply(t, mockPO.mockSerial.getFromWrite, mockPO.mockSerial.sendToRead) // Start up a conversation loop 28 | 29 | // TODO: make something better than a sleep here 30 | time.Sleep(100 * time.Millisecond) 31 | 32 | n := controller.Nodes.Get(8) 33 | assert.NotNil(t, n) 34 | if assert.NotNil(t, n.ProtocolInfo()) { 35 | assert.Equal(t, 36 | serialapi.FuncGetNodeProtocolInfo{ 37 | Listening: false, 38 | Routing: true, 39 | Beaming: true, 40 | Version: 0x04, 41 | Flirs: false, 42 | Security: false, 43 | MaxBaud: 40000, 44 | Basic: 0x04, 45 | Generic: 0x40, 46 | Specific: 0x0, 47 | }, 48 | *n.ProtocolInfo(), 49 | ) 50 | } 51 | } 52 | 53 | func reply(t *testing.T, c chan []byte, w chan string) { 54 | replies := map[string][]string{ 55 | "06": []string{}, 56 | "01030002fe": []string{ // Request discovery nodes 57 | "06", // Ack 58 | "0125010205001d8000000000000000000000000000000000000000000000000000000000050044", // Answer with node 8 active 59 | }, 60 | "0104004108b2": []string{ // Request node information node 8 61 | "06", // Ack 62 | "01080141539c0004403c", // Answer with node information 63 | }, 64 | } 65 | 66 | reads := map[string]int{} // Count the number of reads received 67 | 68 | // Receive messages from gozwave and answer 69 | for { 70 | select { 71 | case v := <-c: 72 | s := fmt.Sprintf("%x", v) // Encode to hex string 73 | 74 | if r, ok := replies[s]; ok { 75 | // Keep a count of how many times we have received each message 76 | if _, ok := reads[s]; !ok { 77 | reads[s] = 0 78 | } 79 | reads[s]++ 80 | 81 | for _, rep := range r { 82 | w <- rep // Send the response 83 | } 84 | 85 | if len(reads) == len(replies) { // We got all messages expected. Exit the loop 86 | //<-time.After(time.Second) 87 | return 88 | } 89 | break 90 | } 91 | 92 | t.Fatalf("Unexpected read %s", s) 93 | return 94 | case <-time.After(time.Second * 3): 95 | fmt.Println("Message counts\n----------------------------------") 96 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 5, ' ', tabwriter.AlignRight|tabwriter.Debug) 97 | for k := range replies { 98 | fmt.Fprintf(w, "%d\t%s\n", reads[k], k) 99 | } 100 | w.Flush() 101 | fmt.Println("----------------------------------") 102 | 103 | for k := range replies { 104 | if _, ok := reads[k]; !ok { 105 | t.Errorf("Test timeout. Have not received message %s", k) 106 | } 107 | } 108 | return 109 | } 110 | } 111 | } 112 | 113 | type poMock struct{} 114 | 115 | func (po *poMock) Open() (io.ReadWriteCloser, error) { 116 | return nil, fmt.Errorf("custom mock connect error") 117 | } 118 | 119 | func TestConnectWithCustomPortOpener(t *testing.T) { 120 | // Test failed portopener 121 | c, err := ConnectWithCustomPortOpener("", "", nil) 122 | 123 | assert.EqualError(t, err, "portopener is nil") 124 | assert.Nil(t, c) 125 | 126 | // Test failed connect 127 | c, err = ConnectWithCustomPortOpener("", "", &poMock{}) 128 | 129 | assert.EqualError(t, err, "custom mock connect error") 130 | assert.Nil(t, c) 131 | } 132 | -------------------------------------------------------------------------------- /interfaces/encodable.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/stampzilla/gozwave/commands/reports" 7 | "github.com/stampzilla/gozwave/serialapi" 8 | ) 9 | 10 | type Encodable interface { 11 | Encode() []byte 12 | } 13 | 14 | type Writer interface { 15 | Write(Encodable) error 16 | WriteWithTimeout(Encodable, time.Duration) (<-chan *serialapi.Message, error) 17 | WriteAndWaitForReport(Encodable, time.Duration, byte) (<-chan reports.Report, error) 18 | } 19 | 20 | type LoadSaveable interface { 21 | LoadConfigurationFromFile() error 22 | SaveConfigurationToFile() error 23 | } 24 | -------------------------------------------------------------------------------- /mock_test.go: -------------------------------------------------------------------------------- 1 | package gozwave 2 | 3 | import ( 4 | "encoding/hex" 5 | "io" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | // generate with impl 'm *mockSerial' io.ReadWriteCloser 11 | type mockSerial struct { 12 | sendToRead chan string 13 | getFromWrite chan []byte 14 | writeLog [][]byte 15 | errorLog []error 16 | } 17 | 18 | func newMockSerial() *mockSerial { 19 | return &mockSerial{ 20 | sendToRead: make(chan string), 21 | getFromWrite: make(chan []byte), 22 | } 23 | } 24 | 25 | func (m *mockSerial) Read(p []byte) (n int, err error) { 26 | data := <-m.sendToRead 27 | 28 | bytes, err := hex.DecodeString(data) 29 | if err != nil { 30 | m.errorLog = append(m.errorLog, err) 31 | return 0, err 32 | } 33 | 34 | n = copy(p, bytes) 35 | if testing.Verbose() { 36 | log.Printf("MOCK read %x", p[:n]) 37 | } 38 | return n, nil 39 | } 40 | 41 | func (m *mockSerial) Write(p []byte) (n int, err error) { 42 | if testing.Verbose() { 43 | log.Printf("MOCK write %x\n", p) 44 | } 45 | m.writeLog = append(m.writeLog, p) 46 | m.getFromWrite <- p 47 | return len(p), nil 48 | } 49 | 50 | func (m *mockSerial) Close() error { 51 | log.Printf("CLOSE") 52 | return nil 53 | } 54 | 55 | type mockPortOpener struct { 56 | mockSerial *mockSerial 57 | } 58 | 59 | func (mpo *mockPortOpener) Open() (io.ReadWriteCloser, error) { 60 | return mpo.mockSerial, nil 61 | 62 | } 63 | 64 | func newMockPortOpener() *mockPortOpener { 65 | return &mockPortOpener{ 66 | mockSerial: newMockSerial(), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /nodes/commands.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/stampzilla/gozwave/commands" 5 | "github.com/stampzilla/gozwave/interfaces" 6 | ) 7 | 8 | func (n *Node) On() { 9 | var send interfaces.Encodable 10 | 11 | switch { 12 | case n.HasCommand(commands.SwitchBinary): 13 | cmd := commands.NewSwitchBinary() 14 | cmd.SetValue(true) 15 | cmd.SetNode(n.Id) 16 | 17 | send = cmd 18 | case n.HasCommand(commands.SwitchMultilevel): 19 | cmd := commands.NewSwitchMultilevel() 20 | cmd.SetValue(100) 21 | cmd.SetNode(n.Id) 22 | 23 | send = cmd 24 | default: 25 | return 26 | } 27 | 28 | n.connection.Write(send) 29 | } 30 | 31 | func (n *Node) Off() { 32 | var send interfaces.Encodable 33 | 34 | switch { 35 | case n.HasCommand(commands.SwitchBinary): 36 | cmd := commands.NewSwitchBinary() 37 | cmd.SetValue(false) 38 | cmd.SetNode(n.Id) 39 | 40 | send = cmd 41 | case n.HasCommand(commands.SwitchMultilevel): 42 | cmd := commands.NewSwitchMultilevel() 43 | cmd.SetValue(0) 44 | cmd.SetNode(n.Id) 45 | 46 | send = cmd 47 | default: 48 | return 49 | } 50 | 51 | n.connection.Write(send) 52 | } 53 | 54 | func (n *Node) Level(value float64) { 55 | var send interfaces.Encodable 56 | 57 | switch { 58 | case n.HasCommand(commands.SwitchMultilevel): 59 | cmd := commands.NewSwitchMultilevel() 60 | cmd.SetValue(value) 61 | cmd.SetNode(n.Id) 62 | 63 | send = cmd 64 | default: 65 | return 66 | } 67 | 68 | n.connection.Write(send) 69 | } 70 | -------------------------------------------------------------------------------- /nodes/endpoints.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/stampzilla/gozwave/commands" 6 | "github.com/stampzilla/gozwave/database" 7 | "github.com/stampzilla/gozwave/interfaces" 8 | ) 9 | 10 | type Endpoint struct { 11 | Id int 12 | CommandClasses []*database.CommandClass 13 | 14 | StateBool map[string]bool 15 | StateFloat map[string]float64 16 | 17 | node *Node 18 | } 19 | 20 | func (n *Node) Endpoint(id int) *Endpoint { 21 | if n == nil { 22 | logrus.Errorf("Failed to get endpoint from NIL node") 23 | return nil 24 | } 25 | 26 | if id > len(n.Endpoints) { 27 | return nil 28 | } 29 | 30 | return n.Endpoints[id] 31 | } 32 | 33 | func (e *Endpoint) Write(msg interfaces.Encodable) { 34 | logrus.Debugf("Send to endpoint %d ", e.Id) 35 | e.node.connection.Write(commands.NewMultiChannelEncap(msg.Encode(), e.Id)) 36 | } 37 | 38 | func (e *Endpoint) On() { 39 | var send interfaces.Encodable 40 | 41 | switch { 42 | case e.node.HasCommand(commands.SwitchBinary): 43 | cmd := commands.NewSwitchBinary() 44 | cmd.SetValue(true) 45 | cmd.SetNode(e.node.Id) 46 | 47 | send = cmd 48 | case e.node.HasCommand(commands.SwitchMultilevel): 49 | cmd := commands.NewSwitchMultilevel() 50 | cmd.SetValue(100) 51 | cmd.SetNode(e.node.Id) 52 | 53 | send = cmd 54 | default: 55 | return 56 | } 57 | 58 | e.Write(send) 59 | } 60 | 61 | func (e *Endpoint) Off() { 62 | var send interfaces.Encodable 63 | 64 | switch { 65 | case e.node.HasCommand(commands.SwitchBinary): 66 | cmd := commands.NewSwitchBinary() 67 | cmd.SetValue(false) 68 | cmd.SetNode(e.node.Id) 69 | 70 | send = cmd 71 | case e.node.HasCommand(commands.SwitchMultilevel): 72 | cmd := commands.NewSwitchMultilevel() 73 | cmd.SetValue(0) 74 | cmd.SetNode(e.node.Id) 75 | 76 | send = cmd 77 | default: 78 | return 79 | } 80 | 81 | e.Write(send) 82 | } 83 | 84 | func (e *Endpoint) Level(value float64) { 85 | var send interfaces.Encodable 86 | 87 | switch { 88 | case e.node.HasCommand(commands.SwitchMultilevel): 89 | cmd := commands.NewSwitchMultilevel() 90 | cmd.SetValue(value) 91 | cmd.SetNode(e.node.Id) 92 | 93 | send = cmd 94 | default: 95 | return 96 | } 97 | 98 | e.Write(send) 99 | } 100 | -------------------------------------------------------------------------------- /nodes/list.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "sync" 7 | ) 8 | 9 | type List struct { 10 | nodes map[int]*Node 11 | //connection *serialapi.Connection 12 | 13 | sync.RWMutex 14 | } 15 | 16 | func NewList() *List { 17 | l := List{ 18 | nodes: make(map[int]*Node), 19 | } 20 | return &l 21 | } 22 | 23 | func (l *List) Add(node *Node) { 24 | l.Lock() 25 | if l.nodes == nil { 26 | l.nodes = make(map[int]*Node) 27 | } 28 | 29 | node.Lock() 30 | //node.connection = l.connection 31 | for k, _ := range node.Endpoints { 32 | node.Endpoints[k].node = node 33 | } 34 | node.Unlock() 35 | 36 | l.nodes[int(node.Id)] = node 37 | l.Unlock() 38 | } 39 | 40 | func (l List) All() map[int]*Node { 41 | l.RLock() 42 | defer l.RUnlock() 43 | 44 | for _, v := range l.nodes { 45 | v.RLock() 46 | defer v.RUnlock() 47 | } 48 | 49 | return l.nodes 50 | } 51 | 52 | func (l List) Get(id int) *Node { 53 | l.RLock() 54 | defer l.RUnlock() 55 | 56 | if l.nodes[id] != nil { 57 | l.nodes[id].RLock() 58 | defer l.nodes[id].RUnlock() 59 | } 60 | 61 | return l.nodes[id] 62 | } 63 | 64 | //func (l *List) SetConnection(connection *serialapi.Connection) { 65 | //l.Lock() 66 | ////l.connection = connection 67 | //l.Unlock() 68 | //} 69 | 70 | func (l List) MarshalJSON() ([]byte, error) { 71 | s := make(map[string]*Node) 72 | 73 | for k, v := range l.All() { 74 | s[strconv.Itoa(k)] = v 75 | } 76 | 77 | return json.Marshal(s) 78 | } 79 | 80 | func (l *List) UnmarshalJSON(data []byte) error { 81 | s := make(map[string]*Node) 82 | 83 | if err := json.Unmarshal(data, &s); err != nil { 84 | return err 85 | } 86 | 87 | for _, v := range s { 88 | l.Add(v) 89 | } 90 | 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /nodes/manufacurer_specific.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/stampzilla/gozwave/commands" 8 | "github.com/stampzilla/gozwave/commands/reports" 9 | ) 10 | 11 | type ManufacurerSpecific struct { 12 | } 13 | 14 | func (n *Node) RequestManufacturerSpecific() (*reports.ManufacturerSpecific, error) { 15 | cmd := commands.NewRaw( 16 | []byte{ 17 | commands.ManufacturerSpecific, // Command 18 | 0x04, // MANUFACTURER_SPECIFIC_GET 19 | 0x00, 20 | //0x05, // TransmitOptions? 21 | //0x23, // Callback? 22 | }) 23 | 24 | cmd.SetNode(n.Id) 25 | 26 | t, err := n.connection.WriteAndWaitForReport(cmd, time.Second*10, 0x05) // Request node information 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | report := <-t 32 | 33 | switch cmd := report.(type) { 34 | case *reports.ManufacturerSpecific: 35 | return cmd, nil 36 | default: 37 | return nil, fmt.Errorf("ManufacurerSpecific: Wrong report type: %t", report) 38 | } 39 | 40 | return nil, fmt.Errorf("Failed to get ManufacurerSpecific") 41 | } 42 | -------------------------------------------------------------------------------- /nodes/protocol_info.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/stampzilla/gozwave/serialapi" 9 | ) 10 | 11 | func (n *Node) ProtocolInfo() *serialapi.FuncGetNodeProtocolInfo { 12 | n.RLock() 13 | defer n.RUnlock() 14 | 15 | return n.protocolInfo 16 | } 17 | 18 | func (n *Node) RequestProtocolInfo() (*serialapi.FuncGetNodeProtocolInfo, error) { 19 | cmd := serialapi.NewRaw( 20 | []byte{ 21 | serialapi.GetNodeProtocolInfo, // Function 22 | byte(n.Id), // Node id 23 | }) 24 | 25 | t, err := n.connection.WriteWithTimeout(cmd, time.Second*10) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | resp := <-t 31 | 32 | logrus.Debugf("RequestProtocolInfo RESP: %#v", resp) 33 | 34 | if resp != nil { 35 | switch r := resp.Data.(type) { 36 | case *serialapi.FuncGetNodeProtocolInfo: 37 | return r, nil 38 | default: 39 | return nil, fmt.Errorf("Wrong type: %t", resp.Data) 40 | } 41 | } 42 | 43 | return nil, fmt.Errorf("Reponse was nil") 44 | } 45 | -------------------------------------------------------------------------------- /nodes/request_endpoints.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/stampzilla/gozwave/commands" 9 | "github.com/stampzilla/gozwave/commands/reports" 10 | ) 11 | 12 | func (n *Node) RequestEndpoints() error { 13 | // Todo: Send raw messages here 14 | n.RLock() 15 | if n.CommandClasses == nil { 16 | n.RUnlock() 17 | n.Lock() 18 | n.Endpoints = make([]*Endpoint, 0) 19 | n.Unlock() 20 | return fmt.Errorf("Failed 'RequestEndpoints', no commandclasses exists") 21 | } 22 | 23 | //for _, v := range n.Device.CommandClasses { 24 | //logrus.Errorf("Request endpoint %d:%x - %s", n.Id, byte(v.ID), v.ID) 25 | //} 26 | n.RUnlock() 27 | 28 | cmd := commands.NewRaw( 29 | []byte{ 30 | commands.MultiInstance, // Command class 31 | 0x07, // Command: MultiInstanceCmd_Get 32 | //byte(v.ID), 33 | 0x25, // TransmitOptions? 34 | //0x23, // Callback? 35 | }) 36 | cmd.SetNode(n.Id) 37 | 38 | fmt.Println("") 39 | fmt.Println("") 40 | logrus.Debugf("Request endpoint %d", n.Id) 41 | 42 | t, _ := n.connection.WriteAndWaitForReport(cmd, time.Second*2, 0x08) // Request node information 43 | report := <-t 44 | 45 | logrus.Debugf("Request endpoint %d report: %#v", n.Id, report) 46 | 47 | if report != nil { 48 | switch cmd := report.(type) { 49 | case *reports.MultiChannelEndpoints: 50 | //n.ManufacurerSpecific = cmd 51 | logrus.Debug(cmd.String()) 52 | n.Lock() 53 | n.Endpoints = make([]*Endpoint, 0) 54 | for i := 1; i < cmd.Endpoints; i++ { 55 | n.Endpoints = append(n.Endpoints, &Endpoint{ 56 | Id: i, 57 | CommandClasses: n.CommandClasses, 58 | 59 | StateBool: make(map[string]bool), 60 | StateFloat: make(map[string]float64), 61 | 62 | node: n, 63 | }) 64 | } 65 | n.Unlock() 66 | return nil 67 | default: 68 | logrus.Errorf("Wrong type: %t", cmd) 69 | } 70 | } 71 | 72 | //} 73 | 74 | return fmt.Errorf("Failed to request endpoints") 75 | } 76 | -------------------------------------------------------------------------------- /nodes/request_states.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/davecgh/go-spew/spew" 7 | "github.com/stampzilla/gozwave/commands" 8 | ) 9 | 10 | func (n *Node) RequestStates() error { 11 | n.RLock() 12 | cc := n.CommandClasses 13 | n.RUnlock() 14 | 15 | for _, v := range cc { 16 | switch v.ID { 17 | case commands.SwitchBinary: 18 | cmd := commands.NewRaw([]byte{ 19 | commands.SwitchBinary, // Command class 20 | 0x02, // Command: GET 21 | 0x25, // TransmitOptions? 22 | //0x23, // Callback? 23 | }) 24 | cmd.SetNode(n.Id) 25 | n.connection.Write(cmd) 26 | case commands.SwitchMultilevel: 27 | cmd := commands.NewRaw([]byte{ 28 | commands.SwitchMultilevel, // Command class 29 | 0x02, // Command: GET 30 | 0x25, // TransmitOptions? 31 | //0x23, // Callback? 32 | }) 33 | cmd.SetNode(n.Id) 34 | n.connection.Write(cmd) 35 | case commands.SensorMultiLevel: 36 | cmd := commands.NewRaw([]byte{ 37 | commands.SensorMultiLevel, // Command class 38 | 0x01, // Command: SupportedGet 39 | 0x25, // TransmitOptions? 40 | //0x23, // Callback? 41 | }) 42 | cmd.SetNode(n.Id) 43 | t, _ := n.connection.WriteAndWaitForReport(cmd, time.Second*2, 0x02) // Wait for SupportedReport (0x02) 44 | 45 | report := <-t 46 | //for k, v := range report.sensors { 47 | // Request value for each sensor endpoint 48 | //} 49 | 50 | //logrus.Errorf("Sensors supportedget:") 51 | spew.Dump(report) 52 | } 53 | } 54 | 55 | n.Lock() 56 | n.statesOk = true 57 | n.Unlock() 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /serialapi/application_command_handler.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "github.com/stampzilla/gozwave/commands" 5 | "github.com/stampzilla/gozwave/commands/reports" 6 | ) 7 | 8 | type FuncApplicationCommandHandler struct { 9 | Command commands.ZWaveCommand `json:"command"` 10 | Class byte `json:"class"` 11 | Node byte `json:"node"` 12 | 13 | Report reports.Report `json:"report"` 14 | } 15 | 16 | // NewApplicationCommandHandler decodes and creates a funcApplicationCommandHandler. This is the most common message from the controller. Its sent everytime a report are received from the zwave network. 17 | func NewApplicationCommandHandler(data []byte) (*FuncApplicationCommandHandler, error) { 18 | f := &FuncApplicationCommandHandler{} 19 | 20 | f.Command = commands.ZWaveCommand(data[0]) 21 | f.Class = data[1] 22 | 23 | var err error 24 | f.Report, err = reports.New(f.Command, f.Class, data[2:]) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | if report, ok := f.Report.(reports.Report); ok && report != nil { 30 | report.SetNode(f.Node) 31 | } 32 | 33 | return f, nil 34 | } 35 | -------------------------------------------------------------------------------- /serialapi/application_command_handler_test.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stampzilla/gozwave/commands" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewApplicationCommandHandler(t *testing.T) { 11 | m, err := NewApplicationCommandHandler([]byte{0x00, 0x00, 0x00}) 12 | 13 | assert.Nil(t, m) 14 | assert.Error(t, err) 15 | 16 | m, err = NewApplicationCommandHandler([]byte{commands.WakeUp, 0x00, 0x00}) 17 | 18 | assert.NotNil(t, m) 19 | assert.NoError(t, err) 20 | } 21 | -------------------------------------------------------------------------------- /serialapi/discovery_nodes.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import "fmt" 4 | 5 | type DiscoverdNodes struct { 6 | ActiveNodes [232]bool `json:"active_nodes"` 7 | 8 | data []byte 9 | } 10 | 11 | // NewDiscoverdNodes decodes and creates a discoveryNode message. This message is received from the controller and contains a list of all available nodes in the network 12 | func NewDiscoverdNodes(data []byte) (*DiscoverdNodes, error) { 13 | f := &DiscoverdNodes{ 14 | data: data, 15 | } 16 | 17 | if len(data) < 6 { 18 | return nil, fmt.Errorf("wrong length, should be at least 6 bytes got %d", len(data)) 19 | } 20 | 21 | for index, bits := range data { 22 | f.ActiveNodes[index*8+0] = bits&0x01 != 0 23 | f.ActiveNodes[index*8+1] = bits&0x02 != 0 24 | f.ActiveNodes[index*8+2] = bits&0x04 != 0 25 | f.ActiveNodes[index*8+3] = bits&0x08 != 0 26 | 27 | f.ActiveNodes[index*8+4] = bits&0x10 != 0 28 | f.ActiveNodes[index*8+5] = bits&0x20 != 0 29 | f.ActiveNodes[index*8+6] = bits&0x40 != 0 30 | f.ActiveNodes[index*8+7] = bits&0x80 != 0 31 | } 32 | 33 | return f, nil 34 | } 35 | -------------------------------------------------------------------------------- /serialapi/discovery_nodes_test.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewDiscoverdNodes(t *testing.T) { 10 | m, err := NewDiscoverdNodes([]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}) 11 | 12 | assert.NoError(t, err) 13 | assert.Equal(t, [232]bool{true, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, true, false, false, false, false, false, true, false, true, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, m.ActiveNodes) 14 | 15 | m, err = NewDiscoverdNodes([]byte{0x01, 0x02, 0x03, 0x04, 0x05}) 16 | 17 | assert.Error(t, err) 18 | assert.Nil(t, m) 19 | } 20 | -------------------------------------------------------------------------------- /serialapi/get_node_protocolinfo.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import "fmt" 4 | 5 | type FuncGetNodeProtocolInfo struct { 6 | Listening bool `json:"listening"` 7 | Routing bool `json:"routing"` 8 | Beaming bool `json:"beaming"` 9 | 10 | Version byte `json:"version"` 11 | 12 | Flirs bool `json:"flirs"` 13 | Security bool `json:"security"` 14 | MaxBaud int `json:"max_baud"` 15 | 16 | Basic byte `json:"basic"` 17 | Generic byte `json:"generic"` 18 | Specific byte `json:"specific"` 19 | } 20 | 21 | // NewGetNodeProtocolInfo decodes and cretes a funcGetNodeProtocolInfo. It is sent from the controller and contanins all basic information that the controller knows about a node. 22 | func NewGetNodeProtocolInfo(data []byte) (*FuncGetNodeProtocolInfo, error) { 23 | pi := &FuncGetNodeProtocolInfo{} 24 | 25 | if len(data) < 6 { 26 | return nil, fmt.Errorf("wrong length, should be at least 6 bytes got len %d data: %x", len(data), data) 27 | } 28 | 29 | // Capabilities 30 | pi.Listening = data[0]&0x80 != 0 31 | pi.Routing = data[0]&0x40 != 0 32 | pi.Version = data[0]&0x07 + 1 33 | 34 | pi.MaxBaud = 9600 35 | if data[0]&0x38 == 0x10 { 36 | pi.MaxBaud = 40000 37 | } 38 | 39 | // Security 40 | pi.Flirs = data[1]&0x60 != 0 41 | pi.Beaming = data[1]&0x10 != 0 42 | pi.Security = data[1]&0x01 != 0 43 | 44 | // data[2] - reserved 45 | 46 | pi.Basic = data[3] 47 | pi.Generic = data[4] 48 | pi.Specific = data[5] 49 | 50 | return pi, nil 51 | } 52 | -------------------------------------------------------------------------------- /serialapi/get_node_protocolinfo_test.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewGetNodeProtocolInfo(t *testing.T) { 10 | m, err := NewGetNodeProtocolInfo([]byte{0x01, 0x00, 0xff, 0x04, 0x05, 0x06}) 11 | assert.NoError(t, err) 12 | assert.Equal(t, false, m.Listening, "Listening") 13 | assert.Equal(t, false, m.Routing, "Routing") 14 | assert.Equal(t, false, m.Beaming, "Beaming") 15 | assert.Equal(t, byte(0x02), m.Version, "Version") 16 | assert.Equal(t, false, m.Flirs, "Flirs") 17 | assert.Equal(t, false, m.Security, "Security") 18 | assert.Equal(t, 9600, m.MaxBaud, "MaxBaud") 19 | assert.Equal(t, byte(0x04), m.Basic, "Basic") 20 | assert.Equal(t, byte(0x05), m.Generic, "Generic") 21 | assert.Equal(t, byte(0x06), m.Specific, "Specific") 22 | 23 | m, err = NewGetNodeProtocolInfo([]byte{0x80, 0x01, 0xff, 0xff, 0xff, 0xff}) 24 | assert.NoError(t, err) 25 | assert.Equal(t, true, m.Listening, "Listening") 26 | assert.Equal(t, false, m.Routing, "Routing") 27 | assert.Equal(t, false, m.Beaming, "Beaming") 28 | assert.Equal(t, false, m.Flirs, "Flirs") 29 | assert.Equal(t, true, m.Security, "Security") 30 | assert.Equal(t, 9600, m.MaxBaud, "MaxBaud") 31 | 32 | m, err = NewGetNodeProtocolInfo([]byte{0x40, 0x10, 0xff, 0xff, 0xff, 0xff}) 33 | assert.NoError(t, err) 34 | assert.Equal(t, false, m.Listening, "Listening") 35 | assert.Equal(t, true, m.Routing, "Routing") 36 | assert.Equal(t, true, m.Beaming, "Beaming") 37 | assert.Equal(t, false, m.Flirs, "Flirs") 38 | assert.Equal(t, false, m.Security, "Security") 39 | assert.Equal(t, 9600, m.MaxBaud, "MaxBaud") 40 | 41 | m, err = NewGetNodeProtocolInfo([]byte{0x10, 0x60, 0xff, 0xff, 0xff, 0xff}) 42 | assert.NoError(t, err) 43 | assert.Equal(t, false, m.Listening, "Listening") 44 | assert.Equal(t, false, m.Routing, "Routing") 45 | assert.Equal(t, false, m.Beaming, "Beaming") 46 | assert.Equal(t, true, m.Flirs, "Flirs") 47 | assert.Equal(t, false, m.Security, "Security") 48 | assert.Equal(t, 40000, m.MaxBaud, "MaxBaud") 49 | 50 | m, err = NewGetNodeProtocolInfo([]byte{0x01, 0x02, 0x03, 0x04, 0x05}) 51 | 52 | assert.Error(t, err) 53 | assert.Nil(t, m) 54 | } 55 | -------------------------------------------------------------------------------- /serialapi/message.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // Message is the decoded message received from the controller 10 | type Message struct { 11 | Length byte 12 | MessageType byte 13 | Function ZWaveFunction 14 | NodeID byte 15 | 16 | startByte byte 17 | Data interface{} 18 | } 19 | 20 | // IsACK checks if message is ACK type 21 | func (m *Message) IsACK() bool { 22 | return m.startByte == 0x06 23 | } 24 | 25 | // IsNAK checks if message is NAK type 26 | func (m *Message) IsNAK() bool { 27 | return m.startByte == 0x15 28 | } 29 | 30 | // IsCAN checks if message is CANCEL type 31 | func (m *Message) IsCAN() bool { 32 | return m.startByte == 0x18 33 | } 34 | 35 | // Decode decodes serialapi raw data 36 | func Decode(data []byte) (length int, msg *Message, err error) { 37 | // SOF 0x01 1 Start Of Frame 38 | // ACK 0x06 6 Message Ack 39 | // NAK 0x15 21 Message NAK 40 | // CAN 0x18 24 Cancel - Resend request 41 | 42 | switch data[0] { 43 | case 0x01: // SOF 44 | if len(data) < 2 { 45 | return 2, nil, nil 46 | } 47 | length := int(data[1]) 48 | if len(data) < length+2 { // Make shure we have the full message 49 | return length + 2, nil, nil 50 | } 51 | 52 | checksum := data[length+1] 53 | checksumData := append(data[1:length+1], byte(0x00)) 54 | if checksum != GenerateChecksum(checksumData) { 55 | return -1, nil, fmt.Errorf("Invalid checksum, is 0x%x should be 0x%x, (len=%d)", checksum, GenerateChecksum(checksumData), length) 56 | } 57 | 58 | msg, err := NewMessage(data) 59 | 60 | return length + 2, msg, err 61 | case 0x06, 0x15, 0x18: // ACK, NAC, CAN 62 | msg, err := NewMessage(data) 63 | if err != nil { 64 | logrus.Error(err) 65 | } 66 | 67 | return 1, msg, nil 68 | } 69 | return -1, nil, nil // Not a valid start char 70 | } 71 | 72 | // GenerateChecksum calculates the checksum for a serialapi message 73 | func GenerateChecksum(data []byte) byte { 74 | var offset int 75 | ret := data[offset] 76 | for i := offset + 1; i < len(data)-1; i++ { 77 | // Xor bytes 78 | ret ^= data[i] 79 | } 80 | // Not result 81 | ret = (byte)(^ret) 82 | return ret 83 | } 84 | 85 | // CompileMessage is used to calculate length and checksum for messages that are going to be sent to the controller 86 | func CompileMessage(data []byte) []byte { 87 | // Compile message 88 | msg := append([]byte{0x00, 0x00}, data...) 89 | msg = append(msg, 0x00) 90 | 91 | // Add length 92 | msg[0] = byte(len(msg) - 1) 93 | 94 | // Add checksum 95 | msg[len(msg)-1] = GenerateChecksum(msg) 96 | 97 | // Add header 98 | msg = append([]byte{0x01}, msg...) 99 | 100 | return msg 101 | } 102 | 103 | // NewMessage tries to decode a binary message from the controller in to a message struct 104 | func NewMessage(data []byte) (*Message, error) { 105 | message := &Message{} 106 | message.startByte = data[0] 107 | if len(data) <= 1 { 108 | return message, nil 109 | } 110 | message.Length = data[1] 111 | message.MessageType = data[2] 112 | message.Function = ZWaveFunction(data[3]) 113 | 114 | var err error 115 | switch message.Function { 116 | case ApplicationCommandHandler: 117 | message.NodeID = data[5] 118 | 119 | message.Data, err = NewApplicationCommandHandler(data[7 : 7+data[6]]) 120 | case DiscoveryNodes: 121 | 122 | message.Data, err = NewDiscoverdNodes(data[7 : 7+data[6]]) 123 | case GetNodeProtocolInfo: 124 | 125 | message.Data, err = NewGetNodeProtocolInfo(data[4:]) 126 | default: 127 | err = fmt.Errorf("Dropping message function='%s' with data %x (not implemented)", message.Function, data[4:]) 128 | } 129 | 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | logrus.Debugf("Message: %+v Message data: %+v", message, message.Data) 135 | return message, err 136 | 137 | // 1 FrameHeader (type of message) 138 | // 1 Message length 139 | // 1 Request (00) or Response (01) 140 | // 1 Function 141 | // 142 | // 1 (Node ID) 143 | // 1 (Command - length) 144 | // - (Command) 145 | // - (Transmit options) 146 | // - (Callback id) 147 | // 1 Checksum 148 | 149 | // Type 150 | //Request = 0x00, 151 | //Response = 0x01, 152 | //GetVersion = 0x15, 153 | //MemoryGetId = 0x20, 154 | //ClockSet = 0x30 155 | 156 | // Transmit options 157 | //Ack = 0x01, 158 | //LowPower = 0x02, 159 | //AutoRoute = 0x04, 160 | //ForceRoute = 0x08 161 | 162 | } 163 | -------------------------------------------------------------------------------- /serialapi/message_test.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestNewMessage(t *testing.T) { 13 | raw := []string{ 14 | "01 11 00 04 00 02 0b 71 05 07 00 00 ff 07 00 01 08 00 00", // Alarm report 15 | "01 11 00 04 00 02 0b 71 05 07 ff 00 ff 07 08 01 08 00 00", // Alarm report 16 | "01 11 00 04 00 02 0b 71 05 07 00 00 ff 07 00 01 08 00 00", // Alarm report 17 | 18 | "01 0c 00 04 00 03 06 31 05 03 0a 00 c2 00", // Sensor multilevel 19 | "01 0c 00 04 00 03 06 31 05 03 0a 00 1d 00", // Sensor multilevel 20 | 21 | "01 08 00 04 04 03 02 84 07 00", // Wakeup 22 | 23 | "01 10 00 02 00 00 06 01 02 03 00 00 00", // DiscoveryNodes 24 | "01 10 00 41 00 00 00 00 00 00 ", // GetNodeProtocolInfo 25 | } 26 | 27 | for _, val := range raw { 28 | data := []byte{} 29 | bytes := strings.Split(val, " ") 30 | for _, b := range bytes { 31 | dhex, _ := hex.DecodeString(b) 32 | data = append(data, dhex...) 33 | } 34 | 35 | msg, err := NewMessage(data) 36 | assert.NoError(t, err, "data: %s", val) 37 | 38 | if msg != nil && testing.Verbose() { 39 | fmt.Printf("%+v\n\n", msg.Data) 40 | } 41 | } 42 | } 43 | 44 | func TestDecode(t *testing.T) { 45 | raw := []string{ 46 | "06", // Ack 47 | "15", // Nak 48 | "18", // Can 49 | 50 | "01 11 00 04 00 02 0b 71 05 07 00 00 ff 07 00 01 08 00 61", // Alarm report 51 | "01 11 00 04 00 02 0b 71 05 07 ff 00 ff 07 08 01 08 00 96", // Alarm report 52 | "01 11 00 04 00 02 0b 71 05 07 00 00 ff 07 00 01 08 00 61", // Alarm report 53 | 54 | "01 0c 00 04 00 03 06 31 05 03 0a 00 c2 0d", // Sensor multilevel 55 | "01 0c 00 04 00 03 06 31 05 03 0a 00 1d d2", // Sensor multilevel 56 | 57 | "01 08 00 04 04 03 02 84 07 75", // Wakeup 58 | } 59 | 60 | for _, val := range raw { 61 | data := []byte{} 62 | bytes := strings.Split(val, " ") 63 | for _, b := range bytes { 64 | dhex, _ := hex.DecodeString(b) 65 | data = append(data, dhex...) 66 | } 67 | 68 | l, msg, err := Decode(data) 69 | assert.NotNil(t, msg) 70 | assert.NotEqual(t, -1, l) 71 | assert.NoError(t, err) 72 | 73 | if msg != nil && testing.Verbose() { 74 | fmt.Printf("%+v\n\n", msg.Data) 75 | } 76 | } 77 | 78 | // Without length 79 | l, msg, err := Decode([]byte{0x01}) 80 | assert.Equal(t, 2, l) 81 | assert.Nil(t, msg) 82 | assert.NoError(t, err) 83 | 84 | // To short 85 | l, msg, err = Decode([]byte{0x01, 0x02}) 86 | assert.Equal(t, 4, l) 87 | assert.Nil(t, msg) 88 | assert.NoError(t, err) 89 | 90 | // Invalid checksum 91 | l, msg, err = Decode([]byte{0x01, 0x02, 0x00, 0x00}) 92 | assert.Equal(t, -1, l) 93 | assert.Nil(t, msg) 94 | assert.Error(t, err) 95 | 96 | // Invalid function 97 | l, msg, err = Decode([]byte{0x01, 0x02, 0x00, 0xfd}) 98 | assert.Equal(t, 4, l) 99 | assert.Nil(t, msg) 100 | assert.Error(t, err) 101 | 102 | // Invalid start char 103 | l, msg, err = Decode([]byte{0x99}) 104 | assert.Equal(t, -1, l) 105 | assert.Nil(t, msg) 106 | assert.NoError(t, err) 107 | 108 | } 109 | 110 | func TestMessageIs(t *testing.T) { 111 | m := Message{} 112 | 113 | assert.Equal(t, false, m.IsACK()) 114 | assert.Equal(t, false, m.IsNAK()) 115 | assert.Equal(t, false, m.IsCAN()) 116 | 117 | m.startByte = 0x06 118 | assert.Equal(t, true, m.IsACK()) 119 | assert.Equal(t, false, m.IsNAK()) 120 | assert.Equal(t, false, m.IsCAN()) 121 | 122 | m.startByte = 0x15 123 | assert.Equal(t, false, m.IsACK()) 124 | assert.Equal(t, true, m.IsNAK()) 125 | assert.Equal(t, false, m.IsCAN()) 126 | 127 | m.startByte = 0x18 128 | assert.Equal(t, false, m.IsACK()) 129 | assert.Equal(t, false, m.IsNAK()) 130 | assert.Equal(t, true, m.IsCAN()) 131 | } 132 | 133 | func TestCompileMessage(t *testing.T) { 134 | m := CompileMessage([]byte{0x01, 0x02}) 135 | 136 | assert.Equal(t, []byte{0x1, 0x4, 0x0, 0x1, 0x2, 0xf8}, m) 137 | } 138 | -------------------------------------------------------------------------------- /serialapi/raw.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | // Raw binary data type 4 | type Raw []byte 5 | 6 | // NewRaw creates a new Raw 7 | func NewRaw(b []byte) Raw { 8 | return Raw(b) 9 | } 10 | 11 | // Encode make sure we implement the Encodable interface 12 | func (r Raw) Encode() []byte { 13 | return r 14 | } 15 | -------------------------------------------------------------------------------- /serialapi/raw_test.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRaw(t *testing.T) { 10 | r := NewRaw([]byte{0x01, 0x02, 0x03, 0x04}) 11 | 12 | e := r.Encode() 13 | 14 | assert.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, e) 15 | } 16 | -------------------------------------------------------------------------------- /serialapi/types_test.go: -------------------------------------------------------------------------------- 1 | package serialapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestTypes(t *testing.T) { 10 | 11 | m := map[byte]string{ 12 | 0x00: "None", 13 | 0x02: "DiscoveryNodes", 14 | 0x03: "SerialApiApplNodeInformation", 15 | 0x04: "ApplicationCommandHandler", 16 | 0x05: "GetControllerCapabilities", 17 | 0x06: "SerialApiSetTimeouts", 18 | 0x07: "SerialGetCapabilities", 19 | 0x08: "SerialApiSoftReset", 20 | 0x10: "SetRFReceiveMode", 21 | 0x11: "SetSleepMode", 22 | 0x12: "SendNodeInformation", 23 | 0x13: "SendData", 24 | 0x14: "SendDataMulti", 25 | 0x15: "GetVersion", 26 | 0x16: "SendDataAbort", 27 | 0x17: "RFPowerLevelSet", 28 | 0x18: "SendDataMeta", 29 | 0x20: "MemoryGetId", 30 | 0x21: "MemoryGetByte", 31 | 0x22: "MemoryPutByte", 32 | 0x23: "MemoryGetBuffer", 33 | 0x24: "MemoryPutBuffer", 34 | 0x25: "ReadMemory", 35 | 0x30: "ClockSet", 36 | 0x31: "ClockGet", 37 | 0x32: "ClockCompare", 38 | 0x33: "RtcTimerCreate", 39 | 0x34: "RtcTimerRead", 40 | 0x35: "RtcTimerDelete", 41 | 0x36: "RtcTimerCall", 42 | 0x41: "GetNodeProtocolInfo", 43 | 0x42: "SetDefault", 44 | 0x44: "ReplicationCommandComplete", 45 | 0x45: "ReplicationSendData", 46 | 0x46: "AssignReturnRoute", 47 | 0x47: "DeleteReturnRoute", 48 | 0x48: "RequestNodeNeighborUpdate", 49 | 0x49: "ApplicationUpdate", 50 | 0x4a: "AddNodeToNetwork", 51 | 0x4b: "RemoveNodeFromNetwork", 52 | 0x4c: "CreateNewPrimary", 53 | 0x4d: "ControllerChange", 54 | 0x50: "SetLearnMode", 55 | 0x51: "AssignSucReturnRoute", 56 | 0x52: "EnableSuc", 57 | 0x53: "RequestNetworkUpdate", 58 | 0x54: "SetSucNodeId", 59 | 0x55: "DeleteSucReturnRoute", 60 | 0x56: "GetSucNodeId", 61 | 0x57: "SendSucId", 62 | 0x59: "RediscoveryNeeded", 63 | 0x60: "RequestNodeInfo", 64 | 0x61: "RemoveFailedNodeId", 65 | 0x62: "IsFailedNode", 66 | 0x63: "ReplaceFailedNode", 67 | 0x70: "TimerStart", 68 | 0x71: "TimerRestart", 69 | 0x72: "TimerCancel", 70 | 0x73: "TimerCall", 71 | 0x80: "GetRoutingTableLine", 72 | 0x81: "GetTXCounter", 73 | 0x82: "ResetTXCounter", 74 | 0x83: "StoreNodeInfo", 75 | 0x84: "StoreHomeId", 76 | 0x90: "LockRouteResponse", 77 | 0x91: "SendDataRouteDemo", 78 | 0x95: "SerialApiTest", 79 | 0xa0: "SerialApiSlaveNodeInfo", 80 | 0xa1: "ApplicationSlaveCommandHandler", 81 | 0xa2: "SendSlaveNodeInfo", 82 | 0xa3: "SendSlaveData", 83 | 0xa4: "SetSlaveLearnMode", 84 | 0xa5: "GetVirtualNodes", 85 | 0xa6: "IsVirtualNode", 86 | 0xd0: "SetPromiscuousMode", 87 | } 88 | 89 | for k, v := range m { 90 | assert.Equal(t, v, ZWaveFunction(k).String()) 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /serialrecorder/direction.go: -------------------------------------------------------------------------------- 1 | package serialrecorder 2 | 3 | // Direction indicates if its a read or write row 4 | type Direction bool 5 | 6 | // DirectionRead is a read 7 | const DirectionRead Direction = false 8 | 9 | // DirectionWrite is a write 10 | const DirectionWrite Direction = true 11 | 12 | // IsRead check if direction was read 13 | func (d Direction) IsRead() bool { 14 | return !bool(d) 15 | } 16 | 17 | // IsWrite check if direction was write 18 | func (d Direction) IsWrite() bool { 19 | return bool(d) 20 | } 21 | 22 | func (d Direction) String() string { 23 | if d.IsRead() { 24 | return "read" 25 | } 26 | 27 | return "write" 28 | } 29 | -------------------------------------------------------------------------------- /serialrecorder/direction_test.go: -------------------------------------------------------------------------------- 1 | package serialrecorder 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDirectionRead(t *testing.T) { 10 | var direction = DirectionRead 11 | assert.Equal(t, true, direction.IsRead()) 12 | assert.Equal(t, false, bool(direction)) 13 | assert.Equal(t, "read", direction.String()) 14 | } 15 | func TestDirectionWrite(t *testing.T) { 16 | var direction = DirectionWrite 17 | assert.Equal(t, true, direction.IsWrite()) 18 | assert.Equal(t, true, bool(direction)) 19 | assert.Equal(t, "write", direction.String()) 20 | } 21 | -------------------------------------------------------------------------------- /serialrecorder/serialrecorder.go: -------------------------------------------------------------------------------- 1 | package serialrecorder 2 | 3 | import ( 4 | "encoding/gob" 5 | "io" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/tarm/serial" 11 | ) 12 | 13 | type serialRecorder struct { 14 | serial io.ReadWriteCloser 15 | port string 16 | baud int 17 | Logger io.Writer 18 | 19 | writeBuffer chan *Row 20 | } 21 | 22 | // Row logs one serial read or write 23 | type Row struct { 24 | Timestamp time.Time 25 | Direction Direction 26 | Data []byte 27 | } 28 | 29 | // New returns a new serialRecorder that implements PortOpener for serial port that will record and output each write and read to stdout 30 | func New(port string, baud int) *serialRecorder { 31 | return &serialRecorder{ 32 | port: port, 33 | baud: baud, 34 | Logger: os.Stdout, 35 | writeBuffer: make(chan *Row, 100), 36 | } 37 | } 38 | 39 | func (sr *serialRecorder) Read(p []byte) (n int, err error) { 40 | n, err = sr.serial.Read(p) 41 | go sr.writeToLog(DirectionRead, p[:n]) 42 | return 43 | } 44 | 45 | func (sr *serialRecorder) Write(p []byte) (n int, err error) { 46 | n, err = sr.serial.Write(p) 47 | go sr.writeToLog(DirectionWrite, p[:n]) 48 | return 49 | } 50 | 51 | func (sr *serialRecorder) writeToLog(d Direction, data []byte) { 52 | sr.writeBuffer <- &Row{ 53 | Timestamp: time.Now(), 54 | Direction: d, 55 | Data: data, 56 | } 57 | } 58 | 59 | func (sr *serialRecorder) logWriter() { 60 | enc := gob.NewEncoder(sr.Logger) 61 | 62 | for row := range sr.writeBuffer { 63 | err := enc.Encode(row) 64 | if err != nil { 65 | log.Println(err) 66 | } 67 | } 68 | } 69 | 70 | func (sr *serialRecorder) Close() error { 71 | return sr.serial.Close() 72 | } 73 | func (sr *serialRecorder) Open() (io.ReadWriteCloser, error) { 74 | go sr.logWriter() 75 | 76 | var err error 77 | sr.serial, err = serial.OpenPort(&serial.Config{Name: sr.port, Baud: sr.baud}) 78 | return sr, err 79 | } 80 | -------------------------------------------------------------------------------- /serialrecorder/serialrecorder_test.go: -------------------------------------------------------------------------------- 1 | package serialrecorder 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "log" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestWriteToLog(t *testing.T) { 14 | 15 | buf := bytes.NewBufferString("") 16 | sr := New("asfd", 100) 17 | sr.Logger = buf 18 | go sr.logWriter() 19 | 20 | sr.writeToLog(DirectionWrite, []byte("asdf")) 21 | 22 | time.Sleep(time.Millisecond * 100) 23 | log.Println(buf) 24 | 25 | dec := gob.NewDecoder(buf) // Will read from network. 26 | 27 | r := &Row{} 28 | err := dec.Decode(&r) 29 | assert.NoError(t, err) 30 | 31 | assert.Equal(t, "asdf", string(r.Data)) 32 | assert.Equal(t, DirectionWrite, r.Direction) 33 | 34 | if time.Now().Before(r.Timestamp) { 35 | t.Error("Timestamp invalid. To old") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /zwave-example/example.go: -------------------------------------------------------------------------------- 1 | // An example that connects to an usb connected controller and starts a 2 | // webserver at port 8080. 3 | // 4 | // http://localhost:8080/nodes 5 | // http://localhost:8080/nodes/2 6 | // http://localhost:8080/nodes/2/on 7 | // http://localhost:8080/nodes/2/off 8 | 9 | package main 10 | 11 | import ( 12 | "flag" 13 | "log" 14 | "strconv" 15 | 16 | "github.com/gin-gonic/gin" 17 | "github.com/stampzilla/gozwave" 18 | "github.com/stampzilla/gozwave/events" 19 | "github.com/stampzilla/gozwave/nodes" 20 | ) 21 | 22 | var port string 23 | 24 | func init() { 25 | flag.StringVar(&port, "port", "/dev/ttyACM0", "SerialAPI communication port (to controller)") 26 | flag.Parse() 27 | 28 | } 29 | 30 | func main() { 31 | z, err := gozwave.Connect(port, "") 32 | 33 | if err != nil { 34 | log.Println(err) 35 | } 36 | 37 | r := gin.Default() 38 | r.GET("/nodes", getNodes(z)) 39 | r.GET("/nodes/:id", getNode(z)) 40 | r.GET("/nodes/:id/:action", control(z)) 41 | go r.Run() // listen and serve on 0.0.0.0:8080 42 | 43 | for { 44 | select { 45 | case event := <-z.GetNextEvent(): 46 | log.Println("----------------------------------------") 47 | log.Printf("Event: %#v\n", event) 48 | switch e := event.(type) { 49 | case events.NodeDiscoverd: 50 | znode := z.Nodes.Get(e.Address) 51 | znode.RLock() 52 | log.Printf("Node: %#v\n", znode) 53 | znode.RUnlock() 54 | 55 | case events.NodeUpdated: 56 | znode := z.Nodes.Get(e.Address) 57 | znode.RLock() 58 | log.Printf("Node: %#v\n", znode) 59 | znode.RUnlock() 60 | } 61 | log.Println("----------------------------------------") 62 | } 63 | } 64 | } 65 | 66 | func getNodes(z *gozwave.Controller) func(*gin.Context) { 67 | return func(c *gin.Context) { 68 | nodes := make(map[string]*nodes.Node) 69 | 70 | for k, v := range z.Nodes.All() { 71 | id := strconv.Itoa(k) 72 | nodes[id] = v 73 | } 74 | 75 | c.IndentedJSON(200, nodes) 76 | } 77 | } 78 | 79 | func getNode(z *gozwave.Controller) func(*gin.Context) { 80 | return func(c *gin.Context) { 81 | id, err := strconv.Atoi(c.Param("id")) 82 | if err != nil { 83 | c.IndentedJSON(500, err) 84 | return 85 | } 86 | log.Println(id) 87 | device := z.Nodes.Get(id) 88 | if device == nil { 89 | c.IndentedJSON(404, "Device not found") 90 | return 91 | } 92 | 93 | c.IndentedJSON(200, device) 94 | } 95 | } 96 | 97 | func control(z *gozwave.Controller) func(*gin.Context) { 98 | return func(c *gin.Context) { 99 | id, err := strconv.Atoi(c.Param("id")) 100 | if err != nil { 101 | c.IndentedJSON(500, err) 102 | return 103 | } 104 | 105 | device := z.Nodes.Get(id) 106 | if device == nil { 107 | c.IndentedJSON(404, "Device not found") 108 | return 109 | } 110 | 111 | switch c.Param("action") { 112 | case "on": 113 | device.On() 114 | case "off": 115 | device := z.Nodes.Get(id) 116 | device.Off() 117 | default: 118 | c.IndentedJSON(404, "Action not found") 119 | return 120 | 121 | } 122 | 123 | c.AbortWithStatus(200) 124 | } 125 | } 126 | --------------------------------------------------------------------------------