天天看点

天猫精灵对接智能设备

why to do:

  我之前一直很喜欢智能家居,可惜的是现在市场上成品的智能家居实在是太贵了,屌丝的码农是在背不起每月高额的房贷和装修费用的基础上,再买成品的智能设备(像某米那样一个智能开关,竟然卖那么贵,小弟实在是承受不起啊)。

  我现在想的很简单,就是家里的窗帘(每个卧室一个,客厅做一个双轨)、灯的开关、厨房的凉霸、还有几处插座面板做成智能的,然后在入户门口做个按钮就是按一下可以关闭屋内所有的灯。

  

天猫精灵对接智能设备

  服务器:我之前买了一个群辉,用群辉中docker做的homeassistant

  窗帘电机:我从瀚思彼岸上买的,感觉价格挺实惠*5

  主灯:我买的yeelight,感觉在灯里面的价格还是挺靠谱的,主要他的开关可以调光,这个我很喜欢

  射灯:这块用的也是瀚思彼岸上买的开关

  插座开关:因为我在装修的时候把插座放到了电视后面,我这就做了个智能开关(2个),主要还得控制机顶盒还有我的高清视频播放器

  天猫精灵(去年双十一屯了几个方糖)

  由于新家还没有装修完,我这边就先做了个简单试验,把系统中基本的功能做完了,后期装修完成了,再好好弄弄,但是目前天猫精灵和homeassistant对接,网上确实有不少,可我是一个java码农,虽说对php了解的也还可以,但是肯定不如java啊,这个确实就比较少了,所以我在这里简单写写吧,希望java小伙伴们,可以多多提出建议哈。

how to do:

  天猫精灵有自己的开放平台:https://open.aligenie.com/

  1.添加技能  

天猫精灵对接智能设备
天猫精灵对接智能设备

  这个可以参考天猫精灵给的文档,我觉得写得挺简单的。

  我这边就提出几个比较重要的点,说一下吧:

  1.需要域名和https的证书(可以在阿里云上购买,证书有一个免费一年的)

  2.自己搭建的中转服务需要在外网访问

  3.homeassistant也需要在外网能访问,这里我自己打了一套ngrok,感觉效果还是不错的

  4.java方面,我用的spring boot这里写一些关键的,这个就是

  OAuth2SecurityConfiguration

@Order(1)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private RedisConnectionFactory redisConnection;

    @Autowired
    private BootUserDetailService userDetailService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 必须配置,不然OAuth2的http配置不生效----不明觉厉
                .requestMatchers()
                .antMatchers(  "/test/**","/auth/login","/auth/authorize","/oauth/**","/plugs/**","/gate")
                .and()
                .authorizeRequests()
                // 自定义页面或处理url是,如果不配置全局允许,浏览器会提示服务器将页面转发多次
                .antMatchers("/test/**","/auth/login","/plugs/**","/gate")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();//跨域关闭

        // 表单登录
        http.formLogin()
                // 登录页面
                .loginPage("/auth/login")
                // 登录处理url
                .loginProcessingUrl("/auth/authorize");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
           

AuthorizationServerConfiguration

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 保存令牌数据栈
     */
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private BootUserDetailService userDetailService;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许表单登录
        security.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        String secret = passwordEncoder.encode("*******");
        clients.inMemory() // 使用in-memory存储
                .withClient("client")
                // client_id
                .secret(secret)
                // client_secret
                .authorizedGrantTypes("refresh_token", "authorization_code")
                // 该client允许的授权类型
                .redirectUris("https://open.bot.tmall.com/oauth/callback")
                .accessTokenValiditySeconds(60*60*24)
                //token过期时间
                .refreshTokenValiditySeconds(60*60*24)
                //refresh过期时间
                .scopes("all");
        // 允许的授权范围
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .tokenStore(tokenStore)
                .userDetailsService(userDetailService);
        endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");
    }


    @Bean
    public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) {
        // return new InMemoryTokenStore(); //使用内存存储令牌 tokeStore
        return new RedisTokenStore(redisConnectionFactory);
        //使用redis存储令牌
    }

}

           

HassIntegerFaceController(和homeassitant交互)

public class HassIntegerFaceController {

