作者: iuu

Armbian自动挂载硬盘

查看系统所检测到的磁盘,这里的 sda1检测到的硬盘但是没有被挂载(注意:这里sda1 是’1’ 而不是’L’,有些可能是sdb1

lsblk         //查看信息

在根目录新建一个目录用于挂载硬盘,命令如下:

cd /.               //进入根目录
mkdir mnts            //新建目录名为‘mnts’可用'ls'查看

挂载新增的磁盘sda1(所有新增硬盘都在/dev/目录下)

mount /dev/sda1 /mnts/        //挂载到mnts
cd /mnts/                //进入挂载的硬盘 'ls'查看内容

开机自动挂载:

这条命令可以显示硬盘信息,并记下UUID,为下一步做准备,这里以sda1为例

blkid /dev/sda1

修改 /etc/fstab 即可。例如我就是在 fstab 最后添加这行:

UUID=722059EC2059B835   /mnts      ntfs    defaults        0 0
vi /etc/fstab         //修改fstab

最后保存并应用, 则成功自定挂载,开机也会自动挂载(注意:这里只对只一个硬盘有效)

mount -a             //应用并启动

斐讯N1盒子安装 Home Assistant Supervised(官方支持版本)

感谢以下链接教程

本文说明

本文主要是基于以上链接进行整理实操记录一下过程,希望有网友会用到吧
本文所使用的设备为PDD购买扩容64G版本的N1盒子
U盘使用大于8G的U盘
网络环境最好是 kexue 环境下
本文所使用的系统版本
https://github.com/ophub/amlogic-s9xxx-armbian/releases
文件名 Armbian_22.08.0_Aml_s905d_bullseye_5.10.134_server_2022.07.30.img.gz

Supervisor:

(中文=管理员)就是以前的HassIO/Hass.io,是用来管理和更新Home Assistant Core,管理操作系统,管理docker(HA和加载项),以及管理前三者之前的API和互动,它自己在docker容器里面,并且管理着其他容器。

Home Assistant Core:

这个以前就叫Home Assistant(core=核心)

Home Assistant OS(HAOS):

以前叫HassOS,是官方为树莓派打造的基于Linux的操作系统,包含了Home Assistant core, Supervisor,也就是完整的全套,可以直接安装于或者虚拟机,这是官方推荐安装方法

Home Assistant Supervised:

这个也是全套,跟HAOS的区别是可以装在普通Linux上因此适合更多硬件,N1用的就是这个。安装原理就是手动把docker,Home Assistant Core、Supervisor和其他所有必要组件安装在普通Linux系统上。为了花更多精力提升HA本上而不是debug各种兼容性问题,去年官方大幅减少支持的环境,目前唯一支持的是Debian 11,否则,轻则安装完后显示“不支持的操作系统”,重则无法安装)

操作步骤

1、将镜像写入大于8G的U盘 我这里用的是 balenaEtcher
2、U盘镜像制作完成后 N1断电 插入U盘【要插入HDIM旁边的USB】,连接显示屏,插上键盘,插网线,插上电源,进入系统。重新设置root密码、创建新用户、新用户密码、设置时区、设置语言等。
完事后执行: armbian-install 将系统安装到EMMC 【如果没有升级内存没有测试 据说很大内存会不够用】,等待提示迁移成功就好了 然后 poweroff 关机
3、拔掉U盘显示器键盘等 然后插电重新开机 这时候路由器会重新分配一个IP 然后用电脑ssh这个IP地址 用户名root 密码就是新修改的root密码
4、进入系统后执行以下代码 更新源 安装必要组件 安装docker

sudo -i
apt update && sudo apt upgrade -y && sudo apt autoremove -y
apt --fix-broken install
apt-get install jq curl avahi-daemon apparmor-utils udisks2 libglib2.0-bin network-manager dbus wget -y
curl -fsSL get.docker.com | sh

以上代码执行完毕后重启机器
可以关机后重新拔插电源 或者执行重启命令
poweroff 关机 reboot 重启

