HomeAssistant自定义组件学习-【二】

news2024/11/5 10:10:57

#要说的话#

前面把中盛科技的控制器组件写完了。稍稍熟悉了一些HA,现在准备写窗帘控制组件,构想的东西会比较多,估计有些难度,过程会比较长,边写边记录吧!

#设备和场景环境#

使用的是Novo的电机,淘宝链接在【前言】文章中有,轨道应该是佳丽斯的,买电机的时候一起配的。电机提供的是RS485接口,所以需要增加一个RS485的服务器,我选用的是“亿佰特”的网口转RS485的模块,型号是NA111-A(使用220V电源供电,就避免了再加一个模块)统一放置在机柜那里,把原来的网络拿两路出来(蓝色和棕色组)作为485的线路,保留100M的网线功能。远端使用的是多功能的面板,带2+3的电源插口和8+4的网络口,网络口刚好可以分成百兆网和485接口。

#组件思路#

流程:选择Novo组件-->选择485设备类型-->发现485设备-->(可选:配置485设备)

-->通过485连接Novo设备-->配置Novo设备地址(需要按SET按钮)-->设置电机转向

-->设置开合范围-->完成配置

思路:

  1. 1、选择组件后,提供界面选择使用的485设备类型,使用下拉框给用户选择;
  2. 2、依据选择的485设备类型触发对应的自发现流程,并列出发现的485设备;
  3. 3、用户点击选择485设备,组件建立与485设备的链接,并显示通讯正常;
  4. 4、组件通过485设备向Novo电机发送查询命令,有返回则说明链路建立成功;
  5. 5、由于是并联了多个Novo电机,所以会返回多个查询回复,所以只能轮循查询,需要提供界面配置的Novo电机地址和通道;
  6. 6、点击485设备条目进行配置,配置界面自动生成Novo电机地址和通道,点击提交,然后到Novo电机上按下相应配置按钮,完成Novo电机的地址配置(电机正反转两次),该条目移至已经配置好的列表;
  7. 7、在已经配置好地址的列表里点击Novo电机,进入到电机的配置:可以配置电机转向、窗帘开合范围,检查开、合是否正确,都正确无误后完成配置(后续版本再做)。
  8. #代码实现#

  9. 通过命令:
  10. python3 -m script.scaffold integration

    初始化组件代码。添加discover方法:

  11. 
    def ebyte_discover(ip_address: str | None = None) -> dict[str, EbyteConfig]:
        """亿佰特-发现设备方法."""
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sock.bind(("", BROADCAST_SRC_PORT))
        sock.settimeout(2)
        found_devices: dict[str, EbyteConfig] = {}
        if ip_address is None:
            addrs = enum_all_broadcast()
        else:
            addrs = [ip_address]
        for addr in addrs:
            try:
                sock.sendto(BROADCAST_MSG, (addr, BROADCAST_PORT))
            except Exception:  # noqa: BLE001
                _LOGGER.warning(f"Can't access network {addr}")  # noqa: G004
        while True:
            try:
                data, addr = sock.recvfrom(512)
                # 返回:fd 06 54 14 a7 dc 74 16 03 01
                # 返回:fd 06 54 14 a7 dc 72 83 03 01
                # 分析:fd 06 为帧头
                #       54 14 a7 dc 74 16 为MAC地址
                #       03 01 为帧尾
                 mac = ebyte_get_mac(data)
                if len(mac[0]) > 6:
                    found_devices[mac[0]] = EbyteConfig(
                        mac[0], "Ebyte_RS485", mac[0], addr[0], 0
                    )
                    found_devices[mac[0]].set_mac_bytes(mac[1])
    
            except TimeoutError:
                break
            except OSError as e:
                _LOGGER.error(f"Socket error: {e!r}")  # noqa: G004
        if len(found_devices) > 0:
            # 查询相关信息
            for conf in found_devices.values():
                bts: bytearray = QUERY_CMD_FORMAT.copy()
                for i in range(len(conf.mac_bytes)):
                    bts[i + QUERY_CMD_FORMAT_MAC_IDX] = conf.mac_bytes[i]
                # 获取名称、版本号和序列号
                bts[QUERY_CMD_FORMAT_CMD_IDX] = 0x05
                sock.sendto(bts, (conf.base_ip, BROADCAST_PORT))
                res = sock.recv(1024)
                mac = ebyte_get_mac(res)[0]
                if mac in found_devices:
                    found_devices[mac].load_data(res)
                # 获取网络配置
                bts[QUERY_CMD_FORMAT_CMD_IDX] = 0x00
                sock.sendto(bts, (conf.base_ip, BROADCAST_PORT))
                res = sock.recv(1024)
                mac = ebyte_get_mac(res)[0]
                if mac in found_devices:
                    found_devices[mac].load_data(res)
        return found_devices

    这里绑定UDP的源端口,是转为亿佰特的设备返回的数据是另外一个固定端口。不绑定源端口的话,返回数据收不到。

  12. 修改config_flow.py中的函数,显示485设备列表:

  13. 
        async def async_step_user(
            self, user_input: dict[str, Any] | None = None
        ) -> ConfigFlowResult:
            """Handle the initial step."""
            errors: dict[str, str] = {}
            ent_data: dict[str, Any] = {}
            if user_input is not None:
                try:
                    # 获取用户选择的设备ID
                    conf: EbyteConfig = self.get_ebyteconfig_by_id(
                        user_input[CONF_USER_INPURT_ID]
                    )
                    ent_data[CONF_HOST] = conf.base_ip
                    ent_data[CONF_PORT] = conf.base_port
                    ent_data[CONF_MAC] = conf.base_mac
                except Exception:  # noqa: BLE001
                    _LOGGER.exception("Unexpected exception")
                    errors["base"] = "unknown"
    
                dp: dict[str, str] = {}
                dp["MAC"] = conf.mac_bytes.hex(":")
                dp["IP"] = conf.base_ip
                dp["Port"] = conf.base_port
                ret: ConfigFlowResult = self.async_create_entry(
                    # 生成集成条目
                    title=f"Ebyte Device[{conf.base_ip}]",
                    description="This is a Ebyte RS485 communication device!",
                    description_placeholders=dp,
                    options=dp,
                    data=ent_data,
                )
                self._ebytelink = static_ebyte_manager.get_link_by_mac(
                    conf.base_mac, conf.base_ip, conf.base_port
                )
                # # 创建对应的RS485通讯链路设备
                # 错误:在此处不能创建设备,只能在__init__.py里创建
            else:  # noqa: RET505
                self._confs = ebyte_discover()
                options: dict[str, str] = {}
                if len(self._confs) < 1:
                    _LOGGER.error("No ebyte communications!")
                else:
                    for conf in self._confs.values():
                        options[conf.base_mac] = (
                            f"{conf.base_name}[{conf.base_ip}:{conf.base_port}]"
                        )
                ops = []
                # 从系统中获取已经配置了的条目
                clist: list[ConfigEntry] = self.hass.config_entries.async_entries(DOMAIN)
                cd: dict[str, str] = {}
                for cf in clist:
                    cd[cf.data[CONF_MAC]] = cf.data[CONF_HOST]
                for k, v in options.items():
                    if k not in cd:
                        ops.append(SelectOptionDict(value=k, label=v))
                if len(ops) > 0:
                    ebyteschema = vol.Schema(
                        {
                            vol.Required(CONF_USER_INPURT_ID): SelectSelector(
                                SelectSelectorConfig(
                                    options=ops,
                                    mode=SelectSelectorMode.DROPDOWN,
                                )
                            ),
                        }
                    )
                else:
                    ebyteschema = vol.Schema({"ERROR:": "No Ebyte RS485 device founded!"})
    
                return self.async_show_form(
                    step_id="user", data_schema=ebyteschema, errors=errors
                )

    这里使用SelectSelector提供选择项。

  14. 生成的效果如下:

要显示集成条目右边的配置按钮,需要在Flow类中添加指定的方法:

    @staticmethod
    @callback
    def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
        """增加本函数后,会在集成条目位置增加“配置”按钮."""
        return NovoOptionsFlow(config_entry)

 完成RS485设备(集成条目)的添加,后续就是在点击“配置”的时候,弹出界面给Novo电机写地址和通道号,代码放在NovoOptionsFlow中,如下:


    async def async_step_init(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Manage the options."""
        errors: dict[str, str] = {}
        if user_input is not None:
            # 有输入信息
            if not user_input[CONFIG_FLOW_TYPE]:
                # 未知的步骤
                _LOGGER.error("Unkown flow type!")
            elif user_input[CONFIG_FLOW_TYPE] == OPTIONS_FLOW_TYPE_SET_ADDRESS:
                # 当前为输入电机地址步骤,需要配置电机
                idb = bytes.fromhex(user_input[CONFIG_ID])
                did: int = int.from_bytes(idb, "big")
                channel: int = user_input["channel"]
                edata: dict[str, Any] = {}
                res = await self._device.async_set_motor_addr(did, channel)
                if res:
                    # 写入成功,则保存相关信息
                    edata[CONF_MAC] = self._entry.data[CONF_MAC]
                    edata[CONF_HOST] = self._entry.data[CONF_HOST]
                    edata[CONF_PORT] = self._entry.data[CONF_PORT]
                    edata[CONFIG_ID] = did
                    edata[CONFIG_CHANNEL] = channel
                    self._save_device_config(edata)
                    return self.async_create_entry(title="", data=edata)

        # 自动生成电机ID
        ida = np.random.random_integers(161, 254, size=(2))
        idab: bytearray = bytearray(2)
        idab[0] = ida[0]
        idab[1] = ida[1]
        ebyteschema = vol.Schema(
            {
                vol.Required(
                    CONFIG_FLOW_TYPE, default=OPTIONS_FLOW_TYPE_SET_ADDRESS
                ): vol.In(ADD_WAY),
                vol.Required(
                    schema=CONFIG_ID,
                    description="Enetry RS485 address.",
                    default=idab.hex(" "),
                ): str,
                vol.Required(
                    schema=CONFIG_CHANNEL, description="Entery Novo channel.", default=4
                ): int,
            }
        )

        return self.async_show_form(
            step_id="init", data_schema=ebyteschema, errors=errors
        )

显示效果:

在这里做了选择器,最初是想把设置地址和配置旋转方向一起的,所以留在这里了。后续再完善。

这里配置完成后,我不知道应该怎么保存配置并使用,看了美的的组件,就直接用他的代码了,就是保存json文件到本地,使用的时候读取就是了。对应的代码:

    def _save_device_config(self, data: dict):
        os.makedirs(
            self.hass.config.path(f"{STORAGE_PATH}/{data[CONF_MAC]}"), exist_ok=True
        )
        record_file = self.hass.config.path(
            f"{STORAGE_PATH}/{data[CONF_MAC]}/{data[CONFIG_ID]}.json"
        )
        save_json(record_file, data)

 添加并配置Novo电机就完成了,后而就是怎么加载这些实体,加载的工作必须放在__init__.py文件中,不能放在其他地方,回到__init__.py文件,修改对应的代码:


# Update entry annotation
async def async_setup_entry(hass: HomeAssistant, entry: NovoConfigEntry) -> bool:
    """Set up Novo from a config entry."""

    # 说明:此方法,每个集成条目都会调用一次

    # 1. Create API instance
    # 2. Validate the API connection (and authentication)
    # 3. Store an API object for your platforms to access

    ip: str = entry.data[CONF_HOST]
    port: int = entry.data[CONF_PORT]
    mac: str = entry.data[CONF_MAC]
    link: EbyteRS485Link = static_ebyte_manager.get_link_by_mac(mac, ip, port)
    if link is None:
        _LOGGER.error(f"Device[{mac},{ip}:{port}] lost!")  # noqa: G004
    # 注册通讯设备:当前是亿佰特的RS485设备
    device_registry = dr.async_get(hass)
    if link.is_connected:
        device_registry.async_get_or_create(
            config_entry_id=entry.entry_id,
            configuration_url=f"http://{link.tcp_ip}:{link.tcp_port}/",
            identifiers={("mac", mac)},
            connections={("mac", mac), ("ip", link.tcp_ip), ("port", link.tcp_port)},
            manufacturer="Ebyte Tech",
            model=link.DOMAIN,
            name=link.base_name,
            serial_number=mac,
            sw_version=link.version,
            translation_key="Ebyte communication",
        )

    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

    novo: NovoDevice = static_novo_device_manager.get_novo_device(mac, link)
    # conf: NovoConfig
    d_id = 0
    c_id = 0

    # 从保存的配置文件中获取实体
    jsons = _load_device_config(hass, mac)
    if len(jsons) > 0:
        for js in jsons:
            # _LOGGER.error(f'get json:{js}')
            d_id = js[CONFIG_ID]
            c_id = js[CONFIG_CHANNEL]
            c_mac = js[CONF_MAC]
            if c_mac == mac:
                # 发送消息给实体类,创建实体
                async_dispatcher_send(
                    hass,
                    DATA_DISCOVER_COMPONENT.format(NovoCoverEntry.DOMAIN),
                    novo,
                    entry,
                    d_id,
                    c_id,
                )

    return True

 因为考虑到需要支持多RS485设备,不同的电机是挂在不同的RS485设备上的,加载的时候就需要一一对应上,在这里的思路就是使用RS485设备的MAC地址作为文件夹区分,并通过文件遍历的方式获取到配置的json文件(每个json文件对应一台电机),遍历的代码:

def _load_device_config(hass: HomeAssistant, device_id):
    # 列表出文件夹下所有的文件
    files = []
    jsons = []
    pypath = hass.config.path(f"{STORAGE_PATH}/{device_id}/")
    if not os.path.isdir(pypath):
        _LOGGER.error(f"file path :{pypath} not exists!")  # noqa: G004
        return jsons
    for filename in os.listdir(pypath):
        filepath = os.path.join(pypath, filename)
        if os.path.isfile(filepath):
            files.append(filename)
            record_file = hass.config.path(f"{STORAGE_PATH}/{device_id}/{filename}")
            jsons.append(load_json(record_file, default={}))
    return jsons

在__init__.py中怎么把配置信息传到实体类,在我的上一篇文章中已经说了,不清楚的可以看看。这里使用了if c_mac == mac:进行限制是否生成实体,是因为在多个RS485(集成条目)的情况下,相应的代码就是执行多次,有冲突后,就不能正确给实体配置上对应的链路。

最后就是实体类Cover了,完整代码如下:

"""窗帘实体类."""

import logging
from typing import Any

from homeassistant.components.cover import (
    ATTR_POSITION,
    CoverEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DATA_DISCOVER_COMPONENT
from .core.NovoDevice import NovoDevice

_LOGGER = logging.getLogger("novocover")


async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """配置窗帘实体."""

    @callback
    def async_discover(
        device: NovoDevice, entry: ConfigEntry, id: int, ch: int
    ) -> None:
        """发现电机回调及添加方法."""
        nentry = NovoCoverEntry(device, entry, id, ch)
        async_add_entities([nentry])

    hass.data[DATA_DISCOVER_COMPONENT.format(NovoCoverEntry.DOMAIN)] = (
        async_dispatcher_connect(
            hass,
            DATA_DISCOVER_COMPONENT.format(NovoCoverEntry.DOMAIN),
            async_discover,
        )
    )


class NovoCoverEntry(CoverEntity):
    """Novo电机窗帘实体类."""

    DOMAIN = "cover"

    _attr_has_entity_name = True

    _id: int
    _ch: int
    _entry: ConfigEntry
    _device: NovoDevice
    _pos: int
    _closed: bool

    def __init__(
        self, device: NovoDevice, entry: ConfigEntry, id: int, ch: int
    ) -> None:
        """初始化Novo电机窗帘实体."""
        super().__init__()
        self._id = id
        self._entry = entry
        self._unique_id = f"{self.DOMAIN}.novo_curtain_{id}"
        self.entity_id = self._unique_id
        self._closed = True
        self._ch = ch
        self._device = device
        self._pos = 0
        self._attr_unique_id = f"{self.DOMAIN}.novo_curtain_{id}"
        self._attr_name = f"NovoCurtain_{id}"

    @property
    def unique_id(self) -> str | None:
        """设备标识符."""
        return self._attr_unique_id

    @property
    def is_closed(self) -> bool:
        """窗帘是否已经关闭."""
        return self._closed

    def open_cover(self, **kwargs: Any) -> None:
        """打开窗帘."""
        self._device.async_open_cover_by_id(self._id, self._ch, 0)

    async def async_open_cover(self, **kwargs: Any) -> None:
        """异步打开窗帘."""
        return await self._device.async_open_cover_by_id(self._id, self._ch, 0)

    def close_cover(self, **kwargs: Any) -> None:
        """关闭窗帘."""
        self._device.async_close_cover_by_id(self._id, self._ch, 100)

    async def async_close_cover(self, **kwargs: Any) -> None:
        """异步关闭窗帘."""
        return await self._device.async_close_cover_by_id(self._id, self._ch, 100)

    def set_cover_position(self, **kwargs: Any) -> None:
        """设置窗帘位置."""
        position = int(kwargs.get(ATTR_POSITION))
        self._device.async_set_cover_position_by_id(self._id, self._ch, position)

    async def async_update(self) -> None:
        """更新代码."""
        await self._device.async_query_position_by_id(self._id, self._ch)
        self._pos = self._device.get_position_by_id(self._id)
        self._attr_current_cover_position = self._pos
        if self._pos >= 99:
            self._attr_is_closed = False
            self._closed = False
        else:
            self._attr_is_closed = True
            self._closed = True

最后运行结果:

学到的知识点:

 1、使用界面获取用户输入:async_show_form方法的使用;

2、集成条目的使用:async_create_entry方法;

3、集成条目配置:async_get_options_flow方法

存在的问题:

1、Novo电机会主动发送信息,需要在链路代码里增加循环读取socket信息的功能,而不是现在一发一收的模式;

2、Novo电机的Update方法是直接调用链路模块发送命令的,有可能会出现冲突的情况,需要封装链路模块,在模块中处理好冲突问题;

3、使用集成条目的配置功能添加Novo电机时,不会自动删除原有的配置文件(json);

4、使用配置功能添加Novo电机后,不会自动刷新实体列表,需要手动“重新加载”集成条目。

增加了自动化:早上7点05分,开灯、开窗帘。早上自动打开了。

Novo窗帘的组件基本功能完成,家里的零冷水泵到了,后续就是把零冷水泵添加到HA中。还有人体传感器、空气质量传感器、电动水阀都有了,都得花时间把这些东西加进去……,又是得花时间折腾……

另外:最近想把项目放到HA里面去,因为需要用到厂家的LOGO,目前在跟厂家沟通,获得授权后,把LOGO加上,就可以放到HA里面了。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2232500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux脚本数组与字符串

文章目录 打印数组与长度数组遍历数组赋值下标索引访问切片追加()删除关联数组(像map)字符串字符拼接截取子串字符串长度字符串替换模式匹配截取分割字符串大小写转换 打印数组与长度 ${arrayName[*]} 打印数组${arrayName[]} 打印数组${#arrayName[*]} 打印数组长度${#arrayN…

闯关leetcode——3289. The Two Sneaky Numbers of Digitville

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/the-two-sneaky-numbers-of-digitville/description/ 内容 In the town of Digitville, there was a list of numbers called nums containing integers from 0 to n - 1. Each number was suppos…

#Jest进阶知识:整合 webpack 综合练习

这一小节&#xff0c;我们来做一个综合的练习&#xff0c;该练习会整合&#xff1a; typescriptwebpackjest 准备工作 首先创建项目目录&#xff0c;通过 npm init -y 进行初始化。 整个项目我们打算使用 typescript 进行开发&#xff0c;因此需要安装 typescript npm i t…

MATLAB——矩阵操作

内容源于b站清风数学建模 数学建模清风老师《MATLAB教程新手入门篇》https://www.bilibili.com/video/BV1dN4y1Q7Kt/ 目录 1.MATLAB中的向量 1.1向量创建方法 1.2向量元素的引用 1.3向量元素修改和删除 2.MATLAB矩阵操作 2.1矩阵创建方法 2.2矩阵元素的引用 2.3矩阵…

原生鸿蒙应用市场开发者服务的技术解析:从集成到应用发布的完整体验

文章目录 引言一、鸿蒙原生应用的高效开发二、用户隐私保护&#xff1a;安全访问管理三、开发者实用工具&#xff1a;应用分析与A/B测试四、应用审核与分发&#xff1a;快速上线4.1 应用加密&#xff1a;保护代码安全4.2 自动化测试与检测前移&#xff1a;提升应用质量 五、结语…

基于SSM+微信小程序的社团登录管理系统(社团1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 2、项目技术 3、开发环境 4、功能介绍 1、项目介绍 基于SSM微信小程序的社团登录管理系统实现了管理员及社团、用户。 1、管理员实现了首页、用户管理、社团管理、社团信息管理、社…

虚拟化环境中的精简版 Android 操作系统 Microdroid

随着移动设备的普及和应用场景的多样化&#xff0c;安全性和隐私保护成为了移动操作系统的重要课题。Google推出的Microdroid&#xff0c;是一个专为虚拟化环境设计的精简版Android操作系统&#xff0c;旨在提供一个安全、隔离的执行环境。本文将详细介绍Microdroid的架构、功能…

手动搭建 Java Web 环境

操作场景 本文档介绍如何在 Linux 操作系统的腾讯云云服务器&#xff08;CVM&#xff09;上手动搭建 Java Web 环境。 进行手动搭建 Java Web 环境&#xff0c;您需要熟悉 Linux 命令&#xff0c;例如 CentOS 环境下通过 YUM 安装软件 等常用命令&#xff0c;并对所安装软件使…

WPF+MVVM案例实战与特效(二十四)- 粒子字体效果实现

文章目录 1、案例效果2、案例实现1、文件创建2.代码实现3、界面与功能代码3、总结1、案例效果 提示:这里可以添加本文要记录的大概内容: 2、案例实现 1、文件创建 打开 Wpf_Examples 项目,在 Views 文件夹下创建窗体界面 ParticleWindow.xaml,在 Models 文件夹下创建粒子…

「Mac畅玩鸿蒙与硬件18」鸿蒙UI组件篇8 - 高级动画效果与缓动控制

高级动画可以显著提升用户体验&#xff0c;为应用界面带来更流畅的视觉效果。本篇将深入介绍鸿蒙框架的高级动画&#xff0c;包括弹性动画、透明度渐变和旋转缩放组合动画等示例。 关键词 高级动画弹性缓动自动动画缓动曲线 一、Animation 组件的高级缓动曲线 缓动曲线&#…

Golang--数组、切片、映射

1、数组 1.1 数组类型 var 数组名 [数组大小]数据类型 package main import "fmt"func main(){//1、定义一个数组var arr1 [5]intarr1[0] 100arr1[1] 200fmt.Println(arr1) //[100 200 0 0 0] } 1.2 数组的初始化方式 package main import "fmt" func …

Android音频进阶之PCM设备创建(九十三)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…

【已解决】C# NPOI如何设置单元格格式

前言 设置单元格格式我们做表格必须要的一步&#xff0c;那么如何对单元格进行设置呢&#xff1f;直接上图看看效果图先&#xff0c;我做的是一个居中然后字体变化的操作&#xff0c;其他的查他的手册即可。 解决方法 直接上代码 IWorkbook excelDoc new XSSFWorkbook();…

系统学习算法:专题一 双指针

题目一&#xff1a; 算法原理&#xff1a; 首先我们可以对这道题目进行题目分类&#xff0c;像这种对数组以某种标准而进行一定的划分的题目&#xff0c;我们统称为数组分块问题&#xff0c;其中使用到的算法就是双指针算法&#xff0c;这里的指针并非真正int*这种&#xff0c…

Python异常检测 - LSTM(长短期记忆网络)

系列文章目录 Python异常检测- Isolation Forest&#xff08;孤立森林&#xff09; python异常检测 - 随机离群选择Stochastic Outlier Selection (SOS) python异常检测-局部异常因子&#xff08;LOF&#xff09;算法 Python异常检测- DBSCAN Python异常检测- 单类支持向量机(…

【双指针】【数之和】 LeetCode 633.平方数之和

算法思想&#xff1a; 双指针枚举i,j&#xff1b;类似三数之和 class Solution { public:bool judgeSquareSum(int c) {long long sum0;vector<int> dp;dp.push_back(0);long long start1;while(sum < c){sum start *start;if(sum>c) break;else dp.push_back(…

前端Nginx的安装与应用

目录 一、前端跨域方式 1.1、CORS(跨域资源共享) 1.2、JSONP(已过时) 1.3、WebSocket 1.4、PostMessage 1.5、Nginx 二、安装 三、应用 四、命令 4.1、基本操作命令 4.2、nginx.conf介绍 4.2.1、location模块 4.2.2、反向代理配置 4.2.3、负载均衡模块 4.2.4、通…

Openlayers高级交互(18/20):根据feature,将图形适配到最可视化窗口

本示例的目的是介绍如何在vue+openlayers中使用extent,使用feature fit的方式来适配窗口。当加载到页面上几个图形要充分展示在窗口的时候,可以用这种方式来平铺到页面中。 效果图 专栏名称内容介绍Openlayers基础实战 (72篇)专栏提供73篇文章,为小白群体提供基础知识及示…

每日OJ题_牛客_相差不超过k的最多数_滑动窗口_C++_Java

目录 牛客_相差不超过k的最多数_滑动窗口 题目解析 C代码 Java代码 牛客_相差不超过k的最多数_滑动窗口 相差不超过k的最多数_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a; 给定一个数组&#xff0c;选择一些数&#xff0c;要求选择的数中任意两数差的绝对值不超过 …

初始JavaEE篇——多线程(5):生产者-消费者模型、阻塞队列

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 文章目录 阻塞队列生产者—消费者模型生产者—消费者模型的优势&#xff1a;生产者—消费者模型的劣势&#xff1a; Java标准库中的阻…