    @Value("${kyz.hassLongToken}")
    private String hassLongToken;
    @Value("${kyz.hassUrl}")
    private String hassUrl;


    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        // Do any additional configuration here
        return builder.build();
    }

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired()
    private UserService userService;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add() {
        return getDeviceInfo().toString();
    }

    /**
     * 获取设备列表
     *
     * @return
     */
    public List<Map<String, Object>> getDeviceInfo() {
        // header填充
        RequestCallback requestCallback = getRequestCallback();
        ResponseExtractor<ResponseEntity<JSONArray>> responseExtractor = restTemplate.responseEntityExtractor(JSONArray.class);
        // 执行execute(),发送请求
        ResponseEntity<JSONArray> response = restTemplate.execute(hassUrl + "/api/states", HttpMethod.GET, requestCallback, responseExtractor);

        JSONArray jsonArray = response.getBody();

        JSONArray jsonArrayCover = new JSONArray();
        Map<String, Object> haDevice = new HashMap<>();
        List<Map<String, Object>> haDeviceList = new ArrayList<>();
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject temp = jsonArray.getJSONObject(i);
            if (temp.getString("entity_id").startsWith("cover")) {
                jsonArrayCover.add(jsonArray.getJSONObject(i));
                haDevice = new HashMap<>();
                haDevice.put("deviceId", temp.getString("entity_id"));
                haDevice.put("friendly_name", temp.getJSONObject("attributes").getString("friendly_name"));
                haDevice.put("type", temp.getString("cover"));
                haDevice.put("state", temp.getString("state"));
                haDevice.put("deviceType", "curtain");
                redisUtil.set("ha_state_" + temp.getString("entity_id"), temp.getString("state"));
                haDeviceList.add(haDevice);
            }
            if (temp.getString("entity_id").startsWith("switch")) {
                jsonArrayCover.add(jsonArray.getJSONObject(i));
                haDevice = new HashMap<>();
                haDevice.put("deviceId", temp.getString("entity_id"));
                haDevice.put("friendly_name", temp.getJSONObject("attributes").getString("friendly_name"));
                haDevice.put("type", temp.getString("switch"));
                haDevice.put("state", temp.getString("state"));
                haDevice.put("deviceType", "switch");
                redisUtil.set("ha_state_" + temp.getString("entity_id"), temp.getString("state"));
                haDeviceList.add(haDevice);
            }
            if (temp.getString("entity_id").startsWith("light")) {
                jsonArrayCover.add(jsonArray.getJSONObject(i));
                haDevice = new HashMap<>();
                haDevice.put("deviceId", temp.getString("entity_id"));
                haDevice.put("friendly_name", temp.getJSONObject("attributes").getString("friendly_name"));
                haDevice.put("type", temp.getString("light"));
                haDevice.put("state", temp.getString("state"));
                haDevice.put("deviceType", "light");
                redisUtil.set("ha_state_" + temp.getString("entity_id"), temp.getString("state"));
                haDeviceList.add(haDevice);
            }
        }
        return haDeviceList;
    }

    /**
     * 控制控制
     *
     * @return
     */
    public Integer deviceControl(String deviceType, String entityId, String state, String postion) {
        // header填充
        RequestCallback requestCallback = getRequestCallback();
        ResponseExtractor<ResponseEntity<String>> responseExtractor = restTemplate.responseEntityExtractor(String.class);
        MultiValueMap<String, String> paramMap = new LinkedMultiValueMap<>();
        String url = "";
        // 执行execute(),发送请求
        switch (deviceType) {
            case "cover":
                paramMap.add("entity_id", "cover." + entityId);
                if (state.equalsIgnoreCase("open") || state.equalsIgnoreCase("close") || state.equalsIgnoreCase("pause")) {
                    url = "/api/services/cover/" + state + "_cover";
                } else if (state.equalsIgnoreCase("position")) {
                    paramMap.add("position", postion);
                    url = "/api/services/cover/set_cover_position";
                }
                break;
            case "switch":
                paramMap.add("entity_id", "switch." + entityId);
                if (state.equalsIgnoreCase("open")) {
                    url = "/api/services/" + deviceType + "/turn_on";
                } else if (state.equalsIgnoreCase("close")) {
                    url = "/api/services/" + deviceType + "/turn_off";
                }
                break;
            case "light":
                paramMap.add("entity_id", "light." + entityId);
                if (state.equalsIgnoreCase("open")) {
                    url = "/api/services/" + deviceType + "/turn_on";
                } else if (state.equalsIgnoreCase("close")) {
                    url = "/api/services/" + deviceType + "/turn_off";
                }
                break;
            case "fan":
                paramMap.add("entity_id", "switch." + entityId);
                if (state.equalsIgnoreCase("open")) {
                    url = "/api/services/" + deviceType + "/turn_on";
                } else if (state.equalsIgnoreCase("close")) {
                    url = "/api/services/" + deviceType + "/turn_off";
                }
                break;
            default:
        }
        ResponseEntity<String> response = restTemplate.execute(hassUrl + url, HttpMethod.POST, requestCallback, responseExtractor, paramMap);
        return response.getStatusCodeValue();
    }

    /**
     * 定义hedader
     *
     * @return
     */
    private RequestCallback getRequestCallback() {
        LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        headers.add("Authorization", "Bearer " + hassLongToken);
        headers.add("Content-Type", "application/json");
        // 获取单例RestTemplate
        HttpEntity request = new HttpEntity(headers);
        return restTemplate.httpEntityCallback(request, JSONArray.class);
    }
}
           

继续阅读