安装OS agent

执行以下代码 安装OS agent。Supervisor通过OS agent对接操作系统,官方已经强制要求
所有版本网址在这里
https://github.com/home-assistant/os-agent/releases
N1的架构是aarch64因此选择代码中这个文件

wget https://github.com/home-assistant/os-agent/releases/download/1.2.2/os-agent_1.2.2_linux_aarch64.deb
dpkg -i os-agent_1.2.2_linux_aarch64.deb

以上代码执行完毕后重启机器
可以关机后重新拔插电源 或者执行重启命令
poweroff 关机 reboot 重启

安装Homeassistant Supervised

依次执行以下命令

sudo -i
wget https://github.com/home-assistant/supervised-installer/releases/latest/download/homeassistant-supervised.deb
dpkg -i homeassistant-supervised.deb

过一会儿会进入一个蓝屏让你选择系统架构,选择qemuarm-64
安装完成之后 等着就行了 他会在后台下载很大的docker镜像 最大的1.5G左右 所以N1一定要科学上网环境
否则就会遇到我的问题 一直打不开端口访问不了

安装HACS

HACS(Home Assistant Community Store)即Home Assistant官方的插件商店,提供各种设备集成、前端装饰等的下载,是Home Assistant必备的插件。

1)安装HACS可以通过 https://github.com/hacs/integration/releases/ 下载离线包,解压后将hacs文件夹通过FTP软件拷贝至/usr/share/Hassio/homeassistant/custom_components(没有此路径的话新建一个)。

2)或者在SSH中输入以下命令一键安装。

wget -O - https://get.hacs.xyz | bash -

然后在后台界面选择“配置”-“系统”,右上角点击“重新启动”。
重启后,在“配置”-“设备与服务”中添加集成。
我没有用到蓝牙,如有用到蓝牙的可以参考其他教程处理 我尝试过但是失败哈哈哈哈 或者是我没搞明白怎么用 打算再刷OpenWrt当软路由用了

前端使用Canvas生成海报

最近项目需要涉及到前端海报合成分享功能,前端靠不上只能自己上...
现学现卖,相关链接 :

CSS代码如下

 * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
}

HTML代码如下

<button id="btn" style="height:50px;width:100%;margin:0 auto">点我生成海报</button>
<!--合成后的海报图-->
<img id="out" src="" style="width:100%;border:1px solid black;margin-top:20px;display: none;">

