物联网】如何实现 ESP32 固件的 OTA 在线升级和更新?
1、背景
在实际产品开发过程中,在线升级可以远程解决产品软件开发引入的问题,更好地满足用户需求。
2、OTA 简介
OTA(空中)更新是使用 Wi-Fi 连接而不是串行端口将固件加载到 ESP 模块的过程。
2.1、ESP32 的 OTA 升级有三种方式:
Arduino IDE:主要用于软件开发阶段,实现不接线固件烧写
Web Browser:通过 Web 浏览器手动提供应用程序更新模块
HTTP Server:自动使用http服务器 - 针对产品应用
在三种升级情况下,必须通过串行端口完成第一个固件上传。
OTA 进程没有强加的安全性,需要确保开发人员只能从合法/受信任的来源获得更新。更新完成后,模块将重新启动,并执行新的代码。开发人员应确保在模块上运行的应用程序以安全的方式关闭并重新启动。
2.2、保密性 Security
模块必须以无线方式显示,以便通过新的草图进行更新。 这使得模块被强行入侵并加载了其他代码。 为了减少被黑客入侵的可能性,请考虑使用密码保护您的上传,选择某些OTA端口等。
可以提高安全性的 ArduinoOTA 库接口:
void setPort(uint16_t port);
void setHostname(const char* hostname);
void setPassword(const char* password);
void onStart(OTA_CALLBACK(fn));
void onEnd(OTA_CALLBACK(fn));
void onProgress(OTA_CALLBACK_PROGRESS(fn));
void onError(OTA_CALLBACK_ERROR (fn));
已经内置了某些保护功能,不需要开发人员进行任何其他编码。ArduinoOTA和espota.py使用Digest-MD5来验证上传。使用MD5校验和,在ESP端验证传输数据的完整性。
2.2、OTA 升级策略 - 针对 http
ESP32 连接 HTTP 服务器,发送请求 Get 升级固件;每次读取1KB固件数据,写入Flash。
ESP32 SPI Flash 内有与升级相关的(至少)四个分区:OTA data、Factory App、OTA_0、OTA_1。其中 FactoryApp 内存有出厂时的默认固件。
首次进行 OTA 升级时,OTA Demo 向 OTA_0 分区烧录目标固件,并在烧录完成后,更新 OTA data 分区数据并重启。
系统重启时获取 OTA data 分区数据进行计算,决定此后加载 OTA_0 分区的固件执行(而不是默认的 Factory App 分区内的固件),从而实现升级。
同理,若某次升级后 ESP32 已经在执行 OTA_0 内的固件,此时再升级时 OTA Demo 就会向 OTA_1 分区写入目标固件。再次启动后,执行 OTA_1 分区实现升级。以此类推,升级的目标固件始终在 OTA_0、OTA_1 两个分区之间交互烧录,不会影响到出厂时的 Factory App 固件。
3、OTA 实例解析
3,1、Arduino IDE 方案固件更新
从 Arduino IDE 无线上传模块适用于以下典型场景:
在固件开发过程中,通过串行加载更快的替代方案 - 用于更新少量模块,只有模块在与 Arduino IDE 的计算机相同的网络上可用。
参考实例:
include <WiFi.h>
include <ESPmDNS.h>
include <WiFiUdp.h>
include <ArduinoOTA.h>
const char* ssid = "..........";
const char* password = "..........";
void setup() {
Serial.begin(115200);
Serial.println("Booting");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Connection Failed! Rebooting...");
delay(5000);
ESP.restart();
}
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
// ArduinoOTA.setHostname("myesp32");
// No authentication by default
// ArduinoOTA.setPassword("admin");
// Password can be set with it's md5 value as well
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
})
.onEnd([]() {
Serial.println("\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
ArduinoOTA.handle();
}
3,2、Web Browser 方案固件更新
该方案使用场景:
直接从 Arduino IDE 加载是不方便或不可能的
用户无法从外部更新服务器公开 OTA 的模块
在设置更新服务器不可行时,将部署后的更新提供给少量模块
参考实例:
include <WiFi.h>
include <WiFiClient.h>
include <WebServer.h>
include <ESPmDNS.h>
include <Update.h>
const char* host = "esp32";
const char* ssid = "xxx";
const char* password = "xxxx";
WebServer server(80);
/*
- Login page
*/
const char* loginIndex =
"
"<table width='20%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"ESP32 Login Page
"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<td>Username:</td>"
"<td><input type='text' size=25 name='userid'><br></td>"
"</tr>"
"<br>"
"<br>"
"<tr>"
"<td>Password:</td>"
"<td><input type='Password' size=25 name='pwd'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
"</tr>"
"</table>"
"
""";
/*
- Server Index Page
*/
const char* serverIndex =
""
"
""
"<input type='submit' value='Update'>"
"</form>"
"
"";
/*
- setup function
*/
void setup(void) {
Serial.begin(115200);
// Connect to WiFi network
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
/use mdns for host name resolution/
if (!MDNS.begin(host)) { //http://esp32.local
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
/return index page which is stored in serverIndex /
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", loginIndex);
});
server.on("/serverIndex", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
/handling uploading firmware file /
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
server.begin();
}
void loop(void) {
server.handleClient();
delay(1);
}
3.3、HTTP 服务器实现更新
ESPhttpUpdate 类可以检查更新并从 HTTP Web 服务器下载二进制文件。可以从网络或 Internet 上的每个 IP 或域名地址下载更新,主要应用于远程服务器更新升级。
参考实例:
/**
AWS S3 OTA Update
Date: 14th June 2017
Author: Arvind Ravulavaru https://github.com/arvindr21
Purpose: Perform an OTA update from a bin located in Amazon S3 (HTTP Only)
Upload:
Step 1 : Download the sample bin file from the examples folder
Step 2 : Upload it to your Amazon S3 account, in a bucket of your choice
Step 3 : Once uploaded, inside S3, select the bin file >> More (button on top of the file list) >> Make Public
Step 4 : You S3 URL => http://bucket-name.s3.ap-south-1.amazonaws.com/sketch-name.ino.bin
Step 5 : Build the above URL and fire it either in your browser or curl it curl -I -v http://bucket-name.ap-south-1.amazonaws.com/sketch-name.ino.bin
to validate the same
Step 6: Plug in your SSID, Password, S3 Host and Bin file below
Build & upload
Step 1 : Menu > Sketch > Export Compiled Library. The bin file will be saved in the sketch folder (Menu > Sketch > Show Sketch folder)
Step 2 : Upload bin to S3 and continue the above process
// Check the bottom of this sketch for sample serial monitor log, during and after successful OTA Update
*/
include <WiFi.h>
include <Update.h>
WiFiClient client;
// Variables to validate
// response from S3
int contentLength = 0;
bool isValidContentType = false;
// Your SSID and PSWD that the chip needs
// to connect to
const char* SSID = "YOUR-SSID";
const char* PSWD = "YOUR-SSID-PSWD";
// S3 Bucket Config
String host = "bucket-name.s3.ap-south-1.amazonaws.com"; // Host => bucket-name.s3.region.amazonaws.com
int port = 80; // Non https. For HTTPS 443. As of today, HTTPS doesn't work.
String bin = "/sketch-name.ino.bin"; // bin file name with a slash in front.
// Utility to extract header value from headers
String getHeaderValue(String header, String headerName) {
return header.substring(strlen(headerName.c_str()));
}
// OTA Logic
void execOTA() {
Serial.println("Connecting to: " + String(host));
// Connect to S3
if (client.connect(host.c_str(), port)) {
// Connection Succeed.
// Fecthing the bin
Serial.println("Fetching Bin: " + String(bin));
// Get the contents of the bin file
client.print(String("GET ") + bin + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
return;
}
}
// Once the response is available,
// check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
{{BIN FILE CONTENTS}}
*/
while (client.available()) {
// read line till /n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. Exiting OTA Update.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/octet-stream") {
isValidContentType = true;
}
}
}
} else {
// Connect to S3 failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(host) + " failed. Please check your setup");
// retry??
// execOTA();
}
// Check what is the contentLength and if content type is application/octet-stream
Serial.println("contentLength : " + String(contentLength) + ", isValidContentType : " + String(isValidContentType));
// check contentLength and content type
if (contentLength && isValidContentType) {
// Check if there is enough to OTA Update
bool canBegin = Update.begin(contentLength);
// If yes, begin
if (canBegin) {
Serial.println("Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience!");
// No activity would appear on the Serial monitor
// So be patient. This may take 2 - 5mins to complete
size_t written = Update.writeStream(client);
if (written == contentLength) {
Serial.println("Written : " + String(written) + " successfully");
} else {
Serial.println("Written only : " + String(written) + "/" + String(contentLength) + ". Retry?" );
// retry??
// execOTA();
}
if (Update.end()) {
Serial.println("OTA done!");
if (Update.isFinished()) {
Serial.println("Update successfully completed. Rebooting.");
ESP.restart();
} else {
Serial.println("Update not finished? Something went wrong!");
}
} else {
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
}
} else {
// not enough space to begin OTA
// Understand the partitions and
// space availability
Serial.println("Not enough space to begin OTA");
client.flush();
}
} else {
Serial.println("There was no content in the response");
client.flush();
}
}
void setup() {
//Begin Serial
Serial.begin(115200);
delay(10);
Serial.println("Connecting to " + String(SSID));
// Connect to provided SSID and PSWD
WiFi.begin(SSID, PSWD);
// Wait for connection to establish
while (WiFi.status() != WL_CONNECTED) {
Serial.print("."); // Keep the serial monitor lit!
delay(500);
}
// Connection Succeed
Serial.println("");
Serial.println("Connected to " + String(SSID));
// Execute OTA Update
execOTA();
}
void loop() {
// chill
}
/*
- Serial Monitor log for this sketch
- If the OTA succeeded, it would load the preference sketch, with a small modification. i.e.
- Print
OTA Update succeeded!! This is an example sketch : Preferences > StartCounter
- And then keeps on restarting every 10 seconds, updating the preferences
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 Connecting to SSID ...... Connected to SSID Connecting to: bucket-name.s3.ap-south-1.amazonaws.com Fetching Bin: /StartCounter.ino.bin Got application/octet-stream payload. Got 357280 bytes from server contentLength : 357280, isValidContentType : 1 Begin OTA. This may take 2 - 5 mins to complete. Things might be quite for a while.. Patience! Written : 357280 successfully OTA done! Update successfully completed. Rebooting. ets Jun 8 2016 00:22:57 rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 OTA Update succeeded!! This is an example sketch : Preferences > StartCounter Current counter value: 1 Restarting in 10 seconds... E (102534) wifi: esp_wifi_stop 802 wifi is not init ets Jun 8 2016 00:22:57 rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0x00 clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0008,len:8 load:0x3fff0010,len:160 load:0x40078000,len:10632 load:0x40080000,len:252 entry 0x40080034 OTA Update succeeded!! This is an example sketch : Preferences > StartCounter Current counter value: 2 Restarting in 10 seconds... ....
从《天道》的角度谈谈产品规划
原创2023-02-24 21:05·产品人卫朋
今天主要借用《天道》中丁元英的商业案例来谈谈产品规划这个话题。
《天道》这部被众人追捧的影视剧来源于豆豆的成名作《遥远的救世主》。
如果没有全局做过产品或者市场的规划,而且是初次接触这部剧。
你就会惊叹于主人公的组局、布局,以及成局的能力。
从互联网拥簇的评论声中,也可见一斑。
剧中的丁元英甚至都有一种被神化的趋势。
而随着个人知识和阅历的增加,再加上每年也都要做产品规划。
也逐渐对这部剧或者这本书有了一些新的认识。
究其本质,这是一种战略性的思维,也是一种规划的能力。
更是一种市场与内部能力的匹配过程。
笔者之前也分享过这块的内容,也看到了一些质疑。
怎么能用虚拟的案例做讲解呢?
其实这么做的原因主要有两点考虑:
首先,这部剧中的商业案例的整体逻辑是自洽的,而且也符合当时的商业环境。
其次,整部剧将整个商业案例完整地呈现了出来,也包括其中很多的决策细节。
这就要比分析现实案例直观得多,也更加有指导意义。
再回到产品规划这个话题上来。
产品规划从本质上来说是一种推演能力,也就是根据第一性原则推演产品从0到1、从1到100的一个过程。
如果说一款产品是一个点的话,那产品规划便是通过构造一种系统能力以达成企业最终的商业目的。
第一性原理是埃隆·马斯克非常推崇的一种思维模型。
通常来说,企业愿景对应的便是企业的第一性原则。
围绕第一性原则可以激发资源优势、制定细分市场目标,最终实现企业目标。
下面以影视剧中丁元英操盘的格律诗音响项目为例,谈谈产品规划。
格律诗音响公司的企业愿景是实现王庙村生产力和市场的对接,最终实现农户脱贫。
这是企业的愿景,同时也是丁元英承诺要给红颜知己芮小丹创造的神话。
启动一个项目或产品,资源和人力配置是你首先要考虑的。
企业在不同的发展周期,对人的要求是有很大差异的。
丁元英在分析完这些人的本质之后,并没有把自己的全套计划完整地告诉原始这些人。
而是通过市场的变化来淘汰掉一部分人。
因为这部分人现在不淘汰掉,在以后的市场变化中,可能会给公司带来毁灭性的灾难。
下面就先梳理一下其中的关键人物:
丁元英作为格律诗音响项目的唯一操盘手,全局规划了整个项目。
他的优势是自己在欧洲的人脉和战略规划能力,以及在欧阳雪等人心中的影响力。
同时,作为发烧级音乐玩家,他对音箱的独特见解也为他们打造差异化的产品起到了关键助力作用。
差异化的意思是相比于竞争对手,你的独特优势或者护城河,没有这个前提,整个策略也就无从谈起,这为他们赢得了时间上的先机。
在音响这个市场,竞品已经很成功了,而且他们提供的价值点已经被用户接受。
如果按照他们的价值点去做产品,你就永远只能跟在他们身后。
这时候就需要找一个跟他们不一样的价值点,做差异化。
欧阳雪这个人呢,做事很踏实,很讲义气,不贪心。优势是人脉、资金和社会地位。
这个人的价值在于她对格律诗的绝对控股,这样就可以确保关键决策权的归属。
由于每个人的认知水平的限制,很多时候不同个体看到的终局是有极大差异的,这个时候你就需要考虑如何增加成事的确定性。
如果开公司的话,股权的分配问题是你优先要考虑的。
不赚钱的时候,大家还都能力出一孔。一旦公司有起色,每个人就开始有自己的诉求,不确定性也就随之而来。
肖亚文见过世面,知道公司怎么运行,知道商务谈判和商务合作的事情,是很精明的职场人物。
而冯世杰和叶晓明想成就一番事业,但没有机会,能够脚踏实地的做事情,但眼光欠缺。刘冰是小人物,唯利是图,关键时刻不能顶上,迟早会被淘汰。
叶晓明,冯世杰,刘冰这三个人的优势就是懂音乐,会组装,可以作为高级技术工。
同时,这三人和王庙村农民有一定的关系,可以作为连接的纽带,核心竞争力是技术和人脉。
乐圣公司的掌舵人是林雨峰(竞争对手),但太过刚硬,只知道进攻,不懂防守,考虑问题存在漏洞。
这就有点类似竞争分析了,通过分析竞争对手的漏洞,找到破局点,制定商业竞争策略。
接下来就需要统一思想了:
想要以小博大,达成乐圣跟王庙村合作的目的,就必须把优势发挥到最大效果。
这才有几次股东开会,召集农民兄弟一起开会等,就是为了统一思想。
市场的生存竞争非常残酷,胜负往往就在毫厘之间,微弱的优势都可能成为关键一环,你比他多一口气,你就是赢家。
最后,丁元英就把这些人的优势资源整合起来,按照需要组建公司,精心规划。
详细案例分析可以参阅笔者之前的文章。
卫朋
人人都是产品经理受邀专栏作家,**** 嵌入式领域新星创作者、资深技术博主。2020 年 8 月开始写产品相关内容,截至目前,人人都是产品经理单渠道阅读 56 万+,鸟哥笔记单渠道阅读200 万+,**** 单渠道阅读 210 万+,51CTO单渠道阅读 180 万+。
卫朋入围2021/2022年人人都是产品经理平台年度作者,光环国际学习社区首批原创者、知识合作伙伴,商业新知 2021 年度产品十佳创作者,腾讯调研云2022年达人榜第三名。
文章被人人都是产品经理、****、华为云、运营派、产品壹佰、鸟哥笔记、光环国际、商业新知、腾讯调研云等头部垂直类媒体转载。文章见仁见智,各位看官可策略性选择对于自己有用的部分。
推荐阅读
-
物联网】如何实现 ESP32 固件的 OTA 在线升级和更新?
-
反传销网8月30日发布:视频区块链里的骗子,币里的韭菜,杜子建骂人了!金融大V周召说区块链!——“一小帮骗子玩一大帮小白,被割韭菜,小白还轮流被割,割的就是你!” 什么区块链,统统是骗子 作者:周召(知乎金融领域大V,毕业于上海财经大学,目前任职上海某股权投资基金合伙人) 有人问我,区块链现在这么火,到底是不是骗局? 我的回答是: 是骗局。而且我并不是说数字货币是骗局,而是说所有搞区块链的都是骗局。 -01- 区块链是一种鸡肋技术 人类社会任何技术的发明应用,本质都是为了提高社会的生产效率。而所谓区块链技术本质不过是几种早已成熟的技术的大杂烩,冗余且十分低效,除了提高了洗钱和诈骗的效率以外,对人类社会的进步毫无贡献。 真正意义上的区块链得包含三个要素:分布式系统(包括记账和存储),无法篡改的数据结构,以及共识算法,三者互为基础和因果,就像三体世界一样。看上去挺让人不明觉厉的,而经过几年的瞎折腾,稍微懂点区块链的碰了几次壁后都已经渐渐明白区块链其实并没有什么卵用,区块链技术已经名存实亡,沦为了营销工具和传销组织的画皮。 因为符合上述定义的、以比特币为代表的原教旨区块链技术,是反效率的,从经济学角度来说,不但不是一种帕累托改进,甚至还可以说是一种帕累托倒退。 原教旨区块链技术的效率十分低下,因为要遍历所有节点,只能做非常轻量级的数据应用,一旦涉及到大量的数据传输与更新,区块链就瞎了。 一方面整条链交易速度会极慢,另一方面数据库容量极速膨胀,考虑到人手一份的存储机制,区块链其实是对存储资源和能源的一种极大的浪费。 这里还没有加上为了取得所谓的共识和挖矿消耗的巨大的能源,如果说区块链技术是屎,那么这波区块链投机浪潮可谓人类历史上最大规模的搅屎运动。 区块链也验证不了任何东西。 所谓的智能合约,即不智能,也非合约。我看有人还说,如果有了智能合约,就可以跟老板签一份放区块链上,如果明年销售业绩提升30%,就加薪10%,由于区块链不能篡改,不能抵赖,所以老板必须得执行,说得有板有眼,不懂行的愣一看,好像还真是那么回事。 但仔细一想,问题就来了。首先,在区块链上如何证明你真的达到了30%业绩提升?即便真的达到老板耍赖如何执行? 也就是说,如果区块链真这么厉害,要法院和仲裁干什么。 人类社会真正的符合成本效益原则的是代理制度。之前有人说要用区块链改造注册会计师行业,我不知道他准备怎么设计,我猜想他思路大概是这样的,首先肯定搞去中心化,让所有会计师到链上来,然后一个新人要成为注册会计师就要所有会计师同意并记录在链上。 那我就请问了,我每天上班累死累活,为什么还要花时间去验证一个跟我无关的的人的专业能力?最优做法当然是组织一个委员会,让专门的人来负责,这不就是现在注册会师协会干的事儿吗?区块链的逻辑相当于什么事情都要拿出来公投,这个绝对是扯淡的。 当然这么说都有点抬举区块链了,区块链技术本身根本没有判断是非能力,如果这么高级的人工智能,靠一个无脑分布式记账就能实现的话,我们早就进入共产主义社会了。 虽然EOS等数字货币采用了超级节点,通过再中心化的方式提高效率,有点行业协会的意思,是对区块链原教旨主义的一种修正,但是依然无法突破区块链技术最本质的局限性。有人说,私有链和联盟链是区块链技术的未来,也是扯淡,因为区块链技术没有未来。如果有,说明他是包装成区块链的伪区块链技术。 区块链所涉及的所有底层技术,不管是分布式数据库技术,加密技术,还是点对点传输技术等,基本都是早已存在没什么秘密可言的技术。 比特币系统最重要的特性是封闭性和自洽性,他验证不了任何系统自身以外产生的信息的真实性。 所谓系统自身产生的信息,就是数据库数据的变动信息,有价值的基本上有且只有交易信息。所以说比特币最初不过是中本聪一种炫技的产物,来证明自己对几种技术的掌握,你看我多牛逼,设计出了一个像三体一样的系统。因此,数字货币很有可能是区块链从始至终唯一的杀手应用。 比特币和区块链概念从诞生到今天已经快10年了,很多人说区块链技术在爆发的前夜,但这个前夜好像是不是有点过长了啊朋友,跟三体里的长夜有一拼啊。都说区块链技术像是90年代初的互联网,可是90年代初的互联网在十年发展后,已经出现了一大批伟大的公司,阿里巴巴在99年都成立了,区块链怎么除了币还是币呢? 正规的数字货币未来发展的形式无外乎几种,要么就是论坛币形式,或者类似股票的权益凭证等。问题是论坛币和股票之前,本来也都电子化了,区块链来了到底改变了什么呢? 所有想把TOKEN和应用场景结合起来的人最后都很痛苦,最后他们会发现区块链技术就是脱裤子放屁,自己辛苦搞半天,干嘛不自己作为中心关心门来收钱?最后这些人都产生了价值的虚无感,最终精神崩溃,只能发币疯狂收割韭菜,一边嘴里还说着我是个好人之类的奇怪的话。 因此,之前币圈链圈还泾渭分明,互相瞧不起,但这两年链圈逐渐坐不住了,想着是不是趁着泡沫没彻底破灭之前赶快收割一波,不然可能什么都捞不着了。 前段时间和一个名校毕业的链圈朋友瞎聊天,他说他们“致力于用区块链技术解决数字版权保护问题”,我就问他一个问题,你们如何保证你链的版权所有权声明是真实的,万一盗版者抢先一步把数据放在链上怎么办。他说他们的解决方案是连入国家数字版权保护中心的数据库进行验证…… 所以说区块链技术就是个鸡肋,研究到最后都会落入效率与真实性的黑洞,很多人一头扎进链圈后才发现,真正意义上的区块链技术,其实什么都干不了。 -02- 不是蠢就是坏的区块链媒体 空气币和区块链的造富神话,让区块链自媒体也开始迎风乱扭。一群群根本不知道区块链为何物的妖魔鬼怪纷纷进驻区块链自媒体战场,开始大放厥词胡编乱造。 任何东西,但凡只要和区块,链,分,分布式,记账,加密,验证,可追溯等等这些个关键词沾到哪怕一点点,这些所谓的区块链媒体人就会像狗闻到了屎了一样疯狂地把区块链概念往上套。 这让我想起曾经一度也是热闹非凡的物联网,我曾经去看过江苏一家号称要改变世界的“物联网”企业,过去一看是生产路由器的,我黑人问号脸,对方解释说没有路由器万物怎么互联,我觉得他说得好有道理,竟无言以对。 好,下面让我们进入奇葩共赏析时间,来看看区城链媒体经常有哪些危言耸听的奇谈怪论 区块链(分布式记账)的典型应用是*?? 正如前面所说,真正意义上的区块链分布式记账,不光包括“记”这个动作,还包括分布式存储和共识机制等。而*诞生远远早于区块链这个词的出现,勉强算是“分布式编辑”吧,就被很多区块链媒体拿来强行充当区块链技术应用的典范。 其实事实恰恰相反,*恰恰是去中心化失败的典范,现在如果没有精英和专业人士的编辑和维护,*早就没法看了。 区块链会促进社会分工?? 罗振宇好像就说过类似的话,虽然罗振宇说过很多没有逻辑的话,但这句话绝对是最没逻辑思维的。很多区块链自媒体也常常用这句话来忽悠老百姓,说分工代表效率提高社会进步,而区块链“无疑”会促进分工,他们的理由仅仅是分工和分布式记账都共用一个“分”字,就强行把他们扯到一起。 实际情况恰恰相反,区块链是逆分工的,区块链精神是号召所有人积极地参与到他不擅长也不想掺合的事情里面去。 区块链不能像上帝一样许诺他的子民死后上天国,只能给他们许诺你们是六度人脉中的第一级,我可以赚后面五级人的钱,你处于金字塔的顶端。