安卓上安装linux
参考文章 Convert-an-Android-Device-to-Linux
- busybox可以模拟部分linux指令和操作, 但毕竟不是linux
- Termux更像是linux, 有自己的包管理,能装node, python, 而且不需要root,但工作起来毕竟还是受限
- 要装任意linux发行版,怎么办, 可以安装Linux Depoly或者Complete Linux Installer
- 还可以装模拟器Limbo PC Emulator
参考文章 Convert-an-Android-Device-to-Linux
根据网上的中文版《FreeRTOS实时内核实用指南》,主要有这几部分内容
其实核心是多任务。
根据ESP8266 FreeRTOS的指南,可以完成如下事项:
在windows上比较简单:
import msvcrt
ch = msvcrt.getch()
其他平台,可以安装getch模块
pip install getch
然后
import getch
# ...
char = getch.getch() # User input, but not displayed on the screen
# or
char = getch.getche() # also displayed on the screen
本网站[idarc.cn]使用了dns解析服务器上设置显性URL的方式来将二级域名grav.idarc.cn进行重定向到 idarc.cn/grav, 但对于主域名idarc.cn也想重定向到idarc.cn/grav上, 这儿有一篇很好很详细的文章:How to Redirect a Web Page
由于我是托管在php主机上, 所以无法随意改主机服务器的设置, 只能修改网页. 我使用了JavaScript的window.location转向的方式, 但似乎搜索引擎只有google能搜索出来, 而百度和bing都不行. 下来试试meta的效果看如何:
<meta http-equiv="refresh" content="0; URL='http://new-website.com'" />
2020-2-21更新
很多次想搞一搞android的自动化界面测试, 这下开始啦!
主要参考的Appium官方文档:
失效文档
Android文档
com.android.support.test.uiautomator:uiautomator
转移为 androidx.test.uiautomator:uiautomator
, 新的文档地址wd.js文档
Webdriver文档
在windows上用appium server, 可以用node安装或者安装Desktop版本. 推荐安装Desktop版本,下载地址在github上
node安装方式:
npm install -g appium
appium
可惜的是, 他需要从googleapis下载一个chromedriver的包, 你知道的,这是不可能的, 所以会一直卡在这儿:
npm install -g appium
C:\Users\Jac\AppData\Roaming\npm\appium -> C:\Users\Jac\AppData\Roaming\npm\node_modules\appium\build\lib\main.js
> appium-chromedriver@3.0.1 install C:\Users\Jac\AppData\Roaming\npm\node_modules\appium\node_modules\appium-chromedriver
> node install-npm.js
info Chromedriver Install Installing Chromedriver version '2.30' for platform 'win' and architecture '32'
info Chromedriver Install Opening temp file to write chromedriver_win32 to...
info Chromedriver Install Downloading https://chromedriver.storage.googleapis.com/2.30/chromedriver_win32.zip...
这儿你可以使用cmder, 设置代理, 然后再下载.
另外还可以老老实实下载desktop版本试试. 下载地址在github上
可以看appium的官方文档,步骤还是不少的, 下面简要说一下:
连接手机
用线连接手机很简单, 要记得点击手机中设置->版本号5次打开开发者模式->打开USB调试.
连接安卓夜神模拟器
连接安卓夜神模拟器的方式, 先在模拟器设置中把平板模式调成手机模式, 然后再点击手机中设置->版本号5次打开开发者模式->打开USB调试.
然后再命令行输入:
adb connect 127.0.0.1:62001
adb devices确定连接的设备有且只有一个. 如果有问题, 你可以重启adb:
adb kill-server && adb devices
测试安卓得知道包名和activity名字, 可测试的人儿哪儿知道...好啦有个办法(linux上用grep替代findstr):
adb shell dumpsys window windows | findstr mFocusedApp
我关注的我们isen的输出是这样的:
adb shell dumpsys window windows | findstr isen
Window #5 Window{f9a1670 u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity}:
mOwnerUid=10242 mShowToOwnerOnly=true package=com.isen.trace.police addTime=2017-11-24 19:49:12 appop=NONE
mToken=AppWindowToken{c40a9b1 token=Token{ca91458 ActivityRecord{471753b u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity, isShadow:false t129}}}
mRootToken=AppWindowToken{c40a9b1 token=Token{ca91458 ActivityRecord{471753b u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity, isShadow:false t129}}}
mAppToken=AppWindowToken{c40a9b1 token=Token{ca91458 ActivityRecord{471753b u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity, isShadow:false t129}}}
WindowStateAnimator{2b01034 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity}:
mSurface=Surface(name=com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity)
mInteractingStatusBarWindow=Window{f9a1670 u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity}
mCurrentFocus=Window{f9a1670 u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity}
mFocusedApp=AppWindowToken{c40a9b1 token=Token{ca91458 ActivityRecord{471753b u0 com.isen.trace.police/com.isen.tz.wifitz.activity.MainActivity, isShadow:false t129}}}
PkgName=com.isen.trace.police
前面安装好了appium desktop版的话, 默认也会装一个appium-doctor,可以用来检查环境.我的目录如下:
"C:\Program Files (x86)\Appium\node_modules\.bin\appium-doctor.cmd" --android
Running Android Checks
✔ ANDROID_HOME is set to "C:\Users\Jac\AppData\Local\Android\Sdk"
✔ JAVA_HOME is set to "C:\Program Files\Java\jdk1.8.0_111."
✔ ADB exists at C:\Users\Jac\AppData\Local\Android\Sdk\platform-tools\adb.exe
✔ Android exists at C:\Users\Jac\AppData\Local\Android\Sdk\tools\android.bat
✔ Emulator exists at C:\Users\Jac\AppData\Local\Android\Sdk\tools\emulator.exe
✔ Android Checks were successful.
✔ All Checks were successful
当然你可以用npm装一个,这样就不需要找appium-doctor到底在那个目录了
npm install -g appium-doctor
appium-doctor.cmd --android
info AppiumDoctor Appium Doctor v.1.4.3
info AppiumDoctor ### Diagnostic starting ###
info AppiumDoctor ✔ The Node.js binary was found at: C:\Program Files\nodejs\node.exe
info AppiumDoctor ✔ Node version is 6.10.0
info AppiumDoctor ✔ ANDROID_HOME is set to: C:\Users\Jac\AppData\Local\Android\Sdk
info AppiumDoctor ✔ JAVA_HOME is set to: C:\Program Files\Java\jdk1.8.0_111
info AppiumDoctor ✔ adb exists at: C:\Users\Jac\AppData\Local\Android\Sdk\platform-tools\adb.exe
info AppiumDoctor ✔ android exists at: C:\Users\Jac\AppData\Local\Android\Sdk\tools\android.bat
info AppiumDoctor ✔ emulator exists at: C:\Users\Jac\AppData\Local\Android\Sdk\tools\emulator.exe
info AppiumDoctor ✔ Bin directory of %JAVA_HOME% is set
info AppiumDoctor ### Diagnostic completed, no fix needed. ###
info AppiumDoctor
info AppiumDoctor Everything looks good, bye!
info AppiumDoctor
主要是是 ANDROID_HOME 这个变量, 我的系统里面只设置了一个叫 ANDROID_SDK_ROOT 的变量...不知道是安装那个程序需要的. 内容是一样的.
当然是先新建个项目目录,然后npm init了,老规矩,然后安装appium官方推荐的wd.js作为客户端driver, 使用promise的方式.
npm init
npm install -S wd chai chai-as-promised colors
把appium跑起来. 这儿选择桌面版的运行. 运行了以后,看到提示服务器地址 http://127.0.0.1:4723
不得不先讲点儿理论了. 就是这个Webdriver和JSONWireProtocol建立了客户端与服务器的会话(Session).
那么Webdriver是什么呢, 说起源头还是Selenium的一部分, 是一个自动化的测试方式, 也就是通过客户端向服务器发命令进行测试的方式, 测试服务器再控制浏览器运行测试命令. 2012年, Webdriver被W3C标准化, 然后也就出现了很多不同的Webdriver的语言实现.
webdriver标准里面定义了服务器客户端使用RestFul API进行交互, 交互的协议称为wire protocol. 数据内容是JSON形式的, 这就是JSONWireProtocol了(也称作Webdriver Wire Protocol). 定义在github这儿.
按照这个定义页面上讲的, JSON Wire Protocol也已经过期, 因此, 目前来看Webdriver标准和JSONWireProtocol, Webdriver Wire Protocol其实就是一个东西在不同时期的叫法不同. 现在来看, 最新叫法就是W3C Webdriver 标准了.
建立连接的步骤分两步:
{
platformName: "Android",
deviceName: "HUAWEI MLA-AL10",
appPackage: "com.ss.android.ugc.aweme", //这个是抖音包名,包名可以在设置->应用->运行中的APP查看
appActivity: ".main.MainActivity", //主Activity,也可以在上面的路径查看
automationName: "UiAutomator1",
platformVersion: "4.4.2",
browserName: "",
noReset: "true", //非常重要! 如果不希望清空原APP数据直接启动,这个一定要为true
newCommandTimeout: 6000,
unicodeKeyboard: "true", //unicodeKeyboard和resetKeyboard非常重要! 设置后才能通过sendKeys发送中文
resetKeyboard: "true"
}
newCommandTimeout默认值为60, 意思是60秒无任何命令输入就断掉session. 如果是使用wd shell交互式命令, 或者使用Appium inspector(后面讲), 或者刚刚开始学习的话, 最好设置久一点,以免正看文档着呢, session就没了就无法操作了, 还得花几分钟重启session呢.
后面在常见问题里讲了如果session中断后如何重连的方式.
下面就可以发命令控制了. 控制浏览器的命令就写在Webdriver标准里面, 控制app的命令写在appium文档里面
界面上很多元素需要获得之后再去操作, H5可以在页面审查,看源码,知道ID和Class什么的用来定位,可是安卓原生应用该怎么办?
两个工具Appium inspector和UIAutomator Viewer
先说UIAutomator Viewer(简称UIAV),这个是跟android SDK一起安装的. 我的Windows下位置是:
C:\Users\Jac\AppData\Local\Android\Sdk\tools\uiautomatorviewer.bat
双击后打开, 点击第三个图标"Device Screeshot with compressed Hierarchy".
UIAV的运行与Appium session的运行冲突, 如果报错, 请将Appium 服务停掉, 相关的client session断掉.
然后左侧窗口将显示屏幕截图, 点击图上某一元素右侧会显示属性.
再说说Appium inspector(以下简称AI), 这是和Appium Desktop版一起安装的. 从界面上我估计这些货都是Electron开发的:)
Appium Desktop的服务器运行后,右上角第一个放大镜按钮点击,打开一个Appium的图形化界面客户端, 在这儿启动session后就会进入AI.
你可以在左侧一行一行的添加caps, 也可以在右侧直接将JSON复制进去并保存. json 格式正确将会成功保存, 格式错误将无法保存, 不过工具目前没有给提示.
输入后点击start session, 等待服务器将必要的app和测试app都装入手机, 将进入Appium inspector.
左侧是截图, 也可以选择元素, 不过位置总不太对. 上面还可以选择录制(眼睛图标), 录制下来的脚本可以选择不同的语言. 不过目前还有很大的问题, wd.js里面有的语法还是写错的...另外, 选择一步操作后,反应还是有些慢.似乎主要都耗时在截图传输上了.
进入AI界面后, 点击录制(start recording)按钮开始录制.
右侧可以选择客户端语言, 以wd.js为例(JS - WD(Promise)). 旁边三个按钮依次是:显示完整代码, 复制代码, 清空代码. 选择滑动, 做一个上滑操作, 显示完整代码, 如下:
// Requires the admc/wd client library
// (npm install wd)
// Then paste this into a .js file and run with Node 7.6+
const wd = require('wd');
const driver = wd.promiseChainRemote("localhost", 4723);
const caps = {"platformName":"android","deviceName":"myphone","app":"C:\\my.apk","browserName":""};
async function main () {
await driver.init(caps);
await (new TouchAction(driver))
.press({x: 326, y: 491})
.moveTo({x: 21: y: -166})
.release()
.perform()
await driver.quit();
}
main().catch(console.log);
注意代码中有两个问题
{x: 21, y: -166}
new TouchAction(driver)
写错, 应该是new wd.TouchAction(driver)
node test.js
, node 版本需要大于7.6, 主要是新版node实现了ES7里面的async和await.wd.js支持repl是个惊喜. 运行方式是在项目目录下面:
.\node_modules\.bin\wd shell
在这儿可以输入wd.js的命令, 进行交互.
因为交互敲命令需要时间,为避免session断开, 可以在caps里面增加一项"newCommandTimeout": 6000
, 延长超时时长(默认60秒)到6000秒.
依次(三条命令分三次)输入如下命令, 达到与代码类似的效果
const wd = require('wd'),
driver = wd.promiseChainRemote("localhost", 4723),
caps = {"platformName":"android","deviceName":"c195b35c","app":"C:\\r\\Appium\\wrtestcases\\apk\\TzRadar_373_v3.7.3_373_jiagu_sign.apk","browserName":"",
"newCommandTimeout": 6000},
TouchAction = wd.TouchAction;
driver.init(caps);
(new TouchAction(driver))
.press({x: 338, y: 716})
.moveTo({x: -5, y: -357})
.release()
.perform();
因为不同的测试平台可用的命令不同, 如果敲入了未实现的命令, 就会返回错误.
哪些命令可用? 参考的知识太多, 涉及webdriver, wd.js, appium, android...同时开这几个文档来回切换看好累...实操可用的如下:
//driver可用的命令, cb表示callback函数. 是否可以await尚未测试.
driver.lock(3) //锁定, 3貌似是延时
driver.backgroundApp(5) //后台运行, 5貌似是延时
driver.hideKeyboard()
driver.startActivity({appPackage: 'com.example.android.apis', appActivity: '.Foo'}, cb)
driver.openNotifications(cb);
driver.isAppInstalled("com.example.android.apis")
.then(function (isAppInstalled) { /*...*/ })
driver.installApp("path/to/my.apk")
driver.removeApp("com.example.android.apis")
driver.closeApp()
driver.launchApp()
driver.resetApp()
driver.shake()
driver.contexts().then(function (contexts) { /*...*/ }) //List all available contexts
driver.currentContext().then(function (context) { /*...*/ })
driver.context() //SWITCH TO DEFAULT CONTEXT
driver.getAppStrings().then(function (appStrings) { /*...*/ })
driver.deviceKeyEvent(wd.SPECIAL_KEYS.Home) //send key event
driver.getCurrentActivity().then(function (activity) { /*...*/ })
//swipe
function swipe(opts) {
var action = new wd.TouchAction(this);
action
.press({x: opts.startX, y: opts.startY})
.wait(opts.duration)
.moveTo({x: opts.endX, y: opts.endY})
.release();
return action.perform();
}
wd.addPromiseChainMethod('swipe', swipe);
// pinch
function pinch(el) {
return Q.all([
el.getSize(),
el.getLocation(),
]).then(function(res) {
var size = res[0];
var loc = res[1];
var center = {
x: loc.x + size.width / 2,
y: loc.y + size.height / 2
};
var a1 = new wd.TouchAction(this);
a1.press({el: el, x: center.x, y:center.y - 100}).moveTo({el: el}).release();
var a2 = new wd.TouchAction(this);
a2.press({el: el, x: center.x, y: center.y + 100}).moveTo({el: el}).release();
var m = new wd.MultiAction(this);
m.add(a1, a2);
return m.perform();
}.bind(this));
};
wd.addPromiseChainMethod('pinch', pinch);
wd.addElementPromiseChainMethod('pinch', function() {
return this.browser.pinch(this);
});
// ...
return driver.pinch(el);
// ...
return el.pinch();
// zoom
function zoom(el) {
return Q.all([
this.getWindowSize(),
this.getLocation(el),
]).then(function(res) {
var size = res[0];
var loc = res[1];
var center = {
x: loc.x + size.width / 2,
y: loc.y + size.height / 2
};
var a1 = new wd.TouchAction(this);
a1.press({el: el}).moveTo({el: el, x: center.x, y: center.y - 100}).release();
var a2 = new wd.TouchAction(this);
a2.press({el: el}).moveTo({el: el, x: center.x, y: center.y + 100}).release();
var m = new wd.MultiAction(this);
m.add(a1, a2);
return m.perform();
}.bind(this));
};
wd.addPromiseChainMethod('zoom', zoom);
wd.addElementPromiseChainMethod('zoom', function() {
return this.browser.zoom(this);
});
// ...
return driver.zoom(el);
// ...
return el.zoom();
// Scroll to an element.
return driver.elementByName().then(function (el) {
driver.execute("mobile: scroll", [{direction: "down", element: el.value}]);
});
//Pulls a file from the device.
driver.pullFile("Library/AddressBook/AddressBook.sqlitedb")
.then(function (base64File) { /*...*/ })
// Pushes a file to the device.
driver.pushFile(path, data)
//SETTINGS
var settings = driver.settings();
browser.updateSettings({'someSetting': true});
获取element有很多方式.
driver.elementById("com.isen.trace.police:id/agree_use")
,这也是AI录制下来的代码的默认方式. let xpath = ' /hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.Button[1]';
let el = await driver.elementByXPath(xpath);
el = await driver.elementByClassName('android.widget.Button', cb)
可是,上面的方式都需要我使用工具AI或者UIAV查看才知道怎么选中element. 能不能只看app界面就知道怎么选中element呢? 那就是下面这个方式了.
怎么查找? 文档里里没说... google到appium论坛里面才知道方法,例如:
el = driver.elementByAndroidUIAutomator('new UiSelector().className(\"android.widget.Button\")',cb)
事实上就是把android命令直接作为字符串传入, android命令参考安卓的开发文档
那么看看能不能只知道element的文字(.text)就可以找到呢(假如文字可以唯一确定的话)? UiSelector文档是这样说的:
UiSelector().text(String text)
UiSelector().textContains(String text)
UiSelector().textMatches(String regex)
UiSelector().textStartsWith(String text)
假如一个控件,文字中包括"不同意"几个字, 可以这么写:
el = driver.elementByAndroidUIAutomator('new UiSelector().textContains("不同意")',cb)
//element可用的命令: element.xxxx()
element.getTagName(cb) -> cb(err, name)
element.clear(cb) -> cb(err) //清除input等输入区域的内容
element.isSelected(cb) -> cb(err, selected)
element.isEnabled(cb) -> cb(err, enabled)
element.isDisplayed(cb) -> cb(err, displayed)
element.getLocation(cb) -> cb(err, location)
element.getLocationInView(cb) -> cb(err, location)
element.getSize(cb) -> cb(err, size)
element.getOrientation(cb) -> cb(err, orientation)
element.tap(cb) -> cb(err)
element.click(cb) -> cb(err)
发送文本(不支持中文!)
element.setText("string", cb)
element.sendKeys("string", cb)
element.type("string", cb)
墙外: 安卓上所有按键的keycode在这儿
当然最好npm install -S keycode-for-android
, 这样就好用多了.
主要的几个按键:
back 4
menu 82
home 37
volume_up 24
volume_down 25
volume_mute 164
power 26
示例:
driver.pressKeycode(4,cb) //返回键
如果连接已经断开, 首先要知道sessionID:
var sid
var sessions = await driver.sessions()
sid = sessions[0].id
如果连接还没有断开,可以显摆sessionId存下来:
sid = await driver.sessionId()
然后通过attach连接ID
await driver.attach(sid)
也有detach()断开连接.
appium git 论坛上有人提出了这个问题, 解决方式是运行:
await driver.execute( "mobile: performEditorAction", { "action": "search" } )
Android5.0以上支持 UiAutomator2, 以下支持UiAutomator1
UIautomator viewer org.xml.sax.SAXParseException;
很遗憾截止目前wd.js不支持, 只有Python和ruby支持. python下安装Appium-Python-Client
pip install Appium-Python-Client
npm install有些包的时候, 这些包总会在安装完之后运行一个post install的脚本, 这个脚本常常莫名其妙的导致一些问题, 其中一个就是要从不可访问的位置下载文件. 这些脚本总是自以为在哪儿都能下载任何地方的东西啊!
windows上幸好有cmder, 不过在设置菜单里面是没有proxy的设置的, 需要在命令行里面设置:
In cmder session:
Set http_proxy=http://[proxy]:[port]
Set https_proxy=http://[proxy]:[port]
In bash session:
export http_proxy=http://[proxy]:[port]
export https_proxy=http://[proxy]:[port]
In PowerShell session:
$env:http_proxy=http://[proxy]:[port]
$env:https_proxy=http://[proxy]:[port]
**
#-*- coding:utf-8 -*-
print('中文')
这样可以打印了,可是windows cmd打出来的是乱码?怎么办?
chcp 65001
改回gbk编码命令是:
chcp 936
如果想在python打印前自动更改,可以在python中调用这条命令
import subprocess
def toUTF8():
process = subprocess.Popen(['chcp', '65001'], shell=True)
process.communicate()
return
toUTF8()
python有个库叫做termcolor,先安装pip install termcolor
然后在代码中
from termcolor import colored
print(colored('some red text', 'red'))
还可以参考这儿**
python中没有像c一样的bool_value ? value_if_true : value_if_false的三元运算,替代方案是:
value_if_true if bool_value else value_if_false
python总是和别人不一样...
a_global_var = 100
def func():
global a_global_var
a_global_var = 200
func()
print(a_global_var)
这主要是参考这儿
适用于python2.7是其中一个lambda表达式的答案,在windows上也测试通过了
import os
cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))
cmd_exists('ls')
科技的最大谎言是什么?就是他保证你生活的轻松而富足。难道不是吗?近100多年以来,科技都是一直在这么宣传自己的。科技说,他可以帮你干活儿帮你赚钱。比如说一个工厂的工人他买了一个机器人,然后把它放在产线上。帮他干活,帮他赚钱。那个人他就可以天天在家里享受这种无忧无虑,又有钱赚的日子啦。可是事实是怎样呢?我们都知道了。那个工人他其实买不起机器人。买得起机器人的工厂呢,最后把他解雇了。
grav相对wp,确实在很多方面体验都要好,比如对md的支持更加完美,界面也更好看,不需要数据库等。然而写文章的体验却不好。
目前在线写文章,还是通过grav的官方admin。创建文章的步骤要繁琐很多,除了写文章标题和内容外,还要选择目录结构、目录名、模板、类别、发布时间等。并且遭遇了一次没有保存,第二天点击保存直接跳出到登录页面(登录过期),导致辛辛苦苦写的内容全部不见了。。。
所以想了想,还是用本地编辑器sublime text或者vs code写好了再通过ftp工具filezilla上传吧!
网络低延时技术,或者可以称之为实时技术,对远程人人交互和远程人机交互有很大的作用。以前有很多设想,都受到网络延迟不可控、带宽低、带宽费用昂贵的影响。而最近一些技术的成熟让这类创新成为可能,或者具备爆发的可能。
现在的一个爆发的应用就是线上娃娃机。使用了信令、语音、视频的低延时的技术,加上和娃娃机这个物体的信令连接,实现了远程操控。而移动宽带费用的下降,则可以实现实时远程操控移动物体,并且不受地理位置的限制。这可能就会有很多应用,可能会真正带来一些远程控制机器人的民用化。AR/VR的输入方式,也会使得这些操控更加直觉和方便。而人工智能的加入,会逐渐提高这些机器人的自主性,使其可以相应无需精确操作的目的指向的指令,如端水、买菜、到达目的地等。
准备迁移wp到基于markdown的Php平台,最好没有数据库这类东东,因为我用的原来万网的虚拟主机,只提供php空间,没有ssh后台登录界面。搜来搜去,发现grav和typecho是比较高关注的两个,要求都是php5.5以上。后来又发现typecho需要数据库,就只有选择grav了。
grav有三个下载包概念:skeleton/theme/plugin。后两个都知道,theme是主题,plugin是插件,但skeleton是什么?翻译过来是“骨架”,其实是框架、整体安装包的意思,包含了特定的一些plugin、示例页面等。这道和grav“坟墓”很搭配啊。不过这倒是谁起的这么个名字,估计要犯了很多国人的忌讳...
要有的功能,第一是有个blog的样子,另一个是能通过网页写md,毕竟通过ftp上传md还是很麻烦的。
这两个功能都不是grav core核心含有的,而是通过组织页面、插件等完成的。还好,有两个skeleton,一个是grav core + admin,另一个是在skeleton里搜索blog, 下载其中的blog site
下载完成后,需要对这两个skeleton合二为一。以blog site为基础,把admin中的user/plugins/中的除了error和problems的目录(包括admin/email/login/form/markdown-notices),全部复制到blog site对应的目录中(user/plugins)。
然后打包、上传ftp、解压缩、使用浏览器安装即可。
如安装到your.site/grav中,进入your.site/grav/admin,创建admin用户,密码必须是8位大小写加数字,就可以在pages中写文章了。
文章需要写在home下面,使用模板是item,options里面taxonomies/category选择blog,这样才搜索得到。
可以看一下搜索的设置,plugins/simplesearch里面,有个category filter,写的是blog,所以只有文章标了category是blog的才会被搜到。
解释一下涉及的目录:
pages下面的目录可以用数字编号如01.home,一方面这会让其出现在一级菜单,另一方面标明菜单顺序。小数点前面的内容不会在页面和url上显示。
.md开始时在两个---之间的,是此文件的配置说明,以YAML格式书写。这种在文件最前面以YAML形式书写的配置文件又称为YAML Front Matter,简称YFM,是标准的。
支持的Markdown语法在这儿
exitwp项目地址,克隆下来(python项目,需要python运行,2.7试过没问题)
git clone --depth=1 https://github.com/thomasf/exitwp.git
wordpress后台->工具->导出,将文章下载为wordpressxxxx.xml格式
把wordpress的下载文章复制到exitwp/wordpress-xml下面
你可能需要安装几个exitwp.py的依赖
pip install pyyaml bs4 html2text
在exitwp下运行:
python exitwp.py
如果还有依赖没有安装就装一下
在exitwp/build目录下可以找到。exitwp是为了jekyll博客准备的,而这个博客是ruby的,不过至少你已经有了md文件
在把8266的(4M Dio)的固件通过esptool.py烧录到8285(1M)的过程中,发现烧录后不能启动。
8266使用的命令如下:
esptool.py --port com3 write_flash 0x00000 esp.flash.bin 0x10000 esp.irom0text.bin 0x3fc000 esp_init_data_default.bin
8285命令如下:
esptool.py --port com3 write_flash 0x00000 esp.flash.bin 0x10000 esp.irom0text.bin 0xfc000 esp_init_data_default.bin
发现esptool.py对8285也是按8266识别的,这倒也没错。识别出来的存储空间也是1M,没错。
连接8285串口,74880波特率查看BOOT ROM打印的错误,如下:
ets Jan 8 2013,rst cause:2, boot mode:(3,6)<\r><\n>
load 0x40100000, len 32440, room 16 <\r><\n>
tail 8<\r><\n>
chksum 0xef<\r><\n>
load 0x00000000, len 0, room 0 <\r><\n>
tail 0<\r><\n>
chksum 0xef<\r><\n>
load 0x00000000, len 0, room 8 <\r><\n>
tail 0<\r><\n>
chksum 0xef<\r><\n>
csum 0xef<\r><\n>
csum err<\r><\n>
ets_main.c
后面两个load 0x0000000显然是有问题的,拿一个正常的8285试了一下,信息如下:
load 0x40100000, len 29408, room 16 <\r><\n>
tail 0<\r><\n>
chksum 0x70<\r><\n>
load 0x3ffe8000, len 1968, room 8 <\r><\n>
tail 8<\r><\n>
chksum 0x72<\r><\n>
load 0x3ffe87b0, len 692, room 0 <\r><\n>
tail 4<\r><\n>
chksum 0x4c<\r><\n>
csum 0x4c<\r><\n>
<\r><\n>
果然后面两个load的地址是不对的。
网上查了一下,8285应将flash_mode设置为dout,如下:
esptool.py --port com3 write_flash --flash_mode dout 0x00000 esp.flash.bin 0x10000 esp.irom0text.bin 0xfc000 esp_init_data_default.bin
烧录后果然成功了。
几个flash mode的解释:
3.spi mode :
1)QIO ,for flash that support quad r/w operation(e.g. W25Q)
2)QOUT,for flash that support quad read operation(e.g. W25Q)
3)DIO,for flash that support dual r/w operation(e.g. W25Q &W25X)
4)DOUT, for flash that support dual read operation(e.g. W25Q &W25X)