JS代码如下

 CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) {
        if (typeof text != 'string' || typeof x != 'number' || typeof y != 'number') {
            return;
        }
        var context = this;
        var canvas = context.canvas;
        if (typeof maxWidth == 'undefined') {
            maxWidth = (canvas && canvas.width) || 300;
        }
        if (typeof lineHeight == 'undefined') {
            lineHeight = (canvas && parseInt(window.getComputedStyle(canvas).lineHeight)) || parseInt(window.getComputedStyle(document.body).lineHeight);
        }
        // 字符分隔为数组
        var arrText = text.split('');
        var line = '';
        for (var n = 0; n < arrText.length; n++) {
            var testLine = line + arrText[n];
            var metrics = context.measureText(testLine);
            var testWidth = metrics.width;
            if (testWidth > maxWidth && n > 0) {
                context.fillText(line, x, y);
                line = arrText[n];
                y += lineHeight;
            } else {
                line = testLine;
            }
        }
        context.fillText(line, x, y);
    };

    function nickname(x, y) {
        context.fillStyle = 'grey';
        context.font = '32px  SC';
        context.wrapText("iuu 邀请你一起学习", x, y, 700, 60)
    }

    // 课程标题
    function goodsTitle(x, y) {
        context.fillStyle = 'black';
        context.font = '40px  bold';
        context.wrapText("第131讲:如何提升社会科学类核心期刊投稿的命中率", x, y, 700, 60)
    }

    // 描述
    function shopMsg(x, y) {
        context.fillStyle = 'grey';
        context.font = '25px  bold';
        context.wrapText("— 科研写作研究所", x, y, 700, 60)
    }
    // 图片描述
    function imgMsg(x, y) {
        context.fillStyle = 'grey';
        context.font = '24px  SC';
        context.fillText("长按保存图片", 310, 1250)
    }
    // 背景
    function bgImgScaleToFill(img) {
        var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
        var x = (canvas.width / 2) - (img.width / 2) * scale;
        var y = (canvas.height / 2) - (img.height / 2) * scale;
        context.drawImage(img, x, y, img.width * scale, img.height * scale);
    }

    // 商品图
    function goodsImgScaleToFit(img, x, y) {
        var scale = Math.min(canvas.width / img.width, canvas.height / img.height);
        context.drawImage(img, x, y, img.width * scale, img.height * scale);
    }
    // 二维码
    function qrImgScaleToFill(img, x, y) {
        context.drawImage(img, x, y, img.width, img.height);
    }
    // 头像
    function avatarImgScaleToFill(img, x, y, w, h) {
        context.drawImage(img, x, y, w, h);
    }
    var canvas = document.createElement("canvas");
    var context = canvas.getContext("2d");
    window.onload = function () {
        var btn = document.getElementById("btn");
        var QrImgUrl = "http://fenxiao-guogao.wukongkeyan.com/tm_ca6379f807f89f5e96ebbce4c81203f7_20220719155912.png"
        var AvatarImgUrl = "http://wechatavator-1252524126.file.myqcloud.com/appzissbssa6278/image/compress/u_api_61edfdfd1c284_PWGOuVUIpM.png"
        var GoodsImgUrl = "http://wechatapppro-1252524126.file.myqcloud.com/appzissbssa6278/image/b_u_6170d73860ce7_73CZtcAY/l4z0x8yo02hu.jpg"

        btn.onclick = function () {
            var all = {avatarImg: null, goodsImg: null, qrImg: null}

            var QrImg = new Promise(function (resolve, reject) {
                var qrImg = new Image();
                qrImg.crossOrigin = 'anonymous';
                qrImg.src = QrImgUrl
                qrImg.onload = function () {
                    resolve(qrImg);
                }
            })

            var AvatarImgCircle = new Promise(function (resolve, reject) {
                var cs = document.createElement('canvas');
                var ctx = cs.getContext('2d');
                var avatar = new Image();
                avatar.crossOrigin = 'anonymous';
                avatar.src = AvatarImgUrl
                avatar.onload = function () {
                    cs.width = avatar.width;
                    cs.height = avatar.height;
                    var width = avatar.width;
                    var height = avatar.height;
                    var circle = {x: width / 2, y: height / 2, r: width / 2}
                    ctx.clearRect(0, 0, width, height);
                    ctx.save();
                    ctx.beginPath();
                    ctx.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2, false);
                    ctx.clip();
                    ctx.drawImage(avatar, 0, 0);
                    ctx.restore();
                    // var base64 =
                    var avatarImg = new Image()
                    avatarImg.src = cs.toDataURL("image/png");
                    resolve(avatarImg);
                }
            })
            var GoodsImg = new Promise(function (resolve, reject) {
                var goodsImg = new Image();
                goodsImg.crossOrigin = 'anonymous';
                goodsImg.src = GoodsImgUrl
                goodsImg.onload = function () {
                    resolve(goodsImg);
                }
            })
            Promise.all([QrImg, GoodsImg, AvatarImgCircle]).then(res => {
                all.avatarImg = res[2]
                all.goodsImg = res[1]
                all.qrImg = res[0]
                var bgImg = new Image();
                bgImg.crossOrigin = 'anonymous';
                bgImg.src = 'http://commonresource-1252524126.cdn.xiaoeknow.com/image/l5c2twsm0lm1.png';
                bgImg.onload = function () {
                    canvas.width = 750;
                    canvas.height = 1334;
                    bgImgScaleToFill(bgImg)
                    goodsImgScaleToFit(all.goodsImg, 0, 0)
                    qrImgScaleToFill(all.qrImg, 300, 1060)
                    avatarImgScaleToFill(all.avatarImg, 25, 680, 80, 80)
                    nickname(145, 740)
                    goodsTitle(40, 900)
                    shopMsg(40, 1020)
                    imgMsg(310, 1250)
                    var base64 = canvas.toDataURL();
                    document.getElementById('out').src = base64;
                    document.getElementById('out').style.display = "block";
                }
            })
        }
    }

这个代码主要是进行海报合成,其中注意点事 所有得图片都必须加载完成之后进行合成操作 所以用到了Promise来进行处理

goutil/dump – 打印漂亮易读的go数据

gookit/goutil/dump - 是一个golang数据打印工具包,可以打印出漂亮易读的go slice, map, struct数据。

主要功能:
使用简单,直接调用 dump.P(vars...) 即可
支持所有的基础数据类型
支持slice, map, struct数据结构
支持传入打印多个变量
默认输出调用位置,方便使用
支持自定义部分能力,如 缩进,色彩主题等

打印基础类型

package main

import "github.com/gookit/goutil/dump"

// rum demo:
//     go run ./dump/_examples/basic_types.go
func main() {
    dump.P(
        nil, true,
        12, int8(12), int16(12), int32(12), int64(12),
        uint(22), uint8(22), uint16(22), uint32(22), uint64(22),
        float32(23.78), float64(56.45),
        'c', byte('d'),
        "string",
    )
}

打印slice

打印 array, slice 都会一行一个元素输出,同时会在最后输出长度。

package main

import "github.com/gookit/goutil/dump"

// rum demo:
//     go run ./dump/_examples/slice.go
func main() {
    dump.P(
        []byte("abc"),
        []int{1, 2, 3},
        []string{"ab", "cd"},
        []interface{}{
            "ab",
            234,
            []int{1, 3},
            []string{"ab", "cd"},
        },
    )
}

打印map

打印map数据结构,会一行一个元素输出,同时会在最后输出map长度。

package main

import "github.com/gookit/goutil/dump"

// rum demo:
//     go run ./map.go
//     go run ./dump/_examples/map.go
func main() {
    dump.P(
        map[string]interface{}{
            "key0": 123,
            "key1": "value1",
            "key2": []int{1, 2, 3},
            "key3": map[string]string{
                "k0": "v0",
                "k1": "v1",
            },
        },
    )
}

打印struct

打印struct数据,指针类型会自动打印底层真实数据

package main

import (
    "fmt"

    "github.com/gookit/color"
    "github.com/gookit/goutil/dump"
)

// rum demo:
//     go run ./struct.go
//     go run ./dump/_examples/struct.go
func main() {
    s1 := &struct {
        cannotExport map[string]interface{}
    }{
        cannotExport: map[string]interface{}{
            "key1": 12,
            "key2": "abcd123",
        },
    }

    s2 := struct {
        ab string
        Cd int
    }{
        "ab", 23,
    }

    color.Infoln("- Use fmt.Println:")
    fmt.Println(s1, s2)

    color.Infoln("\n- Use dump.Println:")
    dump.P(
        s1,
        s2,
    )
}

自定义dumper

支持自定义dumper一些选项。如 缩进,色彩主题等

// Options for dump vars
type Options struct {
    // Output the output writer
    Output io.Writer
    // NoType dont show data type TODO
    NoType bool
    // NoColor don't with color
    NoColor bool
    // IndentLen width. default is 2
    IndentLen int
    // IndentChar default is one space
    IndentChar byte
    // MaxDepth for nested print
    MaxDepth int
    // ShowFlag for display caller position
    ShowFlag int
    // MoreLenNL array/slice elements length > MoreLenNL, will wrap new line
    // MoreLenNL int
    // CallerSkip skip for call runtime.Caller()
    CallerSkip int
    // ColorTheme for print result.
    ColorTheme Theme
}
1 8 9 10 11 12 16